usb: gadget: Add network gadget driver for dun and rmnet
This gadget driver works as independent data and control pass
through between modem device connected over USB or HSIC
and host/laptop.This driver communicates with modem device
using network bridge host driver.
Change-Id: Iaffdeaa6b5dbd601b73cba721a8f062d69912908
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
Signed-off-by: Jack Pham <jackp@codeaurora.org>
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
index 5b14c60..1e3beac 100644
--- a/drivers/usb/gadget/android.c
+++ b/drivers/usb/gadget/android.c
@@ -57,6 +57,8 @@
#include "u_smd.c"
#include "u_bam.c"
#include "u_rmnet_ctrl_smd.c"
+#include "u_ctrl_hsic.c"
+#include "u_data_hsic.c"
#include "f_serial.c"
//#include "f_acm.c"
#include "f_adb.c"
diff --git a/drivers/usb/gadget/f_rmnet.c b/drivers/usb/gadget/f_rmnet.c
index 2c6a31c..32791d9 100644
--- a/drivers/usb/gadget/f_rmnet.c
+++ b/drivers/usb/gadget/f_rmnet.c
@@ -64,7 +64,9 @@
#define NR_RMNET_PORTS 1
static unsigned int nr_rmnet_ports;
static unsigned int no_ctrl_smd_ports;
+static unsigned int no_ctrl_hsic_ports;
static unsigned int no_data_bam_ports;
+static unsigned int no_data_hsic_ports;
static struct rmnet_ports {
enum transport_type data_xport;
enum transport_type ctrl_xport;
@@ -235,11 +237,14 @@
static int rmnet_gport_setup(void)
{
- int ret;
+ int ret;
+ int port_idx;
+ int i;
- pr_debug("%s: bam ports: %u smd ports: %u nr_rmnet_ports: %u\n",
- __func__, no_data_bam_ports, no_ctrl_smd_ports,
- nr_rmnet_ports);
+ pr_debug("%s: bam ports: %u data hsic ports: %u smd ports: %u"
+ " ctrl hsic ports: %u nr_rmnet_ports: %u\n",
+ __func__, no_data_bam_ports, no_data_hsic_ports,
+ no_ctrl_smd_ports, no_ctrl_hsic_ports, nr_rmnet_ports);
if (no_data_bam_ports) {
ret = gbam_setup(no_data_bam_ports);
@@ -253,6 +258,34 @@
return ret;
}
+ if (no_data_hsic_ports) {
+ port_idx = ghsic_data_setup(no_data_hsic_ports,
+ USB_GADGET_RMNET);
+ if (port_idx < 0)
+ return port_idx;
+ for (i = 0; i < nr_rmnet_ports; i++) {
+ if (rmnet_ports[i].data_xport ==
+ USB_GADGET_XPORT_HSIC) {
+ rmnet_ports[i].data_xport_num = port_idx;
+ port_idx++;
+ }
+ }
+ }
+
+ if (no_ctrl_hsic_ports) {
+ port_idx = ghsic_ctrl_setup(no_ctrl_hsic_ports,
+ USB_GADGET_RMNET);
+ if (port_idx < 0)
+ return port_idx;
+ for (i = 0; i < nr_rmnet_ports; i++) {
+ if (rmnet_ports[i].ctrl_xport ==
+ USB_GADGET_XPORT_HSIC) {
+ rmnet_ports[i].ctrl_xport_num = port_idx;
+ port_idx++;
+ }
+ }
+ }
+
return 0;
}
@@ -277,6 +310,14 @@
return ret;
}
break;
+ case USB_GADGET_XPORT_HSIC:
+ ret = ghsic_ctrl_connect(&dev->port, port_num);
+ if (ret) {
+ pr_err("%s: ghsic_ctrl_connect failed: err:%d\n",
+ __func__, ret);
+ return ret;
+ }
+ break;
case USB_GADGET_XPORT_NONE:
break;
default:
@@ -296,6 +337,15 @@
return ret;
}
break;
+ case USB_GADGET_XPORT_HSIC:
+ ret = ghsic_data_connect(&dev->port, port_num);
+ if (ret) {
+ pr_err("%s: ghsic_data_connect failed: err:%d\n",
+ __func__, ret);
+ ghsic_ctrl_disconnect(&dev->port, port_num);
+ return ret;
+ }
+ break;
case USB_GADGET_XPORT_NONE:
break;
default:
@@ -322,6 +372,9 @@
case USB_GADGET_XPORT_SMD:
gsmd_ctrl_disconnect(&dev->port, port_num);
break;
+ case USB_GADGET_XPORT_HSIC:
+ ghsic_ctrl_disconnect(&dev->port, port_num);
+ break;
case USB_GADGET_XPORT_NONE:
break;
default:
@@ -335,6 +388,9 @@
case USB_GADGET_XPORT_BAM:
gbam_disconnect(&dev->port, port_num);
break;
+ case USB_GADGET_XPORT_HSIC:
+ ghsic_data_disconnect(&dev->port, port_num);
+ break;
case USB_GADGET_XPORT_NONE:
break;
default:
@@ -898,6 +954,8 @@
nr_rmnet_ports = 0;
no_ctrl_smd_ports = 0;
no_data_bam_ports = 0;
+ no_ctrl_hsic_ports = 0;
+ no_data_hsic_ports = 0;
}
static int frmnet_init_port(const char *ctrl_name, const char *data_name)
@@ -937,6 +995,10 @@
rmnet_port->ctrl_xport_num = no_ctrl_smd_ports;
no_ctrl_smd_ports++;
break;
+ case USB_GADGET_XPORT_HSIC:
+ rmnet_port->ctrl_xport_num = no_ctrl_hsic_ports;
+ no_ctrl_hsic_ports++;
+ break;
case USB_GADGET_XPORT_NONE:
break;
default:
@@ -951,6 +1013,10 @@
rmnet_port->data_xport_num = no_data_bam_ports;
no_data_bam_ports++;
break;
+ case USB_GADGET_XPORT_HSIC:
+ rmnet_port->data_xport_num = no_data_hsic_ports;
+ no_data_hsic_ports++;
+ break;
case USB_GADGET_XPORT_NONE:
break;
default:
@@ -970,6 +1036,8 @@
nr_rmnet_ports = 0;
no_ctrl_smd_ports = 0;
no_data_bam_ports = 0;
+ no_ctrl_hsic_ports = 0;
+ no_data_hsic_ports = 0;
return ret;
}
diff --git a/drivers/usb/gadget/f_serial.c b/drivers/usb/gadget/f_serial.c
index 7cd0288..de8c8ed 100644
--- a/drivers/usb/gadget/f_serial.c
+++ b/drivers/usb/gadget/f_serial.c
@@ -76,6 +76,7 @@
static unsigned int no_tty_ports;
static unsigned int no_sdio_ports;
static unsigned int no_smd_ports;
+static unsigned int no_hsic_sports;
static unsigned int nr_ports;
static struct port_info {
@@ -254,9 +255,13 @@
static int gport_setup(struct usb_configuration *c)
{
int ret = 0;
+ int port_idx;
+ int i;
- pr_debug("%s: no_tty_ports:%u no_sdio_ports: %u nr_ports:%u\n",
- __func__, no_tty_ports, no_sdio_ports, nr_ports);
+ pr_debug("%s: no_tty_ports: %u no_sdio_ports: %u"
+ " no_smd_ports: %u no_hsic_sports: %u nr_ports: %u\n",
+ __func__, no_tty_ports, no_sdio_ports, no_smd_ports,
+ no_hsic_sports, nr_ports);
if (no_tty_ports)
ret = gserial_setup(c->cdev->gadget, no_tty_ports);
@@ -264,15 +269,34 @@
ret = gsdio_setup(c->cdev->gadget, no_sdio_ports);
if (no_smd_ports)
ret = gsmd_setup(c->cdev->gadget, no_smd_ports);
+ if (no_hsic_sports) {
+ port_idx = ghsic_data_setup(no_hsic_sports, USB_GADGET_SERIAL);
+ if (port_idx < 0)
+ return port_idx;
+ for (i = 0; i < nr_ports; i++) {
+ if (gserial_ports[i].transport ==
+ USB_GADGET_XPORT_HSIC) {
+ gserial_ports[i].client_port_num = port_idx;
+ port_idx++;
+ }
+ }
+
+ /*clinet port num is same for data setup and ctrl setup*/
+ ret = ghsic_ctrl_setup(no_hsic_sports, USB_GADGET_SERIAL);
+ if (ret < 0)
+ return ret;
+ return 0;
+ }
return ret;
}
static int gport_connect(struct f_gser *gser)
{
- unsigned port_num;
+ unsigned port_num;
+ int ret;
- pr_debug("%s: transport:%s f_gser:%p gserial:%p port_num:%d\n",
+ pr_debug("%s: transport: %s f_gser: %p gserial: %p port_num: %d\n",
__func__, xport_to_str(gser->transport),
gser, &gser->port, gser->port_num);
@@ -288,6 +312,21 @@
case USB_GADGET_XPORT_SMD:
gsmd_connect(&gser->port, port_num);
break;
+ case USB_GADGET_XPORT_HSIC:
+ ret = ghsic_ctrl_connect(&gser->port, port_num);
+ if (ret) {
+ pr_err("%s: ghsic_ctrl_connect failed: err:%d\n",
+ __func__, ret);
+ return ret;
+ }
+ ret = ghsic_data_connect(&gser->port, port_num);
+ if (ret) {
+ pr_err("%s: ghsic_data_connect failed: err:%d\n",
+ __func__, ret);
+ ghsic_ctrl_disconnect(&gser->port, port_num);
+ return ret;
+ }
+ break;
default:
pr_err("%s: Un-supported transport: %s\n", __func__,
xport_to_str(gser->transport));
@@ -301,7 +340,7 @@
{
unsigned port_num;
- pr_debug("%s: transport:%s f_gser:%p gserial:%p port_num:%d\n",
+ pr_debug("%s: transport: %s f_gser: %p gserial: %p port_num: %d\n",
__func__, xport_to_str(gser->transport),
gser, &gser->port, gser->port_num);
@@ -317,6 +356,10 @@
case USB_GADGET_XPORT_SMD:
gsmd_disconnect(&gser->port, port_num);
break;
+ case USB_GADGET_XPORT_HSIC:
+ ghsic_ctrl_disconnect(&gser->port, port_num);
+ ghsic_data_disconnect(&gser->port, port_num);
+ break;
default:
pr_err("%s: Un-supported transport:%s\n", __func__,
xport_to_str(gser->transport));
@@ -865,7 +908,7 @@
transport = str_to_xport(name);
pr_debug("%s, port:%d, transport:%s\n", __func__,
- port_num, xport_to_str(transport));
+ port_num, xport_to_str(transport));
gserial_ports[port_num].transport = transport;
gserial_ports[port_num].port_num = port_num;
@@ -883,6 +926,10 @@
gserial_ports[port_num].client_port_num = no_smd_ports;
no_smd_ports++;
break;
+ case USB_GADGET_XPORT_HSIC:
+ /*client port number will be updated in gport_setup*/
+ no_hsic_sports++;
+ break;
default:
pr_err("%s: Un-supported transport transport: %u\n",
__func__, gserial_ports[port_num].transport);
diff --git a/drivers/usb/gadget/u_ctrl_hsic.c b/drivers/usb/gadget/u_ctrl_hsic.c
new file mode 100644
index 0000000..fdfab96
--- /dev/null
+++ b/drivers/usb/gadget/u_ctrl_hsic.c
@@ -0,0 +1,617 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#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 <linux/bitops.h>
+#include <linux/termios.h>
+#include <mach/usb_bridge.h>
+#include <mach/usb_gadget_xport.h>
+
+/* from cdc-acm.h */
+#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */
+#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */
+#define ACM_CTRL_OVERRUN (1 << 6)
+#define ACM_CTRL_PARITY (1 << 5)
+#define ACM_CTRL_FRAMING (1 << 4)
+#define ACM_CTRL_RI (1 << 3)
+#define ACM_CTRL_BRK (1 << 2)
+#define ACM_CTRL_DSR (1 << 1)
+#define ACM_CTRL_DCD (1 << 0)
+
+
+static unsigned int no_ctrl_ports;
+
+static const char *ctrl_bridge_names[] = {
+ "dun_ctrl_hsic0",
+ "rmnet_ctrl_hsic0"
+};
+
+#define CTRL_BRIDGE_NAME_MAX_LEN 20
+#define READ_BUF_LEN 1024
+
+#define CH_OPENED 0
+#define CH_READY 1
+
+struct gctrl_port {
+ /* port */
+ unsigned port_num;
+
+ /* gadget */
+ spinlock_t port_lock;
+ void *port_usb;
+
+ /* work queue*/
+ struct workqueue_struct *wq;
+ struct work_struct connect_w;
+ struct work_struct disconnect_w;
+
+ enum gadget_type gtype;
+
+ /*ctrl pkt response cb*/
+ int (*send_cpkt_response)(void *g, void *buf, size_t len);
+
+ struct bridge brdg;
+
+ /* bridge status */
+ unsigned long bridge_sts;
+
+ /* control bits */
+ unsigned cbits_tomodem;
+ unsigned cbits_tohost;
+
+ /* counters */
+ unsigned long to_modem;
+ unsigned long to_host;
+ unsigned long drp_cpkt_cnt;
+};
+
+static struct {
+ struct gctrl_port *port;
+ struct platform_driver pdrv;
+} gctrl_ports[NUM_PORTS];
+
+static int ghsic_ctrl_receive(void *dev, void *buf, size_t actual)
+{
+ struct gctrl_port *port = dev;
+ int retval = 0;
+
+ pr_debug_ratelimited("%s: read complete bytes read: %d\n",
+ __func__, actual);
+
+ /* send it to USB here */
+ if (port && port->send_cpkt_response) {
+ retval = port->send_cpkt_response(port->port_usb, buf, actual);
+ port->to_host++;
+ }
+
+ return retval;
+}
+
+static int
+ghsic_send_cpkt_tomodem(u8 portno, void *buf, size_t len)
+{
+ void *cbuf;
+ struct gctrl_port *port;
+
+ if (portno >= no_ctrl_ports) {
+ pr_err("%s: Invalid portno#%d\n", __func__, portno);
+ return -ENODEV;
+ }
+
+ port = gctrl_ports[portno].port;
+ if (!port) {
+ pr_err("%s: port is null\n", __func__);
+ return -ENODEV;
+ }
+
+ cbuf = kmalloc(len, GFP_ATOMIC);
+ if (!cbuf)
+ return -ENOMEM;
+
+ memcpy(cbuf, buf, len);
+
+ /* drop cpkt if ch is not open */
+ if (!test_bit(CH_OPENED, &port->bridge_sts)) {
+ port->drp_cpkt_cnt++;
+ kfree(cbuf);
+ return 0;
+ }
+
+ pr_debug("%s: ctrl_pkt:%d bytes\n", __func__, len);
+
+ ctrl_bridge_write(port->brdg.ch_id, cbuf, len);
+
+ port->to_modem++;
+
+ return 0;
+}
+
+static void
+ghsic_send_cbits_tomodem(void *gptr, u8 portno, int cbits)
+{
+ struct gctrl_port *port;
+
+ if (portno >= no_ctrl_ports || !gptr) {
+ pr_err("%s: Invalid portno#%d\n", __func__, portno);
+ return;
+ }
+
+ port = gctrl_ports[portno].port;
+ if (!port) {
+ pr_err("%s: port is null\n", __func__);
+ return;
+ }
+
+ if (cbits == port->cbits_tomodem)
+ return;
+
+ port->cbits_tomodem = cbits;
+
+ if (!test_bit(CH_OPENED, &port->bridge_sts))
+ return;
+
+ pr_debug("%s: ctrl_tomodem:%d\n", __func__, cbits);
+
+ ctrl_bridge_set_cbits(port->brdg.ch_id, cbits);
+}
+
+static void ghsic_ctrl_connect_w(struct work_struct *w)
+{
+ struct gserial *gser = NULL;
+ struct grmnet *gr = NULL;
+ struct gctrl_port *port =
+ container_of(w, struct gctrl_port, connect_w);
+ unsigned long flags;
+ int retval;
+ unsigned cbits;
+
+ if (!port || !test_bit(CH_READY, &port->bridge_sts))
+ return;
+
+ pr_debug("%s: port:%p\n", __func__, port);
+
+ retval = ctrl_bridge_open(&port->brdg);
+ if (retval) {
+ pr_err("%s: ctrl bridge open failed :%d\n", __func__, retval);
+ return;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ ctrl_bridge_close(port->brdg.ch_id);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+ set_bit(CH_OPENED, &port->bridge_sts);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ cbits = ctrl_bridge_get_cbits_tohost(port->brdg.ch_id);
+
+ if (port->gtype == USB_GADGET_SERIAL && (cbits & ACM_CTRL_DCD)) {
+ gser = port->port_usb;
+ if (gser && gser->connect)
+ gser->connect(gser);
+ return;
+ }
+
+ if (port->gtype == USB_GADGET_RMNET) {
+ gr = port->port_usb;
+ if (gr && gr->connect)
+ gr->connect(gr);
+ }
+}
+
+int ghsic_ctrl_connect(void *gptr, int port_num)
+{
+ struct gctrl_port *port;
+ struct gserial *gser;
+ struct grmnet *gr;
+ unsigned long flags;
+
+ pr_debug("%s: port#%d\n", __func__, port_num);
+
+ if (port_num > no_ctrl_ports || !gptr) {
+ pr_err("%s: invalid portno#%d\n", __func__, port_num);
+ return -ENODEV;
+ }
+
+ port = gctrl_ports[port_num].port;
+ if (!port) {
+ pr_err("%s: port is null\n", __func__);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->gtype == USB_GADGET_SERIAL) {
+ gser = gptr;
+ gser->notify_modem = ghsic_send_cbits_tomodem;
+ }
+
+ if (port->gtype == USB_GADGET_RMNET) {
+ gr = gptr;
+ port->send_cpkt_response = gr->send_cpkt_response;
+ gr->send_encap_cmd = ghsic_send_cpkt_tomodem;
+ gr->notify_modem = ghsic_send_cbits_tomodem;
+ }
+
+ port->port_usb = gptr;
+ port->to_host = 0;
+ port->to_modem = 0;
+ port->drp_cpkt_cnt = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ queue_work(port->wq, &port->connect_w);
+
+ return 0;
+}
+
+static void gctrl_disconnect_w(struct work_struct *w)
+{
+ struct gctrl_port *port =
+ container_of(w, struct gctrl_port, disconnect_w);
+
+ if (!test_bit(CH_OPENED, &port->bridge_sts))
+ return;
+
+ /* send the dtr zero */
+ ctrl_bridge_close(port->brdg.ch_id);
+ clear_bit(CH_OPENED, &port->bridge_sts);
+}
+
+void ghsic_ctrl_disconnect(void *gptr, int port_num)
+{
+ struct gctrl_port *port;
+ struct gserial *gser = NULL;
+ struct grmnet *gr = NULL;
+ unsigned long flags;
+
+ pr_debug("%s: port#%d\n", __func__, port_num);
+
+ port = gctrl_ports[port_num].port;
+
+ if (port_num > no_ctrl_ports) {
+ pr_err("%s: invalid portno#%d\n", __func__, port_num);
+ return;
+ }
+
+ if (!gptr || !port) {
+ pr_err("%s: grmnet port is null\n", __func__);
+ return;
+ }
+
+ if (port->gtype == USB_GADGET_SERIAL)
+ gser = gptr;
+ else
+ gr = gptr;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (gr) {
+ gr->send_encap_cmd = 0;
+ gr->notify_modem = 0;
+ }
+
+ if (gser)
+ gser->notify_modem = 0;
+ port->cbits_tomodem = 0;
+ port->port_usb = 0;
+ port->send_cpkt_response = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ queue_work(port->wq, &port->disconnect_w);
+}
+
+static void ghsic_ctrl_status(void *ctxt, unsigned int ctrl_bits)
+{
+ struct gctrl_port *port = ctxt;
+ struct gserial *gser;
+
+ pr_debug("%s - input control lines: dcd%c dsr%c break%c "
+ "ring%c framing%c parity%c overrun%c\n", __func__,
+ ctrl_bits & ACM_CTRL_DCD ? '+' : '-',
+ ctrl_bits & ACM_CTRL_DSR ? '+' : '-',
+ ctrl_bits & ACM_CTRL_BRK ? '+' : '-',
+ ctrl_bits & ACM_CTRL_RI ? '+' : '-',
+ ctrl_bits & ACM_CTRL_FRAMING ? '+' : '-',
+ ctrl_bits & ACM_CTRL_PARITY ? '+' : '-',
+ ctrl_bits & ACM_CTRL_OVERRUN ? '+' : '-');
+
+ port->cbits_tohost = ctrl_bits;
+ gser = port->port_usb;
+ if (gser && gser->send_modem_ctrl_bits)
+ gser->send_modem_ctrl_bits(gser, ctrl_bits);
+}
+
+static int ghsic_ctrl_probe(struct platform_device *pdev)
+{
+ struct gctrl_port *port;
+ unsigned long flags;
+
+ pr_debug("%s: name:%s\n", __func__, pdev->name);
+
+ if (pdev->id >= no_ctrl_ports) {
+ pr_err("%s: invalid port: %d\n", __func__, pdev->id);
+ return -EINVAL;
+ }
+
+ port = gctrl_ports[pdev->id].port;
+ set_bit(CH_READY, &port->bridge_sts);
+
+ /* if usb is online, start read */
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb)
+ queue_work(port->wq, &port->connect_w);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return 0;
+}
+
+static int ghsic_ctrl_remove(struct platform_device *pdev)
+{
+ struct gctrl_port *port;
+ struct gserial *gser = NULL;
+ struct grmnet *gr = NULL;
+ unsigned long flags;
+
+ pr_debug("%s: name:%s\n", __func__, pdev->name);
+
+ if (pdev->id >= no_ctrl_ports) {
+ pr_err("%s: invalid port: %d\n", __func__, pdev->id);
+ return -EINVAL;
+ }
+
+ port = gctrl_ports[pdev->id].port;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ goto not_ready;
+ }
+
+ if (port->gtype == USB_GADGET_SERIAL)
+ gser = port->port_usb;
+ else
+ gr = port->port_usb;
+
+ port->cbits_tohost = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ if (gr && gr->disconnect)
+ gr->disconnect(gr);
+
+ if (gser && gser->disconnect)
+ gser->disconnect(gser);
+
+ ctrl_bridge_close(port->brdg.ch_id);
+
+ clear_bit(CH_OPENED, &port->bridge_sts);
+not_ready:
+ clear_bit(CH_READY, &port->bridge_sts);
+
+ return 0;
+}
+
+static void ghsic_ctrl_port_free(int portno)
+{
+ struct gctrl_port *port = gctrl_ports[portno].port;
+ struct platform_driver *pdrv = &gctrl_ports[portno].pdrv;
+
+ destroy_workqueue(port->wq);
+ kfree(port);
+
+ if (pdrv)
+ platform_driver_unregister(pdrv);
+}
+
+static int gctrl_port_alloc(int portno, enum gadget_type gtype)
+{
+ struct gctrl_port *port;
+ struct platform_driver *pdrv;
+
+ port = kzalloc(sizeof(struct gctrl_port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->wq = create_singlethread_workqueue(ctrl_bridge_names[portno]);
+ if (!port->wq) {
+ pr_err("%s: Unable to create workqueue:%s\n",
+ __func__, ctrl_bridge_names[portno]);
+ return -ENOMEM;
+ }
+
+ port->port_num = portno;
+ port->gtype = gtype;
+
+ spin_lock_init(&port->port_lock);
+
+ INIT_WORK(&port->connect_w, ghsic_ctrl_connect_w);
+ INIT_WORK(&port->disconnect_w, gctrl_disconnect_w);
+
+ port->brdg.ch_id = portno;
+ port->brdg.ctx = port;
+ port->brdg.ops.send_pkt = ghsic_ctrl_receive;
+ if (port->gtype == USB_GADGET_SERIAL)
+ port->brdg.ops.send_cbits = ghsic_ctrl_status;
+ gctrl_ports[portno].port = port;
+
+ pdrv = &gctrl_ports[portno].pdrv;
+ pdrv->probe = ghsic_ctrl_probe;
+ pdrv->remove = ghsic_ctrl_remove;
+ pdrv->driver.name = ctrl_bridge_names[portno];
+ pdrv->driver.owner = THIS_MODULE;
+
+ platform_driver_register(pdrv);
+
+ pr_debug("%s: port:%p portno:%d\n", __func__, port, portno);
+
+ return 0;
+}
+
+int ghsic_ctrl_setup(unsigned int num_ports, enum gadget_type gtype)
+{
+ int first_port_id = no_ctrl_ports;
+ int total_num_ports = num_ports + no_ctrl_ports;
+ int i;
+ int ret = 0;
+
+ if (!num_ports || total_num_ports > NUM_PORTS) {
+ pr_err("%s: Invalid num of ports count:%d\n",
+ __func__, num_ports);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: requested ports:%d\n", __func__, num_ports);
+
+ for (i = first_port_id; i < (first_port_id + num_ports); i++) {
+
+ /*probe can be called while port_alloc,so update no_ctrl_ports*/
+ no_ctrl_ports++;
+ ret = gctrl_port_alloc(i, gtype);
+ if (ret) {
+ no_ctrl_ports--;
+ pr_err("%s: Unable to alloc port:%d\n", __func__, i);
+ goto free_ports;
+ }
+ }
+
+ return first_port_id;
+
+free_ports:
+ for (i = first_port_id; i < no_ctrl_ports; i++)
+ ghsic_ctrl_port_free(i);
+ no_ctrl_ports = first_port_id;
+ return ret;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t gctrl_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct gctrl_port *port;
+ struct platform_driver *pdrv;
+ char *buf;
+ unsigned long flags;
+ int ret;
+ int i;
+ int temp = 0;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < no_ctrl_ports; i++) {
+ port = gctrl_ports[i].port;
+ if (!port)
+ continue;
+ pdrv = &gctrl_ports[i].pdrv;
+ spin_lock_irqsave(&port->port_lock, flags);
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "\nName: %s\n"
+ "#PORT:%d port: %p\n"
+ "to_usbhost: %lu\n"
+ "to_modem: %lu\n"
+ "cpkt_drp_cnt: %lu\n"
+ "DTR: %s\n"
+ "ch_open: %d\n"
+ "ch_ready: %d\n",
+ pdrv->driver.name,
+ i, port,
+ port->to_host, port->to_modem,
+ port->drp_cpkt_cnt,
+ port->cbits_tomodem ? "HIGH" : "LOW",
+ test_bit(CH_OPENED, &port->bridge_sts),
+ test_bit(CH_READY, &port->bridge_sts));
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t gctrl_reset_stats(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct gctrl_port *port;
+ int i;
+ unsigned long flags;
+
+ for (i = 0; i < no_ctrl_ports; i++) {
+ port = gctrl_ports[i].port;
+ if (!port)
+ continue;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->to_host = 0;
+ port->to_modem = 0;
+ port->drp_cpkt_cnt = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+ return count;
+}
+
+const struct file_operations gctrl_stats_ops = {
+ .read = gctrl_read_stats,
+ .write = gctrl_reset_stats,
+};
+
+struct dentry *gctrl_dent;
+struct dentry *gctrl_dfile;
+static void gctrl_debugfs_init(void)
+{
+ gctrl_dent = debugfs_create_dir("ghsic_ctrl_xport", 0);
+ if (IS_ERR(gctrl_dent))
+ return;
+
+ gctrl_dfile =
+ debugfs_create_file("status", 0444, gctrl_dent, 0,
+ &gctrl_stats_ops);
+ if (!gctrl_dfile || IS_ERR(gctrl_dfile))
+ debugfs_remove(gctrl_dent);
+}
+
+static void gctrl_debugfs_exit(void)
+{
+ debugfs_remove(gctrl_dfile);
+ debugfs_remove(gctrl_dent);
+}
+
+#else
+static void gctrl_debugfs_init(void) { }
+static void gctrl_debugfs_exit(void) { }
+#endif
+
+static int __init gctrl_init(void)
+{
+ gctrl_debugfs_init();
+
+ return 0;
+}
+module_init(gctrl_init);
+
+static void __exit gctrl_exit(void)
+{
+ gctrl_debugfs_exit();
+}
+module_exit(gctrl_exit);
+MODULE_DESCRIPTION("hsic control xport driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/gadget/u_data_hsic.c b/drivers/usb/gadget/u_data_hsic.c
new file mode 100644
index 0000000..61458ea
--- /dev/null
+++ b/drivers/usb/gadget/u_data_hsic.c
@@ -0,0 +1,961 @@
+/* Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#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/netdevice.h>
+#include <linux/debugfs.h>
+#include <linux/bitops.h>
+#include <linux/termios.h>
+#include <mach/usb_bridge.h>
+#include <mach/usb_gadget_xport.h>
+
+static unsigned int no_data_ports;
+
+static const char *data_bridge_names[] = {
+ "dun_data_hsic0",
+ "rmnet_data_hsic0"
+};
+
+#define DATA_BRIDGE_NAME_MAX_LEN 20
+
+#define GHSIC_DATA_RMNET_RX_Q_SIZE 50
+#define GHSIC_DATA_RMNET_TX_Q_SIZE 300
+#define GHSIC_DATA_SERIAL_RX_Q_SIZE 2
+#define GHSIC_DATA_SERIAL_TX_Q_SIZE 2
+#define GHSIC_DATA_RX_REQ_SIZE 2048
+
+static unsigned int ghsic_data_rmnet_tx_q_size = GHSIC_DATA_RMNET_TX_Q_SIZE;
+module_param(ghsic_data_rmnet_tx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_rmnet_rx_q_size = GHSIC_DATA_RMNET_RX_Q_SIZE;
+module_param(ghsic_data_rmnet_rx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_serial_tx_q_size = GHSIC_DATA_SERIAL_TX_Q_SIZE;
+module_param(ghsic_data_serial_tx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_serial_rx_q_size = GHSIC_DATA_SERIAL_RX_Q_SIZE;
+module_param(ghsic_data_serial_rx_q_size, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_rx_req_size = GHSIC_DATA_RX_REQ_SIZE;
+module_param(ghsic_data_rx_req_size, uint, S_IRUGO | S_IWUSR);
+
+/*flow ctrl*/
+#define GHSIC_DATA_FLOW_CTRL_EN_THRESHOLD 500
+#define GHSIC_DATA_FLOW_CTRL_DISABLE 300
+#define GHSIC_DATA_FLOW_CTRL_SUPPORT 1
+#define GHSIC_DATA_PENDLIMIT_WITH_BRIDGE 500
+
+static unsigned int ghsic_data_fctrl_support = GHSIC_DATA_FLOW_CTRL_SUPPORT;
+module_param(ghsic_data_fctrl_support, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_fctrl_en_thld =
+ GHSIC_DATA_FLOW_CTRL_EN_THRESHOLD;
+module_param(ghsic_data_fctrl_en_thld, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_fctrl_dis_thld = GHSIC_DATA_FLOW_CTRL_DISABLE;
+module_param(ghsic_data_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int ghsic_data_pend_limit_with_bridge =
+ GHSIC_DATA_PENDLIMIT_WITH_BRIDGE;
+module_param(ghsic_data_pend_limit_with_bridge, uint, S_IRUGO | S_IWUSR);
+
+#define CH_OPENED 0
+#define CH_READY 1
+
+struct gdata_port {
+ /* port */
+ unsigned port_num;
+
+ /* gadget */
+ spinlock_t port_lock;
+ void *port_usb;
+ struct usb_ep *in;
+ struct usb_ep *out;
+
+ enum gadget_type gtype;
+
+ /* data transfer queues */
+ unsigned int tx_q_size;
+ struct list_head tx_idle;
+ struct sk_buff_head tx_skb_q;
+
+ unsigned int rx_q_size;
+ struct list_head rx_idle;
+ struct sk_buff_head rx_skb_q;
+
+ /* work */
+ struct workqueue_struct *wq;
+ struct work_struct connect_w;
+ struct work_struct disconnect_w;
+ struct work_struct write_tomdm_w;
+ struct work_struct write_tohost_w;
+
+ struct bridge brdg;
+
+ /*bridge status*/
+ unsigned long bridge_sts;
+
+ /*counters*/
+ unsigned long to_modem;
+ unsigned long to_host;
+ unsigned int rx_throttled_cnt;
+ unsigned int rx_unthrottled_cnt;
+ unsigned int tx_throttled_cnt;
+ unsigned int tx_unthrottled_cnt;
+ unsigned int tomodem_drp_cnt;
+ unsigned int unthrottled_pnd_skbs;
+};
+
+static struct {
+ struct gdata_port *port;
+ struct platform_driver pdrv;
+} gdata_ports[NUM_PORTS];
+
+static void ghsic_data_start_rx(struct gdata_port *port);
+
+static void ghsic_data_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);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static int ghsic_data_alloc_requests(struct usb_ep *ep, struct list_head *head,
+ int num,
+ void (*cb)(struct usb_ep *ep, struct usb_request *),
+ gfp_t flags)
+{
+ int i;
+ struct usb_request *req;
+
+ pr_debug("%s: ep:%s head:%p num:%d cb:%p", __func__,
+ ep->name, head, num, cb);
+
+ for (i = 0; i < num; i++) {
+ req = usb_ep_alloc_request(ep, flags);
+ 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;
+}
+
+static void ghsic_data_unthrottle_tx(void *ctx)
+{
+ struct gdata_port *port = ctx;
+ unsigned long flags;
+
+ if (!port)
+ return;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb) {
+ port->tx_unthrottled_cnt++;
+ queue_work(port->wq, &port->write_tomdm_w);
+ pr_debug("%s: port num =%d unthrottled\n", __func__,
+ port->port_num);
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static void ghsic_data_write_tohost(struct work_struct *w)
+{
+ unsigned long flags;
+ struct sk_buff *skb;
+ int ret;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ struct gdata_port *port;
+
+ port = container_of(w, struct gdata_port, write_tohost_w);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ ep = port->in;
+
+ while (!list_empty(&port->tx_idle)) {
+ skb = __skb_dequeue(&port->tx_skb_q);
+ if (!skb)
+ break;
+
+ req = list_first_entry(&port->tx_idle, struct usb_request,
+ list);
+ req->context = skb;
+ req->buf = skb->data;
+ req->length = skb->len;
+
+ list_del(&req->list);
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ ret = usb_ep_queue(ep, req, GFP_KERNEL);
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (ret) {
+ pr_err("%s: usb epIn failed\n", __func__);
+ list_add(&req->list, &port->tx_idle);
+ dev_kfree_skb_any(skb);
+ break;
+ }
+ port->to_host++;
+ if (ghsic_data_fctrl_support &&
+ port->tx_skb_q.qlen <= ghsic_data_fctrl_dis_thld &&
+ test_and_clear_bit(RX_THROTTLED, &port->brdg.flags)) {
+ port->rx_unthrottled_cnt++;
+ port->unthrottled_pnd_skbs = port->tx_skb_q.qlen;
+ pr_debug_ratelimited("%s: disable flow ctrl:"
+ " tx skbq len: %u\n",
+ __func__, port->tx_skb_q.qlen);
+ data_bridge_unthrottle_rx(port->brdg.ch_id);
+ }
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int ghsic_data_receive(void *p, void *data, size_t len)
+{
+ struct gdata_port *port = p;
+ unsigned long flags;
+ struct sk_buff *skb = data;
+
+ if (!port) {
+ dev_kfree_skb_any(skb);
+ return -EINVAL;
+ }
+
+ pr_debug("%s: p:%p#%d skb_len:%d\n", __func__,
+ port, port->port_num, skb->len);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ dev_kfree_skb_any(skb);
+ return -ENOTCONN;
+ }
+
+ __skb_queue_tail(&port->tx_skb_q, skb);
+
+ if (ghsic_data_fctrl_support &&
+ port->tx_skb_q.qlen >= ghsic_data_fctrl_en_thld) {
+ set_bit(RX_THROTTLED, &port->brdg.flags);
+ port->rx_throttled_cnt++;
+ pr_debug_ratelimited("%s: flow ctrl enabled: tx skbq len: %u\n",
+ __func__, port->tx_skb_q.qlen);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ queue_work(port->wq, &port->write_tohost_w);
+ return -EBUSY;
+ }
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ queue_work(port->wq, &port->write_tohost_w);
+
+ return 0;
+}
+
+static void ghsic_data_write_tomdm(struct work_struct *w)
+{
+ struct gdata_port *port;
+ struct sk_buff *skb;
+ unsigned long flags;
+ int ret;
+
+ port = container_of(w, struct gdata_port, write_tomdm_w);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ if (test_bit(TX_THROTTLED, &port->brdg.flags)) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ goto start_rx;
+ }
+
+ while ((skb = __skb_dequeue(&port->rx_skb_q))) {
+ pr_debug("%s: port:%p tom:%lu pno:%d\n", __func__,
+ port, port->to_modem, port->port_num);
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ ret = data_bridge_write(port->brdg.ch_id, skb);
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (ret < 0) {
+ if (ret == -EBUSY) {
+ /*flow control*/
+ port->tx_throttled_cnt++;
+ break;
+ }
+ pr_err_ratelimited("%s: write error:%d\n",
+ __func__, ret);
+ port->tomodem_drp_cnt++;
+ dev_kfree_skb_any(skb);
+ break;
+ }
+ port->to_modem++;
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+start_rx:
+ ghsic_data_start_rx(port);
+}
+
+static void ghsic_data_epin_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct gdata_port *port = ep->driver_data;
+ struct sk_buff *skb = req->context;
+ int status = req->status;
+
+ switch (status) {
+ case 0:
+ /* successful completion */
+ break;
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ /* connection gone */
+ dev_kfree_skb_any(skb);
+ req->buf = 0;
+ usb_ep_free_request(ep, req);
+ return;
+ default:
+ pr_err("%s: data tx ep error %d\n", __func__, status);
+ break;
+ }
+
+ dev_kfree_skb_any(skb);
+
+ spin_lock(&port->port_lock);
+ list_add_tail(&req->list, &port->tx_idle);
+ spin_unlock(&port->port_lock);
+
+ queue_work(port->wq, &port->write_tohost_w);
+}
+
+static void
+ghsic_data_epout_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct gdata_port *port = ep->driver_data;
+ struct sk_buff *skb = req->context;
+ int status = req->status;
+ int queue = 0;
+
+ switch (status) {
+ case 0:
+ skb_put(skb, req->actual);
+ queue = 1;
+ break;
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ /* cable disconnection */
+ dev_kfree_skb_any(skb);
+ req->buf = 0;
+ usb_ep_free_request(ep, req);
+ return;
+ default:
+ pr_err_ratelimited("%s: %s response error %d, %d/%d\n",
+ __func__, ep->name, status,
+ req->actual, req->length);
+ dev_kfree_skb_any(skb);
+ break;
+ }
+
+ spin_lock(&port->port_lock);
+ if (queue) {
+ __skb_queue_tail(&port->rx_skb_q, skb);
+ list_add_tail(&req->list, &port->rx_idle);
+ queue_work(port->wq, &port->write_tomdm_w);
+ }
+ spin_unlock(&port->port_lock);
+}
+
+static void ghsic_data_start_rx(struct gdata_port *port)
+{
+ struct usb_request *req;
+ struct usb_ep *ep;
+ unsigned long flags;
+ int ret;
+ struct sk_buff *skb;
+
+ pr_debug("%s: port:%p\n", __func__, port);
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ ep = port->out;
+
+ while (port->port_usb && !list_empty(&port->rx_idle)) {
+ if (port->rx_skb_q.qlen > ghsic_data_pend_limit_with_bridge)
+ break;
+
+ req = list_first_entry(&port->rx_idle,
+ struct usb_request, list);
+
+ skb = alloc_skb(ghsic_data_rx_req_size, GFP_ATOMIC);
+ if (!skb)
+ break;
+
+ list_del(&req->list);
+ req->buf = skb->data;
+ req->length = ghsic_data_rx_req_size;
+ req->context = skb;
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ ret = usb_ep_queue(ep, req, GFP_KERNEL);
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (ret) {
+ dev_kfree_skb_any(skb);
+
+ pr_err_ratelimited("%s: rx queue failed\n", __func__);
+
+ if (port->port_usb)
+ list_add(&req->list, &port->rx_idle);
+ else
+ usb_ep_free_request(ep, req);
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static void ghsic_data_start_io(struct gdata_port *port)
+{
+ unsigned long flags;
+ struct usb_ep *ep;
+ int ret;
+
+ pr_debug("%s: port:%p\n", __func__, port);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (!port->port_usb) {
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ ep = port->out;
+ ret = ghsic_data_alloc_requests(ep, &port->rx_idle,
+ port->rx_q_size, ghsic_data_epout_complete, GFP_ATOMIC);
+ if (ret) {
+ pr_err("%s: rx req allocation failed\n", __func__);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ ep = port->in;
+ ret = ghsic_data_alloc_requests(ep, &port->tx_idle,
+ port->tx_q_size, ghsic_data_epin_complete, GFP_ATOMIC);
+ if (ret) {
+ pr_err("%s: tx req allocation failed\n", __func__);
+ ghsic_data_free_requests(ep, &port->rx_idle);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ return;
+ }
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ /* queue out requests */
+ ghsic_data_start_rx(port);
+}
+
+static void ghsic_data_connect_w(struct work_struct *w)
+{
+ struct gdata_port *port =
+ container_of(w, struct gdata_port, connect_w);
+ int ret;
+
+ if (!port || !test_bit(CH_READY, &port->bridge_sts))
+ return;
+
+ pr_debug("%s: port:%p\n", __func__, port);
+
+ ret = data_bridge_open(&port->brdg);
+ if (ret) {
+ pr_err("%s: unable open bridge ch:%d err:%d\n",
+ __func__, port->brdg.ch_id, ret);
+ return;
+ }
+
+ set_bit(CH_OPENED, &port->bridge_sts);
+
+ ghsic_data_start_io(port);
+}
+
+static void ghsic_data_disconnect_w(struct work_struct *w)
+{
+ struct gdata_port *port =
+ container_of(w, struct gdata_port, disconnect_w);
+
+ if (!test_bit(CH_OPENED, &port->bridge_sts))
+ return;
+
+ data_bridge_close(port->brdg.ch_id);
+ clear_bit(CH_OPENED, &port->bridge_sts);
+}
+
+static void ghsic_data_free_buffers(struct gdata_port *port)
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+
+ if (!port || !port->port_usb)
+ goto free_buf_out;
+
+ ghsic_data_free_requests(port->in, &port->tx_idle);
+ ghsic_data_free_requests(port->out, &port->rx_idle);
+
+ while ((skb = __skb_dequeue(&port->tx_skb_q)))
+ dev_kfree_skb_any(skb);
+
+ while ((skb = __skb_dequeue(&port->rx_skb_q)))
+ dev_kfree_skb_any(skb);
+
+free_buf_out:
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int ghsic_data_probe(struct platform_device *pdev)
+{
+ struct gdata_port *port;
+ unsigned long flags;
+
+ pr_debug("%s: name:%s no_data_ports= %d\n",
+ __func__, pdev->name, no_data_ports);
+
+ if (pdev->id >= no_data_ports) {
+ pr_err("%s: invalid port: %d\n", __func__, pdev->id);
+ return -EINVAL;
+ }
+
+ port = gdata_ports[pdev->id].port;
+ set_bit(CH_READY, &port->bridge_sts);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ /* if usb is online, try opening bridge */
+ if (port->port_usb)
+ queue_work(port->wq, &port->connect_w);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return 0;
+}
+
+/* mdm disconnect */
+static int ghsic_data_remove(struct platform_device *pdev)
+{
+ struct gdata_port *port;
+ struct usb_ep *ep_in = NULL;
+ struct usb_ep *ep_out = NULL;
+ unsigned long flags;
+
+ pr_debug("%s: name:%s\n", __func__, pdev->name);
+
+ if (pdev->id >= no_data_ports) {
+ pr_err("%s: invalid port: %d\n", __func__, pdev->id);
+ return -EINVAL;
+ }
+
+ port = gdata_ports[pdev->id].port;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb) {
+ ep_in = port->in;
+ ep_out = port->out;
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ if (ep_in)
+ usb_ep_fifo_flush(ep_in);
+ if (ep_out)
+ usb_ep_fifo_flush(ep_out);
+
+ ghsic_data_free_buffers(port);
+
+ data_bridge_close(port->brdg.ch_id);
+
+ clear_bit(CH_READY, &port->bridge_sts);
+ clear_bit(CH_OPENED, &port->bridge_sts);
+
+ return 0;
+}
+
+static void ghsic_data_port_free(int portno)
+{
+ struct gdata_port *port = gdata_ports[portno].port;
+ struct platform_driver *pdrv = &gdata_ports[portno].pdrv;
+
+ destroy_workqueue(port->wq);
+ kfree(port);
+
+ if (pdrv)
+ platform_driver_unregister(pdrv);
+}
+
+static int ghsic_data_port_alloc(unsigned port_num, enum gadget_type gtype)
+{
+ struct gdata_port *port;
+ struct platform_driver *pdrv;
+
+ port = kzalloc(sizeof(struct gdata_port), GFP_KERNEL);
+ if (!port)
+ return -ENOMEM;
+
+ port->wq = create_singlethread_workqueue(data_bridge_names[port_num]);
+ if (!port->wq) {
+ pr_err("%s: Unable to create workqueue:%s\n",
+ __func__, data_bridge_names[port_num]);
+ return -ENOMEM;
+ }
+ port->port_num = port_num;
+
+ /* port initialization */
+ spin_lock_init(&port->port_lock);
+
+ INIT_WORK(&port->connect_w, ghsic_data_connect_w);
+ INIT_WORK(&port->disconnect_w, ghsic_data_disconnect_w);
+ INIT_WORK(&port->write_tohost_w, ghsic_data_write_tohost);
+ INIT_WORK(&port->write_tomdm_w, ghsic_data_write_tomdm);
+
+ INIT_LIST_HEAD(&port->tx_idle);
+ INIT_LIST_HEAD(&port->rx_idle);
+
+ skb_queue_head_init(&port->tx_skb_q);
+ skb_queue_head_init(&port->rx_skb_q);
+
+ port->gtype = gtype;
+ port->brdg.ch_id = port_num;
+ port->brdg.ctx = port;
+ port->brdg.ops.send_pkt = ghsic_data_receive;
+ port->brdg.ops.unthrottle_tx = ghsic_data_unthrottle_tx;
+ gdata_ports[port_num].port = port;
+
+ pdrv = &gdata_ports[port_num].pdrv;
+ pdrv->probe = ghsic_data_probe;
+ pdrv->remove = ghsic_data_remove;
+ pdrv->driver.name = data_bridge_names[port_num];
+ pdrv->driver.owner = THIS_MODULE;
+
+ platform_driver_register(pdrv);
+
+ pr_debug("%s: port:%p portno:%d\n", __func__, port, port_num);
+
+ return 0;
+}
+
+void ghsic_data_disconnect(void *gptr, int port_num)
+{
+ struct gdata_port *port;
+ unsigned long flags;
+
+ pr_debug("%s: port#%d\n", __func__, port_num);
+
+ port = gdata_ports[port_num].port;
+
+ if (port_num > no_data_ports) {
+ pr_err("%s: invalid portno#%d\n", __func__, port_num);
+ return;
+ }
+
+ if (!gptr || !port) {
+ pr_err("%s: port is null\n", __func__);
+ return;
+ }
+
+ ghsic_data_free_buffers(port);
+
+ /* disable endpoints */
+ if (port->in)
+ usb_ep_disable(port->out);
+
+ if (port->out)
+ usb_ep_disable(port->in);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->port_usb = 0;
+ port->in = NULL;
+ port->out = NULL;
+ clear_bit(TX_THROTTLED, &port->brdg.flags);
+ clear_bit(RX_THROTTLED, &port->brdg.flags);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ queue_work(port->wq, &port->disconnect_w);
+}
+
+int ghsic_data_connect(void *gptr, int port_num)
+{
+ struct gdata_port *port;
+ struct gserial *gser;
+ struct grmnet *gr;
+ struct usb_endpoint_descriptor *in_desc;
+ struct usb_endpoint_descriptor *out_desc;
+ unsigned long flags;
+ int ret = 0;
+
+ pr_debug("%s: port#%d\n", __func__, port_num);
+
+ port = gdata_ports[port_num].port;
+
+ if (port_num > no_data_ports) {
+ pr_err("%s: invalid portno#%d\n", __func__, port_num);
+ return -ENODEV;
+ }
+
+ if (!gptr || !port) {
+ pr_err("%s: port is null\n", __func__);
+ return -ENODEV;
+ }
+
+ if (port->gtype == USB_GADGET_SERIAL) {
+ gser = gptr;
+ port->in = gser->in;
+ port->out = gser->out;
+ port->tx_q_size = ghsic_data_serial_tx_q_size;
+ port->rx_q_size = ghsic_data_serial_rx_q_size;
+ gser->in->driver_data = port;
+ gser->out->driver_data = port;
+ in_desc = gser->in_desc;
+ out_desc = gser->out_desc;
+ } else {
+ gr = gptr;
+ port->in = gr->in;
+ port->out = gr->out;
+ port->tx_q_size = ghsic_data_rmnet_tx_q_size;
+ port->rx_q_size = ghsic_data_rmnet_rx_q_size;
+ gr->in->driver_data = port;
+ gr->out->driver_data = port;
+ in_desc = gr->in_desc;
+ out_desc = gr->out_desc;
+ }
+
+ ret = usb_ep_enable(port->in, in_desc);
+ if (ret) {
+ pr_err("%s: usb_ep_enable failed eptype:IN ep:%p",
+ __func__, port->in);
+ goto fail;
+ }
+
+ ret = usb_ep_enable(port->out, out_desc);
+ if (ret) {
+ pr_err("%s: usb_ep_enable failed eptype:OUT ep:%p",
+ __func__, port->out);
+ usb_ep_disable(port->in);
+ goto fail;
+ }
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->port_usb = gptr;
+ port->to_host = 0;
+ port->to_modem = 0;
+ port->tomodem_drp_cnt = 0;
+ port->rx_throttled_cnt = 0;
+ port->rx_unthrottled_cnt = 0;
+ port->tx_throttled_cnt = 0;
+ port->tx_unthrottled_cnt = 0;
+ port->unthrottled_pnd_skbs = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ queue_work(port->wq, &port->connect_w);
+fail:
+ return ret;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t ghsic_data_read_stats(struct file *file,
+ char __user *ubuf, size_t count, loff_t *ppos)
+{
+ struct gdata_port *port;
+ struct platform_driver *pdrv;
+ char *buf;
+ unsigned long flags;
+ int ret;
+ int i;
+ int temp = 0;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < no_data_ports; i++) {
+ port = gdata_ports[i].port;
+ if (!port)
+ continue;
+ pdrv = &gdata_ports[i].pdrv;
+ spin_lock_irqsave(&port->port_lock, flags);
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "\nName: %s\n"
+ "#PORT:%d port#: %p\n"
+ "dpkts_to_usbhost: %lu\n"
+ "dpkts_to_modem: %lu\n"
+ "tomodem_drp_cnt: %u\n"
+ "tx_buf_len: %u\n"
+ "rx_buf_len: %u\n"
+ "rx thld cnt %u\n"
+ "rx unthld cnt %u\n"
+ "tx thld cnt %u\n"
+ "tx unthld cnt %u\n"
+ "uthld pnd skbs %u\n"
+ "RX_THROTTLED %d\n"
+ "TX_THROTTLED %d\n"
+ "data_ch_open: %d\n"
+ "data_ch_ready: %d\n",
+ pdrv->driver.name,
+ i, port,
+ port->to_host, port->to_modem,
+ port->tomodem_drp_cnt,
+ port->tx_skb_q.qlen,
+ port->rx_skb_q.qlen,
+ port->rx_throttled_cnt,
+ port->rx_unthrottled_cnt,
+ port->tx_throttled_cnt,
+ port->tx_unthrottled_cnt,
+ port->unthrottled_pnd_skbs,
+ test_bit(RX_THROTTLED, &port->brdg.flags),
+ test_bit(TX_THROTTLED, &port->brdg.flags),
+ test_bit(CH_OPENED, &port->bridge_sts),
+ test_bit(CH_READY, &port->bridge_sts));
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t ghsic_data_reset_stats(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct gdata_port *port;
+ int i;
+ unsigned long flags;
+
+ for (i = 0; i < no_data_ports; i++) {
+ port = gdata_ports[i].port;
+ if (!port)
+ continue;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ port->to_host = 0;
+ port->to_modem = 0;
+ port->tomodem_drp_cnt = 0;
+ port->rx_throttled_cnt = 0;
+ port->rx_unthrottled_cnt = 0;
+ port->tx_throttled_cnt = 0;
+ port->tx_unthrottled_cnt = 0;
+ port->unthrottled_pnd_skbs = 0;
+ spin_unlock_irqrestore(&port->port_lock, flags);
+ }
+ return count;
+}
+
+const struct file_operations ghsic_stats_ops = {
+ .read = ghsic_data_read_stats,
+ .write = ghsic_data_reset_stats,
+};
+
+static struct dentry *gdata_dent;
+static struct dentry *gdata_dfile;
+
+static void ghsic_data_debugfs_init(void)
+{
+ gdata_dent = debugfs_create_dir("ghsic_data_xport", 0);
+ if (IS_ERR(gdata_dent))
+ return;
+
+ gdata_dfile = debugfs_create_file("status", 0444, gdata_dent, 0,
+ &ghsic_stats_ops);
+ if (!gdata_dfile || IS_ERR(gdata_dfile))
+ debugfs_remove(gdata_dent);
+}
+
+static void ghsic_data_debugfs_exit(void)
+{
+ debugfs_remove(gdata_dfile);
+ debugfs_remove(gdata_dent);
+}
+
+#else
+static void ghsic_data_debugfs_init(void) { }
+static void ghsic_data_debugfs_exit(void) { }
+
+#endif
+
+int ghsic_data_setup(unsigned num_ports, enum gadget_type gtype)
+{
+ int first_port_id = no_data_ports;
+ int total_num_ports = num_ports + no_data_ports;
+ int ret = 0;
+ int i;
+
+ if (!num_ports || total_num_ports > NUM_PORTS) {
+ pr_err("%s: Invalid num of ports count:%d\n",
+ __func__, num_ports);
+ return -EINVAL;
+ }
+ pr_debug("%s: count: %d\n", __func__, num_ports);
+
+ for (i = first_port_id; i < (num_ports + first_port_id); i++) {
+
+ /*probe can be called while port_alloc,so update no_data_ports*/
+ no_data_ports++;
+ ret = ghsic_data_port_alloc(i, gtype);
+ if (ret) {
+ no_data_ports--;
+ pr_err("%s: Unable to alloc port:%d\n", __func__, i);
+ goto free_ports;
+ }
+ }
+
+ /*return the starting index*/
+ return first_port_id;
+
+free_ports:
+ for (i = first_port_id; i < no_data_ports; i++)
+ ghsic_data_port_free(i);
+ no_data_ports = first_port_id;
+
+ return ret;
+}
+
+static int __init ghsic_data_init(void)
+{
+ ghsic_data_debugfs_init();
+
+ return 0;
+}
+module_init(ghsic_data_init);
+
+static void __exit ghsic_data_exit(void)
+{
+ ghsic_data_debugfs_exit();
+}
+module_exit(ghsic_data_exit);
+MODULE_DESCRIPTION("hsic data xport driver");
+MODULE_LICENSE("GPL v2");