blob: 5e9b0eca39b0e25504f07aca2aa0a3b0d7717361 [file] [log] [blame]
/*
* u_sdio.c - utilities for USB gadget serial over sdio
*
* This code also borrows from drivers/usb/gadget/u_serial.c, which is
* Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
* Copyright (C) 2008 David Brownell
* Copyright (C) 2008 by Nokia Corporation
* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
*
* This program from the Code Aurora Forum is free software; you can
* redistribute it and/or modify it under the GNU General Public License
* version 2 and only version 2 as published by the Free Software Foundation.
* The original work available from [kernel.org] is subject to the notice below.
*
* This software is distributed under the terms of the GNU General
* Public License ("GPL") as published by the Free Software Foundation,
* either version 2 of that License or (at your option) any later version.
*
*/
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/termios.h>
#include <linux/debugfs.h>
#include <mach/sdio_al.h>
#include <mach/sdio_cmux.h>
#include "u_serial.h"
#define SDIO_RX_QUEUE_SIZE 8
#define SDIO_RX_BUF_SIZE 2048
#define SDIO_TX_QUEUE_SIZE 8
#define SDIO_TX_BUF_SIZE 2048
/* 1 - DUN, 2-NMEA/GPS */
#define SDIO_N_PORTS 2
static struct sdio_portmaster {
struct mutex lock;
struct gsdio_port *port;
struct platform_driver gsdio_ch;
} sdio_ports[SDIO_N_PORTS];
static unsigned n_sdio_ports;
struct sdio_port_info {
/* data channel info */
char *data_ch_name;
struct sdio_channel *ch;
/* control channel info */
int ctrl_ch_id;
};
struct sdio_port_info sport_info[SDIO_N_PORTS] = {
{
.data_ch_name = "SDIO_DUN",
.ctrl_ch_id = 9,
},
{
.data_ch_name = "SDIO_NMEA",
.ctrl_ch_id = 10,
},
};
static struct workqueue_struct *gsdio_wq;
struct gsdio_port {
unsigned port_num;
spinlock_t port_lock;
unsigned n_read;
struct list_head read_pool;
struct list_head read_queue;
struct work_struct push;
unsigned long rp_len;
unsigned long rq_len;
struct list_head write_pool;
struct work_struct pull;
unsigned long wp_len;
struct work_struct notify_modem;
struct gserial *port_usb;
struct usb_cdc_line_coding line_coding;
int sdio_open;
int sdio_probe;
int ctrl_ch_err;
struct sdio_port_info *sport_info;
struct delayed_work sdio_open_work;
#define SDIO_ACM_CTRL_RI (1 << 3)
#define SDIO_ACM_CTRL_DSR (1 << 1)
#define SDIO_ACM_CTRL_DCD (1 << 0)
int cbits_to_laptop;
#define SDIO_ACM_CTRL_RTS (1 << 1) /* unused with full duplex */
#define SDIO_ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */
int cbits_to_modem;
/* pkt logging */
unsigned long nbytes_tolaptop;
unsigned long nbytes_tomodem;
};
void gsdio_free_req(struct usb_ep *ep, struct usb_request *req)
{
kfree(req->buf);
usb_ep_free_request(ep, req);
}
struct usb_request *
gsdio_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags)
{
struct usb_request *req;
req = usb_ep_alloc_request(ep, flags);
if (!req) {
pr_err("%s: usb alloc request failed\n", __func__);
return NULL;
}
req->length = len;
req->buf = kmalloc(len, flags);
if (!req->buf) {
pr_err("%s: request buf allocation failed\n", __func__);
usb_ep_free_request(ep, req);
return NULL;
}
return req;
}
void gsdio_free_requests(struct usb_ep *ep, struct list_head *head)
{
struct usb_request *req;
while (!list_empty(head)) {
req = list_entry(head->next, struct usb_request, list);
list_del(&req->list);
gsdio_free_req(ep, req);
}
}
int gsdio_alloc_requests(struct usb_ep *ep, struct list_head *head,
int num, int size,
void (*cb)(struct usb_ep *ep, struct usb_request *))
{
int i;
struct usb_request *req;
pr_debug("%s: ep:%p head:%p num:%d size:%d cb:%p", __func__,
ep, head, num, size, cb);
for (i = 0; i < num; i++) {
req = gsdio_alloc_req(ep, size, GFP_ATOMIC);
if (!req) {
pr_debug("%s: req allocated:%d\n", __func__, i);
return list_empty(head) ? -ENOMEM : 0;
}
req->complete = cb;
list_add(&req->list, head);
}
return 0;
}
void gsdio_start_rx(struct gsdio_port *port)
{
struct list_head *pool;
struct usb_ep *out;
int ret;
if (!port) {
pr_err("%s: port is null\n", __func__);
return;
}
pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num);
spin_lock_irq(&port->port_lock);
if (!port->port_usb) {
pr_debug("%s: usb is disconnected\n", __func__);
goto start_rx_end;
}
if (!port->sdio_open) {
pr_debug("%s: sdio is not open\n", __func__);
goto start_rx_end;
}
pool = &port->read_pool;
out = port->port_usb->out;
while (!list_empty(pool)) {
struct usb_request *req;
req = list_entry(pool->next, struct usb_request, list);
list_del(&req->list);
req->length = SDIO_RX_BUF_SIZE;
port->rp_len--;
spin_unlock_irq(&port->port_lock);
ret = usb_ep_queue(out, req, GFP_ATOMIC);
spin_lock_irq(&port->port_lock);
if (ret) {
pr_err("%s: usb ep out queue failed"
"port:%p, port#%d\n",
__func__, port, port->port_num);
list_add_tail(&req->list, pool);
port->rp_len++;
break;
}
/* usb could have disconnected while we released spin lock */
if (!port->port_usb) {
pr_debug("%s: usb is disconnected\n", __func__);
goto start_rx_end;
}
}
start_rx_end:
spin_unlock_irq(&port->port_lock);
}
int gsdio_write(struct gsdio_port *port, struct usb_request *req)
{
unsigned avail;
char *packet;
unsigned size = req->actual;
unsigned n;
int ret = 0;
if (!port) {
pr_err("%s: port is null\n", __func__);
return -ENODEV;
}
if (!req) {
pr_err("%s: usb request is null port#%d\n",
__func__, port->port_num);
return -ENODEV;
}
pr_debug("%s: port:%p port#%d req:%p actual:%d n_read:%d\n",
__func__, port, port->port_num, req,
req->actual, port->n_read);
if (!port->sdio_open) {
pr_debug("%s: SDIO IO is not supported\n", __func__);
return -ENODEV;
}
avail = sdio_write_avail(port->sport_info->ch);
pr_debug("%s: sdio_write_avail:%d", __func__, avail);
if (!avail)
return -EBUSY;
if (!req->actual) {
pr_debug("%s: req->actual is already zero,update bytes read\n",
__func__);
port->n_read = 0;
return -ENODEV;
}
packet = req->buf;
n = port->n_read;
if (n) {
packet += n;
size -= n;
}
if (size > avail)
size = avail;
spin_unlock_irq(&port->port_lock);
ret = sdio_write(port->sport_info->ch, packet, size);
spin_lock_irq(&port->port_lock);
if (ret) {
pr_err("%s: port#%d sdio write failed err:%d",
__func__, port->port_num, ret);
/* try again later */
return ret;
}
port->nbytes_tomodem += size;
if (size + n == req->actual)
port->n_read = 0;
else
port->n_read += size;
return ret;
}
void gsdio_rx_push(struct work_struct *w)
{
struct gsdio_port *port = container_of(w, struct gsdio_port, push);
struct list_head *q = &port->read_queue;
struct usb_ep *out;
int ret;
pr_debug("%s: port:%p port#%d read_queue:%p", __func__,
port, port->port_num, q);
spin_lock_irq(&port->port_lock);
if (!port->port_usb) {
pr_debug("%s: usb cable is disconencted\n", __func__);
spin_unlock_irq(&port->port_lock);
return;
}
out = port->port_usb->out;
while (!list_empty(q)) {
struct usb_request *req;
req = list_first_entry(q, struct usb_request, list);
switch (req->status) {
case -ESHUTDOWN:
pr_debug("%s: req status shutdown portno#%d port:%p",
__func__, port->port_num, port);
goto rx_push_end;
default:
pr_warning("%s: port:%p port#%d"
" Unexpected Rx Status:%d\n", __func__,
port, port->port_num, req->status);
/* FALL THROUGH */
case 0:
/* normal completion */
break;
}
if (!port->sdio_open) {
pr_err("%s: sio channel is not open\n", __func__);
list_move(&req->list, &port->read_pool);
port->rp_len++;
port->rq_len--;
goto rx_push_end;
}
list_del(&req->list);
port->rq_len--;
ret = gsdio_write(port, req);
/* as gsdio_write drops spin_lock while writing data
* to sdio usb cable may have been disconnected
*/
if (!port->port_usb) {
port->n_read = 0;
gsdio_free_req(out, req);
spin_unlock_irq(&port->port_lock);
return;
}
if (ret || port->n_read) {
list_add(&req->list, &port->read_queue);
port->rq_len++;
goto rx_push_end;
}
list_add(&req->list, &port->read_pool);
port->rp_len++;
}
if (port->sdio_open && !list_empty(q)) {
if (sdio_write_avail(port->sport_info->ch))
queue_work(gsdio_wq, &port->push);
}
rx_push_end:
spin_unlock_irq(&port->port_lock);
/* start queuing out requests again to host */
gsdio_start_rx(port);
}
void gsdio_read_complete(struct usb_ep *ep, struct usb_request *req)
{
struct gsdio_port *port = ep->driver_data;
unsigned long flags;
pr_debug("%s: ep:%p port:%p\n", __func__, ep, port);
if (!port) {
pr_err("%s: port is null\n", __func__);
return;
}
spin_lock_irqsave(&port->port_lock, flags);
list_add_tail(&req->list, &port->read_queue);
port->rq_len++;
queue_work(gsdio_wq, &port->push);
spin_unlock_irqrestore(&port->port_lock, flags);
return;
}
void gsdio_write_complete(struct usb_ep *ep, struct usb_request *req)
{
struct gsdio_port *port = ep->driver_data;
unsigned long flags;
pr_debug("%s: ep:%p port:%p\n", __func__, ep, port);
if (!port) {
pr_err("%s: port is null\n", __func__);
return;
}
spin_lock_irqsave(&port->port_lock, flags);
list_add(&req->list, &port->write_pool);
port->wp_len++;
switch (req->status) {
default:
pr_warning("%s: port:%p port#%d unexpected %s status %d\n",
__func__, port, port->port_num,
ep->name, req->status);
/* FALL THROUGH */
case 0:
queue_work(gsdio_wq, &port->pull);
break;
case -ESHUTDOWN:
/* disconnect */
pr_debug("%s: %s shutdown\n", __func__, ep->name);
break;
}
spin_unlock_irqrestore(&port->port_lock, flags);
return;
}
void gsdio_read_pending(struct gsdio_port *port)
{
struct sdio_channel *ch;
char buf[1024];
int avail;
if (!port) {
pr_err("%s: port is null\n", __func__);
return;
}
ch = port->sport_info->ch;
if (!ch)
return;
while ((avail = sdio_read_avail(ch))) {
if (avail > 1024)
avail = 1024;
sdio_read(ch, buf, avail);
pr_debug("%s: flushed out %d bytes\n", __func__, avail);
}
}
void gsdio_tx_pull(struct work_struct *w)
{
struct gsdio_port *port = container_of(w, struct gsdio_port, pull);
struct list_head *pool = &port->write_pool;
pr_debug("%s: port:%p port#%d pool:%p\n", __func__,
port, port->port_num, pool);
if (!port->port_usb) {
pr_err("%s: usb disconnected\n", __func__);
/* take out all the pending data from sdio */
gsdio_read_pending(port);
return;
}
spin_lock_irq(&port->port_lock);
while (!list_empty(pool)) {
int avail;
struct usb_ep *in = port->port_usb->in;
struct sdio_channel *ch = port->sport_info->ch;
struct usb_request *req;
unsigned len = SDIO_TX_BUF_SIZE;
int ret;
req = list_entry(pool->next, struct usb_request, list);
if (!port->sdio_open) {
pr_debug("%s: SDIO channel is not open\n", __func__);
goto tx_pull_end;
}
avail = sdio_read_avail(ch);
if (!avail) {
/* REVISIT: for ZLP */
pr_debug("%s: read_avail:%d port:%p port#%d\n",
__func__, avail, port, port->port_num);
goto tx_pull_end;
}
if (avail > len)
avail = len;
list_del(&req->list);
port->wp_len--;
spin_unlock_irq(&port->port_lock);
ret = sdio_read(ch, req->buf, avail);
spin_lock_irq(&port->port_lock);
if (ret) {
pr_err("%s: port:%p port#%d sdio read failed err:%d",
__func__, port, port->port_num, ret);
/* check if usb is still active */
if (!port->port_usb) {
gsdio_free_req(in, req);
} else {
list_add(&req->list, pool);
port->wp_len++;
}
goto tx_pull_end;
}
req->length = avail;
spin_unlock_irq(&port->port_lock);
ret = usb_ep_queue(in, req, GFP_KERNEL);
spin_lock_irq(&port->port_lock);
if (ret) {
pr_err("%s: usb ep out queue failed"
"port:%p, port#%d err:%d\n",
__func__, port, port->port_num, ret);
/* could be usb disconnected */
if (!port->port_usb) {
gsdio_free_req(in, req);
} else {
list_add(&req->list, pool);
port->wp_len++;
}
goto tx_pull_end;
}
port->nbytes_tolaptop += avail;
}
tx_pull_end:
spin_unlock_irq(&port->port_lock);
}
int gsdio_start_io(struct gsdio_port *port)
{
int ret;
unsigned long flags;
pr_debug("%s:\n", __func__);
spin_lock_irqsave(&port->port_lock, flags);
if (!port->port_usb) {
spin_unlock_irqrestore(&port->port_lock, flags);
return -ENODEV;
}
/* start usb out queue */
ret = gsdio_alloc_requests(port->port_usb->out,
&port->read_pool,
SDIO_RX_QUEUE_SIZE, SDIO_RX_BUF_SIZE,
gsdio_read_complete);
if (ret) {
spin_unlock_irqrestore(&port->port_lock, flags);
pr_err("%s: unable to allocate out reqs\n", __func__);
return ret;
}
port->rp_len = SDIO_RX_QUEUE_SIZE;
ret = gsdio_alloc_requests(port->port_usb->in,
&port->write_pool,
SDIO_TX_QUEUE_SIZE, SDIO_TX_BUF_SIZE,
gsdio_write_complete);
if (ret) {
gsdio_free_requests(port->port_usb->out, &port->read_pool);
port->rp_len = 0;
spin_unlock_irqrestore(&port->port_lock, flags);
pr_err("%s: unable to allocate in reqs\n", __func__);
return ret;
}
port->wp_len = SDIO_TX_QUEUE_SIZE;
spin_unlock_irqrestore(&port->port_lock, flags);
gsdio_start_rx(port);
queue_work(gsdio_wq, &port->pull);
return 0;
}
void gsdio_port_free(unsigned portno)
{
struct gsdio_port *port = sdio_ports[portno].port;
struct platform_driver *pdriver = &sdio_ports[portno].gsdio_ch;
if (!port) {
pr_err("%s: invalid portno#%d\n", __func__, portno);
return;
}
platform_driver_unregister(pdriver);
kfree(port);
}
void gsdio_ctrl_wq(struct work_struct *w)
{
struct gsdio_port *port;
port = container_of(w, struct gsdio_port, notify_modem);
if (!port) {
pr_err("%s: port is null\n", __func__);
return;
}
if (!port->sdio_open || port->ctrl_ch_err)
return;
sdio_cmux_tiocmset(port->sport_info->ctrl_ch_id,
port->cbits_to_modem, ~(port->cbits_to_modem));
}
void gsdio_ctrl_notify_modem(void *gptr, u8 portno, int ctrl_bits)
{
struct gsdio_port *port;
int temp;
struct gserial *gser = gptr;
if (portno >= n_sdio_ports) {
pr_err("%s: invalid portno#%d\n", __func__, portno);
return;
}
if (!gser) {
pr_err("%s: gser is null\n", __func__);
return;
}
port = sdio_ports[portno].port;
temp = ctrl_bits & SDIO_ACM_CTRL_DTR ? TIOCM_DTR : 0;
if (port->cbits_to_modem == temp)
return;
port->cbits_to_modem = temp;
/* TIOCM_DTR - 0x002 - bit(1) */
pr_debug("%s: port:%p port#%d ctrl_bits:%08x\n", __func__,
port, port->port_num, ctrl_bits);
if (!port->sdio_open) {
pr_err("%s: port:%p port#%d sdio not connected\n",
__func__, port, port->port_num);
return;
}
/* whenever DTR is high let laptop know that modem status */
if (port->cbits_to_modem && gser->send_modem_ctrl_bits)
gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop);
queue_work(gsdio_wq, &port->notify_modem);
}
void gsdio_ctrl_modem_status(int ctrl_bits, void *_dev)
{
struct gsdio_port *port = _dev;
/* TIOCM_CD - 0x040 - bit(6)
* TIOCM_RI - 0x080 - bit(7)
* TIOCM_DSR- 0x100 - bit(8)
*/
pr_debug("%s: port:%p port#%d event:%08x\n", __func__,
port, port->port_num, ctrl_bits);
port->cbits_to_laptop = 0;
ctrl_bits &= TIOCM_RI | TIOCM_CD | TIOCM_DSR;
if (ctrl_bits & TIOCM_RI)
port->cbits_to_laptop |= SDIO_ACM_CTRL_RI;
if (ctrl_bits & TIOCM_CD)
port->cbits_to_laptop |= SDIO_ACM_CTRL_DCD;
if (ctrl_bits & TIOCM_DSR)
port->cbits_to_laptop |= SDIO_ACM_CTRL_DSR;
if (port->port_usb && port->port_usb->send_modem_ctrl_bits)
port->port_usb->send_modem_ctrl_bits(port->port_usb,
port->cbits_to_laptop);
}
void gsdio_ch_notify(void *_dev, unsigned event)
{
struct gsdio_port *port = _dev;
pr_debug("%s: port:%p port#%d event:%s\n", __func__,
port, port->port_num,
event == 1 ? "READ AVAIL" : "WRITE_AVAIL");
if (event == SDIO_EVENT_DATA_WRITE_AVAIL)
queue_work(gsdio_wq, &port->push);
if (event == SDIO_EVENT_DATA_READ_AVAIL)
queue_work(gsdio_wq, &port->pull);
}
static void gsdio_open_work(struct work_struct *w)
{
struct gsdio_port *port =
container_of(w, struct gsdio_port, sdio_open_work.work);
struct sdio_port_info *pi = port->sport_info;
struct gserial *gser;
int ret;
int ctrl_bits;
int startio;
ret = sdio_open(pi->data_ch_name, &pi->ch, port, gsdio_ch_notify);
if (ret) {
pr_err("%s: port:%p port#%d unable to open sdio ch:%s\n",
__func__, port, port->port_num,
pi->data_ch_name);
return;
}
port->ctrl_ch_err = 0;
ret = sdio_cmux_open(pi->ctrl_ch_id, 0, 0,
gsdio_ctrl_modem_status, port);
if (ret) {
pr_err("%s: port:%p port#%d unable to open ctrl ch:%d\n",
__func__, port, port->port_num, pi->ctrl_ch_id);
port->ctrl_ch_err = 1;
}
/* check for latest status update from modem */
if (!port->ctrl_ch_err) {
ctrl_bits = sdio_cmux_tiocmget(pi->ctrl_ch_id);
gsdio_ctrl_modem_status(ctrl_bits, port);
}
pr_debug("%s: SDIO data:%s ctrl:%d are open\n", __func__,
pi->data_ch_name,
pi->ctrl_ch_id);
port->sdio_open = 1;
/* start tx if usb is open already */
spin_lock_irq(&port->port_lock);
startio = port->port_usb ? 1 : 0;
gser = port->port_usb;
spin_unlock_irq(&port->port_lock);
if (startio) {
pr_debug("%s: USB is already open, start io\n", __func__);
gsdio_start_io(port);
if (gser->send_modem_ctrl_bits)
gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop);
}
}
#define SDIO_CH_NAME_MAX_LEN 9
#define SDIO_OPEN_DELAY msecs_to_jiffies(10000)
static int gsdio_ch_remove(struct platform_device *dev)
{
struct gsdio_port *port;
struct sdio_port_info *pi;
int i;
unsigned long flags;
pr_debug("%s: name:%s\n", __func__, dev->name);
for (i = 0; i < n_sdio_ports; i++) {
port = sdio_ports[i].port;
pi = port->sport_info;
if (!strncmp(pi->data_ch_name, dev->name,
SDIO_CH_NAME_MAX_LEN)) {
struct gserial *gser = port->port_usb;
port->sdio_open = 0;
port->sdio_probe = 0;
port->ctrl_ch_err = 1;
/* check if usb cable is connected */
if (!gser)
continue;
/* indicated call status to usb host */
gsdio_ctrl_modem_status(0, port);
usb_ep_fifo_flush(gser->in);
usb_ep_fifo_flush(gser->out);
cancel_work_sync(&port->push);
cancel_work_sync(&port->pull);
spin_lock_irqsave(&port->port_lock, flags);
gsdio_free_requests(gser->out, &port->read_pool);
gsdio_free_requests(gser->out, &port->read_queue);
gsdio_free_requests(gser->in, &port->write_pool);
port->rp_len = 0;
port->rq_len = 0;
port->wp_len = 0;
port->n_read = 0;
spin_unlock_irqrestore(&port->port_lock, flags);
}
}
return 0;
}
static int gsdio_ch_probe(struct platform_device *dev)
{
struct gsdio_port *port;
struct sdio_port_info *pi;
int i;
pr_debug("%s: name:%s\n", __func__, dev->name);
for (i = 0; i < n_sdio_ports; i++) {
port = sdio_ports[i].port;
pi = port->sport_info;
pr_debug("%s: sdio_ch_name:%s dev_name:%s\n", __func__,
pi->data_ch_name, dev->name);
/* unfortunately cmux channle might not be ready even if
* sdio channel is ready. as we dont have good notification
* mechanism schedule a delayed work
*/
if (!strncmp(pi->data_ch_name, dev->name,
SDIO_CH_NAME_MAX_LEN)) {
port->sdio_probe = 1;
queue_delayed_work(gsdio_wq,
&port->sdio_open_work, SDIO_OPEN_DELAY);
return 0;
}
}
pr_info("%s: name:%s is not found\n", __func__, dev->name);
return -ENODEV;
}
int gsdio_port_alloc(unsigned portno,
struct usb_cdc_line_coding *coding,
struct sdio_port_info *pi)
{
struct gsdio_port *port;
struct platform_driver *pdriver;
port = kzalloc(sizeof(struct gsdio_port), GFP_KERNEL);
if (!port) {
pr_err("%s: port allocation failed\n", __func__);
return -ENOMEM;
}
port->port_num = portno;
spin_lock_init(&port->port_lock);
port->line_coding = *coding;
/* READ: read from usb and write into sdio */
INIT_LIST_HEAD(&port->read_pool);
INIT_LIST_HEAD(&port->read_queue);
INIT_WORK(&port->push, gsdio_rx_push);
INIT_LIST_HEAD(&port->write_pool);
INIT_WORK(&port->pull, gsdio_tx_pull);
INIT_WORK(&port->notify_modem, gsdio_ctrl_wq);
INIT_DELAYED_WORK(&port->sdio_open_work, gsdio_open_work);
sdio_ports[portno].port = port;
port->sport_info = pi;
pdriver = &sdio_ports[portno].gsdio_ch;
pdriver->probe = gsdio_ch_probe;
pdriver->remove = gsdio_ch_remove;
pdriver->driver.name = pi->data_ch_name;
pdriver->driver.owner = THIS_MODULE;
pr_debug("%s: port:%p port#%d sdio_name: %s\n", __func__,
port, port->port_num, pi->data_ch_name);
platform_driver_register(pdriver);
pr_debug("%s: port:%p port#%d\n", __func__, port, port->port_num);
return 0;
}
int gsdio_connect(struct gserial *gser, u8 portno)
{
struct gsdio_port *port;
int ret = 0;
unsigned long flags;
if (portno >= n_sdio_ports) {
pr_err("%s: invalid portno#%d\n", __func__, portno);
return -EINVAL;
}
if (!gser) {
pr_err("%s: gser is null\n", __func__);
return -EINVAL;
}
port = sdio_ports[portno].port;
spin_lock_irqsave(&port->port_lock, flags);
port->port_usb = gser;
gser->notify_modem = gsdio_ctrl_notify_modem;
spin_unlock_irqrestore(&port->port_lock, flags);
ret = usb_ep_enable(gser->in);
if (ret) {
pr_err("%s: failed to enable in ep w/ err:%d\n",
__func__, ret);
port->port_usb = 0;
return ret;
}
gser->in->driver_data = port;
ret = usb_ep_enable(gser->out);
if (ret) {
pr_err("%s: failed to enable in ep w/ err:%d\n",
__func__, ret);
usb_ep_disable(gser->in);
port->port_usb = 0;
gser->in->driver_data = 0;
return ret;
}
gser->out->driver_data = port;
if (port->sdio_open) {
pr_debug("%s: sdio is already open, start io\n", __func__);
gsdio_start_io(port);
if (gser->send_modem_ctrl_bits)
gser->send_modem_ctrl_bits(gser, port->cbits_to_laptop);
}
return 0;
}
void gsdio_disconnect(struct gserial *gser, u8 portno)
{
unsigned long flags;
struct gsdio_port *port;
if (portno >= n_sdio_ports) {
pr_err("%s: invalid portno#%d\n", __func__, portno);
return;
}
if (!gser) {
pr_err("%s: gser is null\n", __func__);
return;
}
port = sdio_ports[portno].port;
/* send dtr zero to modem to notify disconnect */
port->cbits_to_modem = 0;
queue_work(gsdio_wq, &port->notify_modem);
spin_lock_irqsave(&port->port_lock, flags);
port->port_usb = 0;
port->nbytes_tomodem = 0;
port->nbytes_tolaptop = 0;
spin_unlock_irqrestore(&port->port_lock, flags);
/* disable endpoints, aborting down any active I/O */
usb_ep_disable(gser->out);
usb_ep_disable(gser->in);
spin_lock_irqsave(&port->port_lock, flags);
gsdio_free_requests(gser->out, &port->read_pool);
gsdio_free_requests(gser->out, &port->read_queue);
gsdio_free_requests(gser->in, &port->write_pool);
port->rp_len = 0;
port->rq_len = 0;
port->wp_len = 0;
port->n_read = 0;
spin_unlock_irqrestore(&port->port_lock, flags);
}
#if defined(CONFIG_DEBUG_FS)
static char debug_buffer[PAGE_SIZE];
static ssize_t debug_sdio_read_stats(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct gsdio_port *port;
char *buf;
unsigned long flags;
int i = 0;
int temp = 0;
int ret;
buf = kzalloc(sizeof(char) * 1024, GFP_KERNEL);
if (!buf)
return -ENOMEM;
while (i < n_sdio_ports) {
port = sdio_ports[i].port;
spin_lock_irqsave(&port->port_lock, flags);
temp += scnprintf(buf + temp, PAGE_SIZE - temp,
"###PORT:%d port:%p###\n"
"nbytes_tolaptop: %lu\n"
"nbytes_tomodem: %lu\n"
"cbits_to_modem: %u\n"
"cbits_to_laptop: %u\n"
"read_pool_len: %lu\n"
"read_queue_len: %lu\n"
"write_pool_len: %lu\n"
"n_read: %u\n"
"sdio_open: %d\n"
"sdio_probe: %d\n",
i, port,
port->nbytes_tolaptop, port->nbytes_tomodem,
port->cbits_to_modem, port->cbits_to_laptop,
port->rp_len, port->rq_len, port->wp_len,
port->n_read,
port->sdio_open, port->sdio_probe);
spin_unlock_irqrestore(&port->port_lock, flags);
i++;
}
ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
kfree(buf);
return ret;
}
static ssize_t debug_sdio_reset_stats(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct gsdio_port *port;
unsigned long flags;
int i = 0;
while (i < n_sdio_ports) {
port = sdio_ports[i].port;
spin_lock_irqsave(&port->port_lock, flags);
port->nbytes_tolaptop = 0;
port->nbytes_tomodem = 0;
spin_unlock_irqrestore(&port->port_lock, flags);
i++;
}
return count;
}
static int debug_sdio_open(struct inode *inode, struct file *file)
{
return 0;
}
static const struct file_operations debug_gsdio_ops = {
.open = debug_sdio_open,
.read = debug_sdio_read_stats,
.write = debug_sdio_reset_stats,
};
static void gsdio_debugfs_init(void)
{
struct dentry *dent;
dent = debugfs_create_dir("usb_gsdio", 0);
if (IS_ERR(dent))
return;
debugfs_create_file("status", 0444, dent, 0, &debug_gsdio_ops);
}
#else
static void gsdio_debugfs_init(void)
{
return;
}
#endif
/* connect, disconnect, alloc_requests, free_requests */
int gsdio_setup(struct usb_gadget *g, unsigned count)
{
struct usb_cdc_line_coding coding;
int i;
int ret = 0;
pr_debug("%s: gadget:(%p) count:%d\n", __func__, g, count);
if (count == 0 || count > SDIO_N_PORTS) {
pr_err("%s: invalid number of ports count:%d max_ports:%d\n",
__func__, count, SDIO_N_PORTS);
return -EINVAL;
}
coding.dwDTERate = cpu_to_le32(9600);
coding.bCharFormat = 8;
coding.bParityType = USB_CDC_NO_PARITY;
coding.bDataBits = USB_CDC_1_STOP_BITS;
gsdio_wq = create_singlethread_workqueue("k_gserial");
if (!gsdio_wq) {
pr_err("%s: unable to create workqueue gsdio_wq\n",
__func__);
return -ENOMEM;
}
for (i = 0; i < count; i++) {
mutex_init(&sdio_ports[i].lock);
ret = gsdio_port_alloc(i, &coding, sport_info + i);
n_sdio_ports++;
if (ret) {
n_sdio_ports--;
pr_err("%s: sdio logical port allocation failed\n",
__func__);
goto free_sdio_ports;
}
}
gsdio_debugfs_init();
return 0;
free_sdio_ports:
for (i = 0; i < n_sdio_ports; i++)
gsdio_port_free(i);
destroy_workqueue(gsdio_wq);
return ret;
}
/* TODO: Add gserial_cleanup */