Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/usb/gadget/u_sdio.c b/drivers/usb/gadget/u_sdio.c
new file mode 100644
index 0000000..09d898f
--- /dev/null
+++ b/drivers/usb/gadget/u_sdio.c
@@ -0,0 +1,1097 @@
+/*
+ * 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 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;
+ }
+
+ 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 = req->buf;
+ 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(struct gserial *gser, u8 portno, int ctrl_bits)
+{
+ struct gsdio_port *port;
+ int temp;
+
+ 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;
+ }
+
+ 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_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)) {
+ 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->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, gser->in_desc);
+ 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, gser->out_desc);
+ 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 = debug_buffer;
+ unsigned long flags;
+ int i = 0;
+ int temp = 0;
+
+ 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",
+ 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);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ i++;
+ }
+
+ return simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+}
+
+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;
+ struct sdio_port_info *port_info;
+
+ 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);
+ if (ret) {
+ pr_err("%s: sdio logical port allocation failed\n",
+ __func__);
+ goto free_sdio_ports;
+ }
+ n_sdio_ports++;
+ port_info++;
+
+#ifdef DEBUG
+ /* REVISIT: create one file per port
+ * or do not create any file
+ */
+ if (i == 0) {
+ ret = device_create_file(&g->dev, &dev_attr_input);
+ if (ret)
+ pr_err("%s: unable to create device file\n",
+ __func__);
+ }
+#endif
+
+ }
+
+ 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 */