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/f_rmnet_smd_sdio.c b/drivers/usb/gadget/f_rmnet_smd_sdio.c
new file mode 100644
index 0000000..e99716b
--- /dev/null
+++ b/drivers/usb/gadget/f_rmnet_smd_sdio.c
@@ -0,0 +1,1995 @@
+/*
+ * f_rmnet_smd_sdio.c -- RmNet SMD & SDIO function driver
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 Nokia Corporation
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/netdevice.h>
+#include <linux/interrupt.h>
+
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <asm/ioctls.h>
+
+#include <linux/usb/cdc.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/android_composite.h>
+#include <linux/termios.h>
+#include <linux/debugfs.h>
+
+#include <mach/msm_smd.h>
+#include <mach/sdio_cmux.h>
+#include <mach/sdio_dmux.h>
+
+static uint32_t rmnet_sdio_ctl_ch = CONFIG_RMNET_SMD_SDIO_CTL_CHANNEL;
+module_param(rmnet_sdio_ctl_ch, uint, S_IRUGO);
+MODULE_PARM_DESC(rmnet_sdio_ctl_ch, "RmNet control SDIO channel ID");
+
+static uint32_t rmnet_sdio_data_ch = CONFIG_RMNET_SMD_SDIO_DATA_CHANNEL;
+module_param(rmnet_sdio_data_ch, uint, S_IRUGO);
+MODULE_PARM_DESC(rmnet_sdio_data_ch, "RmNet data SDIO channel ID");
+
+static char *rmnet_smd_data_ch = CONFIG_RMNET_SDIO_SMD_DATA_CHANNEL;
+module_param(rmnet_smd_data_ch, charp, S_IRUGO);
+MODULE_PARM_DESC(rmnet_smd_data_ch, "RmNet data SMD channel");
+
+#define ACM_CTRL_DTR	(1 << 0)
+
+#define SDIO_MUX_HDR           8
+#define RMNET_SDIO_NOTIFY_INTERVAL  5
+#define RMNET_SDIO_MAX_NFY_SZE  sizeof(struct usb_cdc_notification)
+
+#define RMNET_SDIO_RX_REQ_MAX             16
+#define RMNET_SDIO_RX_REQ_SIZE            2048
+#define RMNET_SDIO_TX_REQ_MAX             100
+
+#define RMNET_SDIO_TX_PKT_DROP_THRESHOLD		1000
+#define RMNET_SDIO_RX_PKT_FLOW_CTRL_EN_THRESHOLD	1000
+#define RMNET_SDIO_RX_PKT_FLOW_CTRL_DISABLE		500
+
+static uint32_t sdio_tx_pkt_drop_thld = RMNET_SDIO_TX_PKT_DROP_THRESHOLD;
+module_param(sdio_tx_pkt_drop_thld, uint, S_IRUGO | S_IWUSR);
+
+static uint32_t sdio_rx_fctrl_en_thld =
+		RMNET_SDIO_RX_PKT_FLOW_CTRL_EN_THRESHOLD;
+module_param(sdio_rx_fctrl_en_thld, uint, S_IRUGO | S_IWUSR);
+
+static uint32_t sdio_rx_fctrl_dis_thld = RMNET_SDIO_RX_PKT_FLOW_CTRL_DISABLE;
+module_param(sdio_rx_fctrl_dis_thld, uint, S_IRUGO | S_IWUSR);
+
+
+#define RMNET_SMD_RX_REQ_MAX		8
+#define RMNET_SMD_RX_REQ_SIZE		2048
+#define RMNET_SMD_TX_REQ_MAX		8
+#define RMNET_SMD_TX_REQ_SIZE		2048
+#define RMNET_SMD_TXN_MAX		2048
+
+struct rmnet_ctrl_pkt {
+	void *buf;
+	int len;
+	struct list_head list;
+};
+
+enum usb_rmnet_xport_type {
+	USB_RMNET_XPORT_UNDEFINED,
+	USB_RMNET_XPORT_SDIO,
+	USB_RMNET_XPORT_SMD,
+};
+
+struct rmnet_ctrl_dev {
+	struct list_head tx_q;
+	wait_queue_head_t tx_wait_q;
+	unsigned long tx_len;
+
+	struct list_head rx_q;
+	unsigned long rx_len;
+
+	unsigned long cbits_to_modem;
+
+	unsigned	opened;
+};
+
+struct rmnet_sdio_dev {
+	/* Tx/Rx lists */
+	struct list_head tx_idle;
+	struct sk_buff_head    tx_skb_queue;
+	struct list_head rx_idle;
+	struct sk_buff_head    rx_skb_queue;
+
+
+
+	struct work_struct data_rx_work;
+
+	struct delayed_work open_work;
+	atomic_t sdio_open;
+
+	unsigned int dpkts_pending_atdmux;
+};
+
+/* Data SMD channel */
+struct rmnet_smd_info {
+	struct smd_channel *ch;
+	struct tasklet_struct tx_tlet;
+	struct tasklet_struct rx_tlet;
+#define CH_OPENED 0
+	unsigned long flags;
+	/* pending rx packet length */
+	atomic_t rx_pkt;
+	/* wait for smd open event*/
+	wait_queue_head_t wait;
+};
+
+struct rmnet_smd_dev {
+	/* Tx/Rx lists */
+	struct list_head tx_idle;
+	struct list_head rx_idle;
+	struct list_head rx_queue;
+
+	struct rmnet_smd_info smd_data;
+};
+
+struct rmnet_dev {
+	struct usb_function function;
+	struct usb_composite_dev *cdev;
+
+	struct usb_ep *epout;
+	struct usb_ep *epin;
+	struct usb_ep *epnotify;
+	struct usb_request *notify_req;
+
+	struct rmnet_smd_dev smd_dev;
+	struct rmnet_sdio_dev sdio_dev;
+	struct rmnet_ctrl_dev ctrl_dev;
+
+	u8 ifc_id;
+	enum usb_rmnet_xport_type xport;
+	spinlock_t lock;
+	atomic_t online;
+	atomic_t notify_count;
+	struct workqueue_struct *wq;
+	struct work_struct disconnect_work;
+
+	/* pkt counters */
+	unsigned long dpkts_tomsm;
+	unsigned long dpkts_tomdm;
+	unsigned long dpkts_tolaptop;
+	unsigned long tx_drp_cnt;
+	unsigned long cpkts_tolaptop;
+	unsigned long cpkts_tomdm;
+	unsigned long cpkts_drp_cnt;
+};
+
+static struct rmnet_dev *_dev;
+
+static struct usb_interface_descriptor rmnet_interface_desc = {
+	.bLength =              USB_DT_INTERFACE_SIZE,
+	.bDescriptorType =      USB_DT_INTERFACE,
+	.bNumEndpoints =        3,
+	.bInterfaceClass =      USB_CLASS_VENDOR_SPEC,
+	.bInterfaceSubClass =   USB_CLASS_VENDOR_SPEC,
+	.bInterfaceProtocol =   USB_CLASS_VENDOR_SPEC,
+};
+
+/* Full speed support */
+static struct usb_endpoint_descriptor rmnet_fs_notify_desc = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bEndpointAddress =     USB_DIR_IN,
+	.bmAttributes =         USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	__constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE),
+	.bInterval =            1 << RMNET_SDIO_NOTIFY_INTERVAL,
+};
+
+static struct usb_endpoint_descriptor rmnet_fs_in_desc  = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bEndpointAddress =     USB_DIR_IN,
+	.bmAttributes =         USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize   = __constant_cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor rmnet_fs_out_desc = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bEndpointAddress =     USB_DIR_OUT,
+	.bmAttributes =         USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize = __constant_cpu_to_le16(64),
+};
+
+static struct usb_descriptor_header *rmnet_fs_function[] = {
+	(struct usb_descriptor_header *) &rmnet_interface_desc,
+	(struct usb_descriptor_header *) &rmnet_fs_notify_desc,
+	(struct usb_descriptor_header *) &rmnet_fs_in_desc,
+	(struct usb_descriptor_header *) &rmnet_fs_out_desc,
+	NULL,
+};
+
+/* High speed support */
+static struct usb_endpoint_descriptor rmnet_hs_notify_desc  = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bEndpointAddress =     USB_DIR_IN,
+	.bmAttributes =         USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize =	__constant_cpu_to_le16(RMNET_SDIO_MAX_NFY_SZE),
+	.bInterval =            RMNET_SDIO_NOTIFY_INTERVAL + 4,
+};
+
+static struct usb_endpoint_descriptor rmnet_hs_in_desc = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bEndpointAddress =     USB_DIR_IN,
+	.bmAttributes =         USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	__constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor rmnet_hs_out_desc = {
+	.bLength =              USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType =      USB_DT_ENDPOINT,
+	.bEndpointAddress =     USB_DIR_OUT,
+	.bmAttributes =         USB_ENDPOINT_XFER_BULK,
+	.wMaxPacketSize =	__constant_cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *rmnet_hs_function[] = {
+	(struct usb_descriptor_header *) &rmnet_interface_desc,
+	(struct usb_descriptor_header *) &rmnet_hs_notify_desc,
+	(struct usb_descriptor_header *) &rmnet_hs_in_desc,
+	(struct usb_descriptor_header *) &rmnet_hs_out_desc,
+	NULL,
+};
+
+/* String descriptors */
+
+static struct usb_string rmnet_string_defs[] = {
+	[0].s = "RmNet",
+	{  } /* end of list */
+};
+
+static struct usb_gadget_strings rmnet_string_table = {
+	.language =             0x0409, /* en-us */
+	.strings =              rmnet_string_defs,
+};
+
+static struct usb_gadget_strings *rmnet_strings[] = {
+	&rmnet_string_table,
+	NULL,
+};
+
+static char *xport_to_str(enum usb_rmnet_xport_type t)
+{
+	switch (t) {
+	case USB_RMNET_XPORT_SDIO:
+		return "SDIO";
+	case USB_RMNET_XPORT_SMD:
+		return "SMD";
+	default:
+		return "UNDEFINED";
+	}
+}
+
+static struct rmnet_ctrl_pkt *rmnet_alloc_ctrl_pkt(unsigned len, gfp_t flags)
+{
+	struct rmnet_ctrl_pkt *cpkt;
+
+	cpkt = kzalloc(sizeof(struct rmnet_ctrl_pkt), flags);
+	if (!cpkt)
+		return 0;
+
+	cpkt->buf = kzalloc(len, flags);
+	if (!cpkt->buf) {
+		kfree(cpkt);
+		return 0;
+	}
+
+	cpkt->len = len;
+
+	return cpkt;
+
+}
+
+static void rmnet_free_ctrl_pkt(struct rmnet_ctrl_pkt *cpkt)
+{
+	kfree(cpkt->buf);
+	kfree(cpkt);
+}
+
+/*
+ * Allocate a usb_request and its buffer.  Returns a pointer to the
+ * usb_request or a pointer with an error code if there is an error.
+ */
+static struct usb_request *
+rmnet_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
+{
+	struct usb_request *req;
+
+	req = usb_ep_alloc_request(ep, kmalloc_flags);
+
+	if (len && req != NULL) {
+		req->length = len;
+		req->buf = kmalloc(len, kmalloc_flags);
+		if (req->buf == NULL) {
+			usb_ep_free_request(ep, req);
+			req = NULL;
+		}
+	}
+
+	return req ? req : ERR_PTR(-ENOMEM);
+}
+
+/*
+ * Free a usb_request and its buffer.
+ */
+static void rmnet_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+	kfree(req->buf);
+	usb_ep_free_request(ep, req);
+}
+
+static int rmnet_sdio_rx_submit(struct rmnet_dev *dev, struct usb_request *req,
+				gfp_t gfp_flags)
+{
+	struct sk_buff *skb;
+	int retval;
+
+	skb = alloc_skb(RMNET_SDIO_RX_REQ_SIZE + SDIO_MUX_HDR, gfp_flags);
+	if (skb == NULL)
+		return -ENOMEM;
+	skb_reserve(skb, SDIO_MUX_HDR);
+
+	req->buf = skb->data;
+	req->length = RMNET_SDIO_RX_REQ_SIZE;
+	req->context = skb;
+
+	retval = usb_ep_queue(dev->epout, req, gfp_flags);
+	if (retval)
+		dev_kfree_skb_any(skb);
+
+	return retval;
+}
+
+static void rmnet_sdio_start_rx(struct rmnet_dev *dev)
+{
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	int status;
+	struct usb_request *req;
+	struct list_head *pool;
+	unsigned long flags;
+
+	if (!atomic_read(&dev->online)) {
+		pr_debug("%s: USB not connected\n", __func__);
+		return;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	pool = &sdio_dev->rx_idle;
+	while (!list_empty(pool)) {
+		req = list_first_entry(pool, struct usb_request, list);
+		list_del(&req->list);
+
+		spin_unlock_irqrestore(&dev->lock, flags);
+		status = rmnet_sdio_rx_submit(dev, req, GFP_KERNEL);
+		spin_lock_irqsave(&dev->lock, flags);
+
+		if (status) {
+			ERROR(cdev, "rmnet data rx enqueue err %d\n", status);
+			list_add_tail(&req->list, &sdio_dev->rx_idle);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void rmnet_sdio_start_tx(struct rmnet_dev *dev)
+{
+	unsigned long			flags;
+	int				status;
+	struct sk_buff			*skb;
+	struct usb_request		*req;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct usb_composite_dev	*cdev = dev->cdev;
+
+
+	if (!atomic_read(&dev->online))
+		return;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	while (!list_empty(&sdio_dev->tx_idle)) {
+		skb = __skb_dequeue(&sdio_dev->tx_skb_queue);
+		if (!skb) {
+			spin_unlock_irqrestore(&dev->lock, flags);
+			return;
+		}
+
+		req = list_first_entry(&sdio_dev->tx_idle,
+				struct usb_request, list);
+		req->context = skb;
+		req->buf = skb->data;
+		req->length = skb->len;
+
+		list_del(&req->list);
+		spin_unlock(&dev->lock);
+		status = usb_ep_queue(dev->epin, req, GFP_ATOMIC);
+		spin_lock(&dev->lock);
+		if (status) {
+			/* USB still online, queue requests back */
+			if (atomic_read(&dev->online)) {
+				ERROR(cdev, "rmnet tx data enqueue err %d\n",
+						status);
+				list_add_tail(&req->list, &sdio_dev->tx_idle);
+				__skb_queue_head(&sdio_dev->tx_skb_queue, skb);
+			} else {
+				req->buf = 0;
+				rmnet_free_req(dev->epin, req);
+				dev_kfree_skb_any(skb);
+			}
+			break;
+		}
+		dev->dpkts_tolaptop++;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void rmnet_sdio_data_receive_cb(void *priv, struct sk_buff *skb)
+{
+	struct rmnet_dev *dev = priv;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	unsigned long flags;
+
+	if (!skb)
+		return;
+	if (!atomic_read(&dev->online)) {
+		dev_kfree_skb_any(skb);
+		return;
+	}
+	spin_lock_irqsave(&dev->lock, flags);
+	if (sdio_dev->tx_skb_queue.qlen > sdio_tx_pkt_drop_thld) {
+		pr_err_ratelimited("%s: tx pkt dropped: tx_drop_cnt:%lu\n",
+			__func__, dev->tx_drp_cnt);
+		dev->tx_drp_cnt++;
+		spin_unlock_irqrestore(&dev->lock, flags);
+		dev_kfree_skb_any(skb);
+		return;
+	}
+	__skb_queue_tail(&sdio_dev->tx_skb_queue, skb);
+	spin_unlock_irqrestore(&dev->lock, flags);
+	rmnet_sdio_start_tx(dev);
+}
+
+static void rmnet_sdio_data_write_done(void *priv, struct sk_buff *skb)
+{
+	struct rmnet_dev *dev = priv;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+
+	if (!skb)
+		return;
+
+	dev_kfree_skb_any(skb);
+	/* this function is called from
+	 * sdio mux from spin_lock_irqsave
+	 */
+	spin_lock(&dev->lock);
+	sdio_dev->dpkts_pending_atdmux--;
+
+	if (sdio_dev->dpkts_pending_atdmux >= sdio_rx_fctrl_dis_thld) {
+		spin_unlock(&dev->lock);
+		return;
+	}
+	spin_unlock(&dev->lock);
+
+	rmnet_sdio_start_rx(dev);
+}
+
+static void rmnet_sdio_data_rx_work(struct work_struct *w)
+{
+	struct rmnet_dev *dev = container_of(w, struct rmnet_dev,
+			sdio_dev.data_rx_work);
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+
+	struct sk_buff *skb;
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	while ((skb = __skb_dequeue(&sdio_dev->rx_skb_queue))) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		ret = msm_sdio_dmux_write(rmnet_sdio_data_ch, skb);
+		spin_lock_irqsave(&dev->lock, flags);
+		if (ret < 0) {
+			ERROR(cdev, "rmnet SDIO data write failed\n");
+			dev_kfree_skb_any(skb);
+		} else {
+			dev->dpkts_tomdm++;
+			sdio_dev->dpkts_pending_atdmux++;
+		}
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void
+rmnet_sdio_complete_epout(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev *dev = ep->driver_data;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct sk_buff *skb = req->context;
+	int status = req->status;
+	int queue = 0;
+
+	if (dev->xport == USB_RMNET_XPORT_UNDEFINED) {
+		dev_kfree_skb_any(skb);
+		req->buf = 0;
+		rmnet_free_req(ep, req);
+		return;
+	}
+
+	switch (status) {
+	case 0:
+		/* successful completion */
+		skb_put(skb, req->actual);
+		queue = 1;
+		break;
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		dev_kfree_skb_any(skb);
+		req->buf = 0;
+		rmnet_free_req(ep, req);
+		return;
+	default:
+		/* unexpected failure */
+		ERROR(cdev, "RMNET %s response error %d, %d/%d\n",
+			ep->name, status,
+			req->actual, req->length);
+		dev_kfree_skb_any(skb);
+		break;
+	}
+
+	spin_lock(&dev->lock);
+	if (queue) {
+		__skb_queue_tail(&sdio_dev->rx_skb_queue, skb);
+		queue_work(dev->wq, &sdio_dev->data_rx_work);
+	}
+
+	if (sdio_dev->dpkts_pending_atdmux >= sdio_rx_fctrl_en_thld) {
+		list_add_tail(&req->list, &sdio_dev->rx_idle);
+		spin_unlock(&dev->lock);
+		return;
+	}
+	spin_unlock(&dev->lock);
+
+	status = rmnet_sdio_rx_submit(dev, req, GFP_ATOMIC);
+	if (status) {
+		ERROR(cdev, "rmnet data rx enqueue err %d\n", status);
+		list_add_tail(&req->list, &sdio_dev->rx_idle);
+	}
+}
+
+static void
+rmnet_sdio_complete_epin(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev *dev = ep->driver_data;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct sk_buff  *skb = req->context;
+	struct usb_composite_dev *cdev = dev->cdev;
+	int status = req->status;
+
+	if (dev->xport == USB_RMNET_XPORT_UNDEFINED) {
+		dev_kfree_skb_any(skb);
+		req->buf = 0;
+		rmnet_free_req(ep, req);
+		return;
+	}
+
+	switch (status) {
+	case 0:
+		/* successful completion */
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		break;
+	default:
+		ERROR(cdev, "rmnet data tx ep error %d\n", status);
+		break;
+	}
+
+	spin_lock(&dev->lock);
+	list_add_tail(&req->list, &sdio_dev->tx_idle);
+	spin_unlock(&dev->lock);
+	dev_kfree_skb_any(skb);
+
+	rmnet_sdio_start_tx(dev);
+}
+
+static int rmnet_sdio_enable(struct rmnet_dev *dev)
+{
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	int i;
+	struct usb_request *req;
+
+	/*
+	 * If the memory allocation fails, all the allocated
+	 * requests will be freed upon cable disconnect.
+	 */
+	for (i = 0; i < RMNET_SDIO_RX_REQ_MAX; i++) {
+		req = rmnet_alloc_req(dev->epout, 0, GFP_KERNEL);
+		if (IS_ERR(req))
+			return PTR_ERR(req);
+		req->complete = rmnet_sdio_complete_epout;
+		list_add_tail(&req->list, &sdio_dev->rx_idle);
+	}
+	for (i = 0; i < RMNET_SDIO_TX_REQ_MAX; i++) {
+		req = rmnet_alloc_req(dev->epin, 0, GFP_KERNEL);
+		if (IS_ERR(req))
+			return PTR_ERR(req);
+		req->complete = rmnet_sdio_complete_epin;
+		list_add_tail(&req->list, &sdio_dev->tx_idle);
+	}
+
+	rmnet_sdio_start_rx(dev);
+	return 0;
+}
+
+static void rmnet_smd_start_rx(struct rmnet_dev *dev)
+{
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	int status;
+	struct usb_request *req;
+	struct list_head *pool = &smd_dev->rx_idle;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	while (!list_empty(pool)) {
+		req = list_entry(pool->next, struct usb_request, list);
+		list_del(&req->list);
+
+		spin_unlock_irqrestore(&dev->lock, flags);
+		status = usb_ep_queue(dev->epout, req, GFP_ATOMIC);
+		spin_lock_irqsave(&dev->lock, flags);
+
+		if (status) {
+			ERROR(cdev, "rmnet data rx enqueue err %d\n", status);
+			list_add_tail(&req->list, pool);
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void rmnet_smd_data_tx_tlet(unsigned long arg)
+{
+	struct rmnet_dev *dev = (struct rmnet_dev *) arg;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct usb_request *req;
+	int status;
+	int sz;
+	unsigned long flags;
+
+	while (1) {
+		if (!atomic_read(&dev->online))
+			break;
+		sz = smd_cur_packet_size(smd_dev->smd_data.ch);
+		if (sz == 0)
+			break;
+		if (smd_read_avail(smd_dev->smd_data.ch) < sz)
+			break;
+
+		spin_lock_irqsave(&dev->lock, flags);
+		if (list_empty(&smd_dev->tx_idle)) {
+			spin_unlock_irqrestore(&dev->lock, flags);
+			DBG(cdev, "rmnet data Tx buffers full\n");
+			break;
+		}
+		req = list_first_entry(&smd_dev->tx_idle,
+				struct usb_request, list);
+		list_del(&req->list);
+		spin_unlock_irqrestore(&dev->lock, flags);
+
+		req->length = smd_read(smd_dev->smd_data.ch, req->buf, sz);
+		status = usb_ep_queue(dev->epin, req, GFP_ATOMIC);
+		if (status) {
+			ERROR(cdev, "rmnet tx data enqueue err %d\n", status);
+			spin_lock_irqsave(&dev->lock, flags);
+			list_add_tail(&req->list, &smd_dev->tx_idle);
+			spin_unlock_irqrestore(&dev->lock, flags);
+			break;
+		}
+		dev->dpkts_tolaptop++;
+	}
+
+}
+
+static void rmnet_smd_data_rx_tlet(unsigned long arg)
+{
+	struct rmnet_dev *dev = (struct rmnet_dev *) arg;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct usb_request *req;
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	while (1) {
+		if (!atomic_read(&dev->online))
+			break;
+		if (list_empty(&smd_dev->rx_queue)) {
+			atomic_set(&smd_dev->smd_data.rx_pkt, 0);
+			break;
+		}
+		req = list_first_entry(&smd_dev->rx_queue,
+			struct usb_request, list);
+		if (smd_write_avail(smd_dev->smd_data.ch) < req->actual) {
+			atomic_set(&smd_dev->smd_data.rx_pkt, req->actual);
+			DBG(cdev, "rmnet SMD data channel full\n");
+			break;
+		}
+
+		list_del(&req->list);
+		spin_unlock_irqrestore(&dev->lock, flags);
+		ret = smd_write(smd_dev->smd_data.ch, req->buf, req->actual);
+		spin_lock_irqsave(&dev->lock, flags);
+		if (ret != req->actual) {
+			ERROR(cdev, "rmnet SMD data write failed\n");
+			break;
+		}
+		dev->dpkts_tomsm++;
+		list_add_tail(&req->list, &smd_dev->rx_idle);
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	/* We have free rx data requests. */
+	rmnet_smd_start_rx(dev);
+}
+
+/* If SMD has enough room to accommodate a data rx packet,
+ * write into SMD directly. Otherwise enqueue to rx_queue.
+ * We will not write into SMD directly untill rx_queue is
+ * empty to strictly follow the ordering requests.
+ */
+static void
+rmnet_smd_complete_epout(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev *dev = req->context;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	int status = req->status;
+	int ret;
+
+	if (dev->xport == USB_RMNET_XPORT_UNDEFINED) {
+		rmnet_free_req(ep, req);
+		return;
+	}
+
+	switch (status) {
+	case 0:
+		/* normal completion */
+		break;
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		spin_lock(&dev->lock);
+		list_add_tail(&req->list, &smd_dev->rx_idle);
+		spin_unlock(&dev->lock);
+		return;
+	default:
+		/* unexpected failure */
+		ERROR(cdev, "RMNET %s response error %d, %d/%d\n",
+			ep->name, status,
+			req->actual, req->length);
+		spin_lock(&dev->lock);
+		list_add_tail(&req->list, &smd_dev->rx_idle);
+		spin_unlock(&dev->lock);
+		return;
+	}
+
+	spin_lock(&dev->lock);
+	if (!atomic_read(&smd_dev->smd_data.rx_pkt)) {
+		if (smd_write_avail(smd_dev->smd_data.ch) < req->actual) {
+			atomic_set(&smd_dev->smd_data.rx_pkt, req->actual);
+			goto queue_req;
+		}
+		spin_unlock(&dev->lock);
+		ret = smd_write(smd_dev->smd_data.ch, req->buf, req->actual);
+		/* This should never happen */
+		if (ret != req->actual)
+			ERROR(cdev, "rmnet data smd write failed\n");
+		/* Restart Rx */
+		dev->dpkts_tomsm++;
+		spin_lock(&dev->lock);
+		list_add_tail(&req->list, &smd_dev->rx_idle);
+		spin_unlock(&dev->lock);
+		rmnet_smd_start_rx(dev);
+		return;
+	}
+queue_req:
+	list_add_tail(&req->list, &smd_dev->rx_queue);
+	spin_unlock(&dev->lock);
+}
+
+static void rmnet_smd_complete_epin(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev *dev = req->context;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	int status = req->status;
+	int schedule = 0;
+
+	if (dev->xport == USB_RMNET_XPORT_UNDEFINED) {
+		rmnet_free_req(ep, req);
+		return;
+	}
+
+	switch (status) {
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		spin_lock(&dev->lock);
+		list_add_tail(&req->list, &smd_dev->tx_idle);
+		spin_unlock(&dev->lock);
+		break;
+	default:
+		ERROR(cdev, "rmnet data tx ep error %d\n", status);
+		/* FALLTHROUGH */
+	case 0:
+		spin_lock(&dev->lock);
+		if (list_empty(&smd_dev->tx_idle))
+			schedule = 1;
+		list_add_tail(&req->list, &smd_dev->tx_idle);
+
+		if (schedule)
+			tasklet_schedule(&smd_dev->smd_data.tx_tlet);
+		spin_unlock(&dev->lock);
+		break;
+	}
+
+}
+
+
+static void rmnet_smd_notify(void *priv, unsigned event)
+{
+	struct rmnet_dev *dev = priv;
+	struct rmnet_smd_info *smd_info = &dev->smd_dev.smd_data;
+	int len = atomic_read(&smd_info->rx_pkt);
+
+	switch (event) {
+	case SMD_EVENT_DATA: {
+		if (!atomic_read(&dev->online))
+			break;
+		if (len && (smd_write_avail(smd_info->ch) >= len))
+			tasklet_schedule(&smd_info->rx_tlet);
+
+		if (smd_read_avail(smd_info->ch))
+			tasklet_schedule(&smd_info->tx_tlet);
+
+		break;
+	}
+	case SMD_EVENT_OPEN:
+		/* usb endpoints are not enabled untill smd channels
+		 * are opened. wake up worker thread to continue
+		 * connection processing
+		 */
+		set_bit(CH_OPENED, &smd_info->flags);
+		wake_up(&smd_info->wait);
+		break;
+	case SMD_EVENT_CLOSE:
+		/* We will never come here.
+		 * reset flags after closing smd channel
+		 * */
+		clear_bit(CH_OPENED, &smd_info->flags);
+		break;
+	}
+}
+
+static int rmnet_smd_enable(struct rmnet_dev *dev)
+{
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	int i, ret;
+	struct usb_request *req;
+
+	if (test_bit(CH_OPENED, &smd_dev->smd_data.flags))
+		goto smd_alloc_req;
+
+	ret = smd_open(rmnet_smd_data_ch, &smd_dev->smd_data.ch,
+			dev, rmnet_smd_notify);
+	if (ret) {
+		ERROR(cdev, "Unable to open data smd channel\n");
+		return ret;
+	}
+
+	wait_event(smd_dev->smd_data.wait, test_bit(CH_OPENED,
+				&smd_dev->smd_data.flags));
+
+	/* Allocate bulk in/out requests for data transfer.
+	 * If the memory allocation fails, all the allocated
+	 * requests will be freed upon cable disconnect.
+	 */
+smd_alloc_req:
+	for (i = 0; i < RMNET_SMD_RX_REQ_MAX; i++) {
+		req = rmnet_alloc_req(dev->epout, RMNET_SMD_RX_REQ_SIZE,
+				GFP_KERNEL);
+		if (IS_ERR(req))
+			return PTR_ERR(req);
+		req->length = RMNET_SMD_TXN_MAX;
+		req->context = dev;
+		req->complete = rmnet_smd_complete_epout;
+		list_add_tail(&req->list, &smd_dev->rx_idle);
+	}
+
+	for (i = 0; i < RMNET_SMD_TX_REQ_MAX; i++) {
+		req = rmnet_alloc_req(dev->epin, RMNET_SMD_TX_REQ_SIZE,
+				GFP_KERNEL);
+		if (IS_ERR(req))
+			return PTR_ERR(req);
+		req->context = dev;
+		req->complete = rmnet_smd_complete_epin;
+		list_add_tail(&req->list, &smd_dev->tx_idle);
+	}
+
+	rmnet_smd_start_rx(dev);
+	return 0;
+}
+
+static void rmnet_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev *dev = req->context;
+	struct usb_composite_dev *cdev = dev->cdev;
+	int status = req->status;
+
+	switch (status) {
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		/* connection gone */
+		atomic_set(&dev->notify_count, 0);
+		break;
+	default:
+		ERROR(cdev, "rmnet notifyep error %d\n", status);
+		/* FALLTHROUGH */
+	case 0:
+
+		if (atomic_dec_and_test(&dev->notify_count))
+			break;
+
+		status = usb_ep_queue(dev->epnotify, req, GFP_ATOMIC);
+		if (status) {
+			atomic_dec(&dev->notify_count);
+			ERROR(cdev, "rmnet notify ep enq error %d\n", status);
+		}
+		break;
+	}
+}
+
+static void ctrl_response_available(struct rmnet_dev *dev)
+{
+	struct usb_composite_dev *cdev = dev->cdev;
+	struct usb_request              *req = dev->notify_req;
+	struct usb_cdc_notification     *event = req->buf;
+	int status;
+
+	/* Response will be sent later */
+	if (atomic_inc_return(&dev->notify_count) != 1)
+		return;
+
+	event->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
+			| USB_RECIP_INTERFACE;
+	event->bNotificationType = USB_CDC_NOTIFY_RESPONSE_AVAILABLE;
+	event->wValue = cpu_to_le16(0);
+	event->wIndex = cpu_to_le16(dev->ifc_id);
+	event->wLength = cpu_to_le16(0);
+
+	status = usb_ep_queue(dev->epnotify, dev->notify_req, GFP_ATOMIC);
+	if (status < 0) {
+		atomic_dec(&dev->notify_count);
+		ERROR(cdev, "rmnet notify ep enqueue error %d\n", status);
+	}
+}
+
+#define MAX_CTRL_PKT_SIZE	4096
+
+static void rmnet_response_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev *dev = req->context;
+	struct usb_composite_dev *cdev = dev->cdev;
+
+	switch (req->status) {
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+	case 0:
+		return;
+	default:
+		INFO(cdev, "rmnet %s response error %d, %d/%d\n",
+			ep->name, req->status,
+			req->actual, req->length);
+	}
+}
+
+static void rmnet_command_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct rmnet_dev		*dev = req->context;
+	struct usb_composite_dev	*cdev = dev->cdev;
+	struct rmnet_ctrl_dev		*ctrl_dev = &dev->ctrl_dev;
+	struct rmnet_ctrl_pkt		*cpkt;
+	int				len = req->actual;
+
+	if (req->status < 0) {
+		ERROR(cdev, "rmnet command error %d\n", req->status);
+		return;
+	}
+
+	cpkt = rmnet_alloc_ctrl_pkt(len, GFP_ATOMIC);
+	if (!cpkt) {
+		ERROR(cdev, "unable to allocate memory for ctrl req\n");
+		return;
+	}
+
+	spin_lock(&dev->lock);
+	if (!ctrl_dev->opened) {
+		spin_unlock(&dev->lock);
+		kfree(cpkt);
+		dev->cpkts_drp_cnt++;
+		pr_err_ratelimited(
+			"%s: ctrl pkts dropped: cpkts_drp_cnt: %lu\n",
+			__func__, dev->cpkts_drp_cnt);
+		return;
+	}
+
+	memcpy(cpkt->buf, req->buf, len);
+
+	list_add_tail(&cpkt->list, &ctrl_dev->tx_q);
+	ctrl_dev->tx_len++;
+	spin_unlock(&dev->lock);
+
+	/* wakeup read thread */
+	wake_up(&ctrl_dev->tx_wait_q);
+}
+
+static int
+rmnet_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	struct usb_composite_dev *cdev = f->config->cdev;
+	struct usb_request      *req = cdev->req;
+	int                     ret = -EOPNOTSUPP;
+	u16                     w_index = le16_to_cpu(ctrl->wIndex);
+	u16                     w_value = le16_to_cpu(ctrl->wValue);
+	u16                     w_length = le16_to_cpu(ctrl->wLength);
+	struct rmnet_ctrl_pkt	*cpkt;
+
+	if (!atomic_read(&dev->online))
+		return -ENOTCONN;
+
+	switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_SEND_ENCAPSULATED_COMMAND:
+		if (w_length > req->length)
+			goto invalid;
+		ret = w_length;
+		req->complete = rmnet_command_complete;
+		req->context = dev;
+		break;
+
+
+	case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_GET_ENCAPSULATED_RESPONSE:
+		if (w_value)
+			goto invalid;
+		else {
+			unsigned len;
+
+			spin_lock(&dev->lock);
+			if (list_empty(&ctrl_dev->rx_q)) {
+				DBG(cdev, "ctrl resp queue empty"
+					" %02x.%02x v%04x i%04x l%d\n",
+					ctrl->bRequestType, ctrl->bRequest,
+					w_value, w_index, w_length);
+				spin_unlock(&dev->lock);
+				goto invalid;
+
+			}
+			cpkt = list_first_entry(&ctrl_dev->rx_q,
+					struct rmnet_ctrl_pkt, list);
+			list_del(&cpkt->list);
+			ctrl_dev->rx_len--;
+			spin_unlock(&dev->lock);
+
+			len = min_t(unsigned, w_length, cpkt->len);
+			memcpy(req->buf, cpkt->buf, len);
+			ret = len;
+			req->complete = rmnet_response_complete;
+			req->context = dev;
+			rmnet_free_ctrl_pkt(cpkt);
+
+			dev->cpkts_tolaptop++;
+		}
+		break;
+	case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+			| USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+		/* This is a workaround for RmNet and is borrowed from the
+		 * CDC/ACM standard. The host driver will issue the above ACM
+		 * standard request to the RmNet interface in the following
+		 * scenario: Once the network adapter is disabled from device
+		 * manager, the above request will be sent from the qcusbnet
+		 * host driver, with DTR being '0'. Once network adapter is
+		 * enabled from device manager (or during enumeration), the
+		 * request will be sent with DTR being '1'.
+		 */
+		if (w_value & ACM_CTRL_DTR)
+			ctrl_dev->cbits_to_modem |= TIOCM_DTR;
+		else
+			ctrl_dev->cbits_to_modem &= ~TIOCM_DTR;
+
+		ret = 0;
+
+		break;
+	default:
+
+invalid:
+	DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+		ctrl->bRequestType, ctrl->bRequest,
+		w_value, w_index, w_length);
+	}
+
+	/* respond with data transfer or status phase? */
+	if (ret >= 0) {
+		VDBG(cdev, "rmnet req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, w_length);
+		req->zero = (ret < w_length);
+		req->length = ret;
+		ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+		if (ret < 0)
+			ERROR(cdev, "rmnet ep0 enqueue err %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void rmnet_free_buf(struct rmnet_dev *dev)
+{
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct rmnet_ctrl_pkt *cpkt;
+	struct usb_request *req;
+	struct list_head *pool;
+	struct sk_buff *skb;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	/* free all usb requests in SDIO tx pool */
+	pool = &sdio_dev->tx_idle;
+	while (!list_empty(pool)) {
+		req = list_first_entry(pool, struct usb_request, list);
+		list_del(&req->list);
+		req->buf = NULL;
+		rmnet_free_req(dev->epout, req);
+	}
+
+	pool = &sdio_dev->rx_idle;
+	/* free all usb requests in SDIO rx pool */
+	while (!list_empty(pool)) {
+		req = list_first_entry(pool, struct usb_request, list);
+		list_del(&req->list);
+		req->buf = NULL;
+		rmnet_free_req(dev->epin, req);
+	}
+
+	while ((skb = __skb_dequeue(&sdio_dev->tx_skb_queue)))
+		dev_kfree_skb_any(skb);
+
+	while ((skb = __skb_dequeue(&sdio_dev->rx_skb_queue)))
+		dev_kfree_skb_any(skb);
+
+	/* free all usb requests in SMD tx pool */
+	pool = &smd_dev->tx_idle;
+	while (!list_empty(pool)) {
+		req = list_first_entry(pool, struct usb_request, list);
+		list_del(&req->list);
+		rmnet_free_req(dev->epout, req);
+	}
+
+	pool = &smd_dev->rx_idle;
+	/* free all usb requests in SMD rx pool */
+	while (!list_empty(pool)) {
+		req = list_first_entry(pool, struct usb_request, list);
+		list_del(&req->list);
+		rmnet_free_req(dev->epin, req);
+	}
+
+	/* free all usb requests in SMD rx queue */
+	pool = &smd_dev->rx_queue;
+	while (!list_empty(pool)) {
+		req = list_first_entry(pool, struct usb_request, list);
+		list_del(&req->list);
+		rmnet_free_req(dev->epin, req);
+	}
+
+	pool = &ctrl_dev->tx_q;
+	while (!list_empty(pool)) {
+		cpkt = list_first_entry(pool, struct rmnet_ctrl_pkt, list);
+		list_del(&cpkt->list);
+		rmnet_free_ctrl_pkt(cpkt);
+		ctrl_dev->tx_len--;
+	}
+
+	pool = &ctrl_dev->rx_q;
+	while (!list_empty(pool)) {
+		cpkt = list_first_entry(pool, struct rmnet_ctrl_pkt, list);
+		list_del(&cpkt->list);
+		rmnet_free_ctrl_pkt(cpkt);
+		ctrl_dev->rx_len--;
+	}
+	spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static void rmnet_disconnect_work(struct work_struct *w)
+{
+	struct rmnet_dev *dev = container_of(w, struct rmnet_dev,
+			disconnect_work);
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+
+	if (dev->xport == USB_RMNET_XPORT_SMD) {
+		tasklet_kill(&smd_dev->smd_data.rx_tlet);
+		tasklet_kill(&smd_dev->smd_data.tx_tlet);
+	}
+
+	rmnet_free_buf(dev);
+	dev->xport = 0;
+
+	/* wakeup read thread */
+	wake_up(&ctrl_dev->tx_wait_q);
+}
+
+static void rmnet_suspend(struct usb_function *f)
+{
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+
+	if (!atomic_read(&dev->online))
+		return;
+	/* This is a workaround for Windows Host bug during suspend.
+	 * Windows 7/xp Hosts are suppose to drop DTR, when Host suspended.
+	 * Since it is not being done, Hence exclusively dropping the DTR
+	 * from function driver suspend.
+	 */
+	ctrl_dev->cbits_to_modem &= ~TIOCM_DTR;
+}
+
+static void rmnet_disable(struct usb_function *f)
+{
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+
+	if (!atomic_read(&dev->online))
+		return;
+
+	atomic_set(&dev->online, 0);
+
+	usb_ep_fifo_flush(dev->epnotify);
+	usb_ep_disable(dev->epnotify);
+	rmnet_free_req(dev->epnotify, dev->notify_req);
+
+	usb_ep_fifo_flush(dev->epout);
+	usb_ep_disable(dev->epout);
+
+	usb_ep_fifo_flush(dev->epin);
+	usb_ep_disable(dev->epin);
+
+	/* cleanup work */
+	ctrl_dev->cbits_to_modem = 0;
+	queue_work(dev->wq, &dev->disconnect_work);
+}
+
+#define SDIO_OPEN_RETRY_DELAY	msecs_to_jiffies(2000)
+#define SDIO_OPEN_MAX_RETRY	90
+static void rmnet_open_sdio_work(struct work_struct *w)
+{
+	struct rmnet_dev *dev =
+		container_of(w, struct rmnet_dev, sdio_dev.open_work.work);
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+	int ret;
+	static int retry_cnt;
+
+	/* Data channel for network packets */
+	ret = msm_sdio_dmux_open(rmnet_sdio_data_ch, dev,
+				rmnet_sdio_data_receive_cb,
+				rmnet_sdio_data_write_done);
+	if (ret) {
+		if (retry_cnt > SDIO_OPEN_MAX_RETRY) {
+			ERROR(cdev, "Unable to open SDIO DATA channel\n");
+			return;
+		}
+		retry_cnt++;
+		queue_delayed_work(dev->wq, &sdio_dev->open_work,
+					SDIO_OPEN_RETRY_DELAY);
+		return;
+	}
+
+
+	atomic_set(&sdio_dev->sdio_open, 1);
+	pr_info("%s: usb rmnet sdio channels are open retry_cnt:%d\n",
+				__func__, retry_cnt);
+	retry_cnt = 0;
+	return;
+}
+
+static int rmnet_set_alt(struct usb_function *f,
+			unsigned intf, unsigned alt)
+{
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct usb_composite_dev *cdev = dev->cdev;
+
+	/* allocate notification */
+	dev->notify_req = rmnet_alloc_req(dev->epnotify,
+				RMNET_SDIO_MAX_NFY_SZE, GFP_ATOMIC);
+
+	if (IS_ERR(dev->notify_req))
+		return PTR_ERR(dev->notify_req);
+
+	dev->notify_req->complete = rmnet_notify_complete;
+	dev->notify_req->context = dev;
+	dev->notify_req->length = RMNET_SDIO_MAX_NFY_SZE;
+	usb_ep_enable(dev->epnotify, ep_choose(cdev->gadget,
+				&rmnet_hs_notify_desc,
+				&rmnet_fs_notify_desc));
+
+	dev->epin->driver_data = dev;
+	usb_ep_enable(dev->epin, ep_choose(cdev->gadget,
+				&rmnet_hs_in_desc,
+				&rmnet_fs_in_desc));
+	dev->epout->driver_data = dev;
+	usb_ep_enable(dev->epout, ep_choose(cdev->gadget,
+				&rmnet_hs_out_desc,
+				&rmnet_fs_out_desc));
+
+	dev->dpkts_tolaptop = 0;
+	dev->cpkts_tolaptop = 0;
+	dev->cpkts_tomdm = 0;
+	dev->dpkts_tomdm = 0;
+	dev->dpkts_tomsm = 0;
+	dev->tx_drp_cnt = 0;
+	dev->cpkts_drp_cnt = 0;
+	sdio_dev->dpkts_pending_atdmux = 0;
+	atomic_set(&dev->online, 1);
+
+	return 0;
+}
+
+static ssize_t transport_store(
+		struct device *device, struct device_attribute *attr,
+		const char *buf, size_t size)
+{
+	struct usb_function *f = dev_get_drvdata(device);
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	int value;
+	enum usb_rmnet_xport_type given_xport;
+	enum usb_rmnet_xport_type t;
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct list_head *pool;
+	struct sk_buff_head *skb_pool;
+	struct sk_buff *skb;
+	struct usb_request *req;
+	unsigned long flags;
+
+	if (!atomic_read(&dev->online)) {
+		pr_err("%s: usb cable is not connected\n", __func__);
+		return -EINVAL;
+	}
+
+	sscanf(buf, "%d", &value);
+	if (value)
+		given_xport = USB_RMNET_XPORT_SDIO;
+	else
+		given_xport = USB_RMNET_XPORT_SMD;
+
+	if (given_xport == dev->xport) {
+		pr_err("%s: given_xport:%s cur_xport:%s doing nothing\n",
+				__func__, xport_to_str(given_xport),
+				xport_to_str(dev->xport));
+		return 0;
+	}
+
+	pr_debug("usb_rmnet: TransportRequested: %s\n",
+			xport_to_str(given_xport));
+
+	/* prevent any other pkts to/from usb  */
+	t = dev->xport;
+	dev->xport = USB_RMNET_XPORT_UNDEFINED;
+	if (t != USB_RMNET_XPORT_UNDEFINED) {
+		usb_ep_fifo_flush(dev->epin);
+		usb_ep_fifo_flush(dev->epout);
+	}
+
+	switch (t) {
+	case USB_RMNET_XPORT_SDIO:
+		spin_lock_irqsave(&dev->lock, flags);
+		/* tx_idle */
+
+		sdio_dev->dpkts_pending_atdmux = 0;
+
+		pool = &sdio_dev->tx_idle;
+		while (!list_empty(pool)) {
+			req = list_first_entry(pool, struct usb_request, list);
+			list_del(&req->list);
+			req->buf = NULL;
+			rmnet_free_req(dev->epout, req);
+		}
+
+		/* rx_idle */
+		pool = &sdio_dev->rx_idle;
+		/* free all usb requests in SDIO rx pool */
+		while (!list_empty(pool)) {
+			req = list_first_entry(pool, struct usb_request, list);
+			list_del(&req->list);
+			req->buf = NULL;
+			rmnet_free_req(dev->epin, req);
+		}
+
+		/* tx_skb_queue */
+		skb_pool = &sdio_dev->tx_skb_queue;
+		while ((skb = __skb_dequeue(skb_pool)))
+			dev_kfree_skb_any(skb);
+		/* rx_skb_queue */
+		skb_pool = &sdio_dev->rx_skb_queue;
+		while ((skb = __skb_dequeue(skb_pool)))
+			dev_kfree_skb_any(skb);
+
+		spin_unlock_irqrestore(&dev->lock, flags);
+		break;
+	case USB_RMNET_XPORT_SMD:
+		/* close smd xport */
+		tasklet_kill(&smd_dev->smd_data.rx_tlet);
+		tasklet_kill(&smd_dev->smd_data.tx_tlet);
+
+		spin_lock_irqsave(&dev->lock, flags);
+		/* free all usb requests in SMD tx pool */
+		pool = &smd_dev->tx_idle;
+		while (!list_empty(pool)) {
+			req = list_first_entry(pool, struct usb_request, list);
+			list_del(&req->list);
+			rmnet_free_req(dev->epout, req);
+		}
+
+		pool = &smd_dev->rx_idle;
+		/* free all usb requests in SMD rx pool */
+		while (!list_empty(pool)) {
+			req = list_first_entry(pool, struct usb_request, list);
+			list_del(&req->list);
+			rmnet_free_req(dev->epin, req);
+		}
+
+		/* free all usb requests in SMD rx queue */
+		pool = &smd_dev->rx_queue;
+		while (!list_empty(pool)) {
+			req = list_first_entry(pool, struct usb_request, list);
+			list_del(&req->list);
+			rmnet_free_req(dev->epin, req);
+		}
+
+		spin_unlock_irqrestore(&dev->lock, flags);
+		break;
+	default:
+		pr_debug("%s: undefined xport, do nothing\n", __func__);
+	}
+
+	dev->xport = given_xport;
+
+	switch (dev->xport) {
+	case USB_RMNET_XPORT_SDIO:
+		rmnet_sdio_enable(dev);
+		break;
+	case USB_RMNET_XPORT_SMD:
+		rmnet_smd_enable(dev);
+		break;
+	default:
+		/* we should never come here */
+		pr_err("%s: undefined transport\n", __func__);
+	}
+
+	return size;
+}
+static DEVICE_ATTR(transport, S_IRUGO | S_IWUSR, NULL, transport_store);
+
+static int rmnet_bind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct usb_composite_dev *cdev = c->cdev;
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	int id, ret;
+	struct usb_ep *ep;
+
+	dev->cdev = cdev;
+
+	/* allocate interface ID */
+	id = usb_interface_id(c, f);
+	if (id < 0)
+		return id;
+	dev->ifc_id = id;
+	rmnet_interface_desc.bInterfaceNumber = id;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_in_desc);
+	if (!ep)
+		goto out;
+	ep->driver_data = cdev; /* claim endpoint */
+	dev->epin = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_out_desc);
+	if (!ep)
+		goto out;
+	ep->driver_data = cdev; /* claim endpoint */
+	dev->epout = ep;
+
+	ep = usb_ep_autoconfig(cdev->gadget, &rmnet_fs_notify_desc);
+	if (!ep)
+		goto out;
+	ep->driver_data = cdev; /* claim endpoint */
+	dev->epnotify = ep;
+
+	/* support all relevant hardware speeds... we expect that when
+	 * hardware is dual speed, all bulk-capable endpoints work at
+	 * both speeds
+	 */
+	if (gadget_is_dualspeed(c->cdev->gadget)) {
+		rmnet_hs_in_desc.bEndpointAddress =
+			rmnet_fs_in_desc.bEndpointAddress;
+		rmnet_hs_out_desc.bEndpointAddress =
+			rmnet_fs_out_desc.bEndpointAddress;
+		rmnet_hs_notify_desc.bEndpointAddress =
+			rmnet_fs_notify_desc.bEndpointAddress;
+	}
+
+	ret = device_create_file(f->dev, &dev_attr_transport);
+	if (ret)
+		goto out;
+
+	queue_delayed_work(dev->wq, &sdio_dev->open_work, 0);
+
+	return 0;
+
+out:
+	if (dev->epnotify)
+		dev->epnotify->driver_data = NULL;
+	if (dev->epout)
+		dev->epout->driver_data = NULL;
+	if (dev->epin)
+		dev->epin->driver_data = NULL;
+
+	return -ENODEV;
+}
+
+static void rmnet_smd_init(struct rmnet_smd_dev *smd_dev)
+{
+	struct rmnet_dev *dev = container_of(smd_dev,
+			struct rmnet_dev, smd_dev);
+
+	atomic_set(&smd_dev->smd_data.rx_pkt, 0);
+	tasklet_init(&smd_dev->smd_data.rx_tlet, rmnet_smd_data_rx_tlet,
+					(unsigned long) dev);
+	tasklet_init(&smd_dev->smd_data.tx_tlet, rmnet_smd_data_tx_tlet,
+					(unsigned long) dev);
+
+	init_waitqueue_head(&smd_dev->smd_data.wait);
+
+	INIT_LIST_HEAD(&smd_dev->rx_idle);
+	INIT_LIST_HEAD(&smd_dev->rx_queue);
+	INIT_LIST_HEAD(&smd_dev->tx_idle);
+}
+
+static void rmnet_sdio_init(struct rmnet_sdio_dev *sdio_dev)
+{
+	INIT_WORK(&sdio_dev->data_rx_work, rmnet_sdio_data_rx_work);
+
+	INIT_DELAYED_WORK(&sdio_dev->open_work, rmnet_open_sdio_work);
+
+	INIT_LIST_HEAD(&sdio_dev->rx_idle);
+	INIT_LIST_HEAD(&sdio_dev->tx_idle);
+	skb_queue_head_init(&sdio_dev->tx_skb_queue);
+	skb_queue_head_init(&sdio_dev->rx_skb_queue);
+}
+
+static void
+rmnet_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+	struct rmnet_dev *dev = container_of(f, struct rmnet_dev, function);
+	struct rmnet_smd_dev *smd_dev = &dev->smd_dev;
+
+	smd_close(smd_dev->smd_data.ch);
+	smd_dev->smd_data.flags = 0;
+
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE	1024
+static ssize_t debug_read_stats(struct file *file, char __user *ubuf,
+		size_t count, loff_t *ppos)
+{
+	struct rmnet_dev *dev = file->private_data;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	char *debug_buf;
+	unsigned long flags;
+	int ret;
+
+	debug_buf = kmalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+	if (!debug_buf)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	ret = scnprintf(debug_buf, DEBUG_BUF_SIZE,
+			"dpkts_tomsm:  %lu\n"
+			"dpkts_tomdm: %lu\n"
+			"cpkts_tomdm: %lu\n"
+			"dpkts_tolaptop: %lu\n"
+			"cpkts_tolaptop:  %lu\n"
+			"cbits_to_modem: %lu\n"
+			"tx skb size:     %u\n"
+			"rx_skb_size:     %u\n"
+			"dpkts_pending_at_dmux: %u\n"
+			"tx drp cnt: %lu\n"
+			"cpkts_drp_cnt: %lu\n"
+			"cpkt_tx_qlen: %lu\n"
+			"cpkt_rx_qlen_to_modem: %lu\n"
+			"xport: %s\n"
+			"ctr_ch_opened:	%d\n",
+			dev->dpkts_tomsm, dev->dpkts_tomdm,
+			dev->cpkts_tomdm, dev->dpkts_tolaptop,
+			dev->cpkts_tolaptop, ctrl_dev->cbits_to_modem,
+			sdio_dev->tx_skb_queue.qlen,
+			sdio_dev->rx_skb_queue.qlen,
+			sdio_dev->dpkts_pending_atdmux, dev->tx_drp_cnt,
+			dev->cpkts_drp_cnt,
+			ctrl_dev->tx_len, ctrl_dev->rx_len,
+			xport_to_str(dev->xport), ctrl_dev->opened);
+
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	ret = simple_read_from_buffer(ubuf, count, ppos, debug_buf, ret);
+
+	kfree(debug_buf);
+
+	return ret;
+}
+
+static ssize_t debug_reset_stats(struct file *file, const char __user *buf,
+				 size_t count, loff_t *ppos)
+{
+	struct rmnet_dev *dev = file->private_data;
+	struct rmnet_sdio_dev *sdio_dev = &dev->sdio_dev;
+
+	dev->dpkts_tolaptop = 0;
+	dev->cpkts_tolaptop = 0;
+	dev->cpkts_tomdm = 0;
+	dev->dpkts_tomdm = 0;
+	dev->dpkts_tomsm = 0;
+	sdio_dev->dpkts_pending_atdmux = 0;
+	dev->tx_drp_cnt = 0;
+	dev->cpkts_drp_cnt = 0;
+	return count;
+}
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+
+	return 0;
+}
+
+const struct file_operations rmnet_svlte_debug_stats_ops = {
+	.open = debug_open,
+	.read = debug_read_stats,
+	.write = debug_reset_stats,
+};
+
+static void usb_debugfs_init(struct rmnet_dev *dev)
+{
+	struct dentry *dent;
+
+	dent = debugfs_create_dir("usb_rmnet", 0);
+	if (IS_ERR(dent))
+		return;
+
+	debugfs_create_file("status", 0444, dent, dev,
+			&rmnet_svlte_debug_stats_ops);
+}
+#else
+static void usb_debugfs_init(struct rmnet_dev *dev) {}
+#endif
+
+int usb_rmnet_ctrl_open(struct inode *inode, struct file *fp)
+{
+	struct rmnet_dev *dev =  _dev;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (ctrl_dev->opened) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		pr_err("%s: device is already opened\n", __func__);
+		return -EBUSY;
+	}
+
+	ctrl_dev->opened = 1;
+	fp->private_data = dev;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return 0;
+}
+
+
+int usb_rmnet_ctrl_release(struct inode *inode, struct file *fp)
+{
+	struct rmnet_dev *dev = fp->private_data;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->lock, flags);
+	ctrl_dev->opened = 0;
+	fp->private_data = 0;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	return 0;
+}
+
+ssize_t usb_rmnet_ctrl_read(struct file *fp,
+		      char __user *buf,
+		      size_t count,
+		      loff_t *ppos)
+{
+	struct rmnet_dev *dev = fp->private_data;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	struct rmnet_ctrl_pkt *cpkt;
+	unsigned long flags;
+	int ret = 0;
+
+ctrl_read:
+	if (!atomic_read(&dev->online)) {
+		pr_debug("%s: USB cable not connected\n", __func__);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	if (list_empty(&ctrl_dev->tx_q)) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		/* Implement sleep and wakeup here */
+		ret = wait_event_interruptible(ctrl_dev->tx_wait_q,
+					!list_empty(&ctrl_dev->tx_q) ||
+					!atomic_read(&dev->online));
+		if (ret < 0)
+			return ret;
+
+		goto ctrl_read;
+	}
+
+	cpkt = list_first_entry(&ctrl_dev->tx_q, struct rmnet_ctrl_pkt, list);
+	if (cpkt->len > count) {
+		spin_unlock_irqrestore(&dev->lock, flags);
+		pr_err("%s: cpkt size:%d > buf size:%d\n",
+				__func__, cpkt->len, count);
+		return -ENOMEM;
+	}
+	list_del(&cpkt->list);
+	ctrl_dev->tx_len--;
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	count = cpkt->len;
+
+	ret = copy_to_user(buf, cpkt->buf, count);
+	dev->cpkts_tomdm++;
+
+	rmnet_free_ctrl_pkt(cpkt);
+
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+ssize_t usb_rmnet_ctrl_write(struct file *fp,
+		       const char __user *buf,
+		       size_t count,
+		       loff_t *ppos)
+{
+	struct rmnet_dev *dev = fp->private_data;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	struct rmnet_ctrl_pkt *cpkt;
+	unsigned long flags;
+	int ret = 0;
+
+	if (!atomic_read(&dev->online)) {
+		pr_debug("%s: USB cable not connected\n", __func__);
+		return -ENODEV;
+	}
+
+	if (!count) {
+		pr_err("%s: zero length ctrl pkt\n", __func__);
+		return -ENODEV;
+	}
+
+	if (count > MAX_CTRL_PKT_SIZE) {
+		pr_err("%s: max_pkt_size:%d given_pkt_size:%d\n",
+				__func__, MAX_CTRL_PKT_SIZE, count);
+		return -ENOMEM;
+	}
+
+	cpkt = rmnet_alloc_ctrl_pkt(count, GFP_KERNEL);
+	if (!cpkt) {
+		pr_err("%s: cannot allocate rmnet ctrl pkt\n", __func__);
+		return -ENOMEM;
+	}
+
+	ret = copy_from_user(cpkt->buf, buf, count);
+	if (ret) {
+		pr_err("%s: copy_from_user failed err:%d\n",
+				__func__, ret);
+		rmnet_free_ctrl_pkt(cpkt);
+		return ret;
+	}
+
+	spin_lock_irqsave(&dev->lock, flags);
+	ctrl_dev->rx_len++;
+	list_add(&cpkt->list, &ctrl_dev->rx_q);
+	spin_unlock_irqrestore(&dev->lock, flags);
+
+	ctrl_response_available(dev);
+
+	return count;
+}
+
+
+#define RMNET_CTRL_GET_DTR	_IOR(0xFE, 0, int)
+static long
+usb_rmnet_ctrl_ioctl(struct file *fp, unsigned c, unsigned long value)
+{
+	struct rmnet_dev *dev = fp->private_data;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+	unsigned long *temp = (unsigned long *)value;
+	int ret = 0;
+
+	if (c != RMNET_CTRL_GET_DTR)
+		return -ENODEV;
+
+	ret = copy_to_user(temp,
+			&ctrl_dev->cbits_to_modem,
+			sizeof(*temp));
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct file_operations rmnet_ctrl_fops = {
+	.owner		= THIS_MODULE,
+	.open		= usb_rmnet_ctrl_open,
+	.release	= usb_rmnet_ctrl_release,
+	.read		= usb_rmnet_ctrl_read,
+	.write		= usb_rmnet_ctrl_write,
+	.unlocked_ioctl	= usb_rmnet_ctrl_ioctl,
+};
+
+static struct miscdevice rmnet_ctrl_dev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "rmnet_ctrl",
+	.fops = &rmnet_ctrl_fops,
+};
+
+static int rmnet_ctrl_device_init(struct rmnet_dev *dev)
+{
+	int ret;
+	struct rmnet_ctrl_dev *ctrl_dev = &dev->ctrl_dev;
+
+	INIT_LIST_HEAD(&ctrl_dev->tx_q);
+	INIT_LIST_HEAD(&ctrl_dev->rx_q);
+	init_waitqueue_head(&ctrl_dev->tx_wait_q);
+
+	ret = misc_register(&rmnet_ctrl_dev);
+	if (ret) {
+		pr_err("%s: failed to register misc device\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rmnet_function_add(struct usb_configuration *c)
+{
+	struct rmnet_dev *dev;
+	int ret;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	_dev = dev;
+
+	dev->wq = create_singlethread_workqueue("k_rmnet_work");
+	if (!dev->wq) {
+		ret = -ENOMEM;
+		goto free_dev;
+	}
+
+	spin_lock_init(&dev->lock);
+	atomic_set(&dev->notify_count, 0);
+	atomic_set(&dev->online, 0);
+	INIT_WORK(&dev->disconnect_work, rmnet_disconnect_work);
+	rmnet_smd_init(&dev->smd_dev);
+	rmnet_sdio_init(&dev->sdio_dev);
+
+	ret = rmnet_ctrl_device_init(dev);
+	if (ret) {
+		pr_debug("%s: rmnet_ctrl_device_init failed, err:%d\n",
+				__func__, ret);
+		goto free_wq;
+	}
+
+	dev->function.name = "rmnet_smd_sdio";
+	dev->function.strings = rmnet_strings;
+	dev->function.descriptors = rmnet_fs_function;
+	dev->function.hs_descriptors = rmnet_hs_function;
+	dev->function.bind = rmnet_bind;
+	dev->function.unbind = rmnet_unbind;
+	dev->function.setup = rmnet_setup;
+	dev->function.set_alt = rmnet_set_alt;
+	dev->function.disable = rmnet_disable;
+	dev->function.suspend = rmnet_suspend;
+
+	ret = usb_add_function(c, &dev->function);
+	if (ret)
+		goto free_wq;
+
+	usb_debugfs_init(dev);
+
+	return 0;
+
+free_wq:
+	destroy_workqueue(dev->wq);
+free_dev:
+	kfree(dev);
+
+	return ret;
+}
+
+#ifdef CONFIG_USB_ANDROID_RMNET_SMD_SDIO
+static struct android_usb_function rmnet_function = {
+	.name = "rmnet_smd_sdio",
+	.bind_config = rmnet_function_add,
+};
+
+static int __init rmnet_init(void)
+{
+	android_register_function(&rmnet_function);
+	return 0;
+}
+module_init(rmnet_init);
+
+#endif /* CONFIG_USB_ANDROID_RMNET_SDIO */