usb: Add network bridge host driver for dun and rmnet
This host driver will be used to communicate with modem devices
with dial up network and RMNET interfaces. This driver works as
a bridge to pass control and data packets between the modem and
peripheral usb gadget driver. Driver currently supports
modem devices (vendor ID 0x05c6) with PIDs 0x9001
Change-Id: Id85b552b39d061528a1c3c90a354d73580c9b631
Signed-off-by: Hemant Kumar <hemantk@codeaurora.org>
Signed-off-by: Jack Pham <jackp@codeaurora.org>
diff --git a/arch/arm/mach-msm/include/mach/usb_bridge.h b/arch/arm/mach-msm/include/mach/usb_bridge.h
new file mode 100644
index 0000000..2b7e754
--- /dev/null
+++ b/arch/arm/mach-msm/include/mach/usb_bridge.h
@@ -0,0 +1,122 @@
+/* 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.
+ */
+
+
+#ifndef __LINUX_USB_BRIDGE_H__
+#define __LINUX_USB_BRIDGE_H__
+
+#include <linux/netdevice.h>
+#include <linux/usb.h>
+
+/* bridge device 0: DUN
+ * bridge device 1 : Tethered RMNET
+ */
+#define MAX_BRIDGE_DEVICES 2
+
+/*PID 9001*/
+#define DUN_IFACE_NUM 2
+#define TETHERED_RMNET_IFACE_NUM 3
+
+struct bridge_ops {
+ int (*send_pkt)(void *, void *, size_t actual);
+ void (*send_cbits)(void *, unsigned int);
+
+ /* flow control */
+ void (*unthrottle_tx)(void *);
+};
+
+#define TX_THROTTLED BIT(0)
+#define RX_THROTTLED BIT(1)
+
+struct bridge {
+ /* context of the gadget port using bridge driver */
+ void *ctx;
+
+ /* bridge device array index mapped to the gadget port array index.
+ * data bridge[ch_id] <-- bridge --> gadget port[ch_id]
+ */
+ unsigned int ch_id;
+
+ /* flow control bits */
+ unsigned long flags;
+
+ /* data/ctrl bridge callbacks */
+ struct bridge_ops ops;
+};
+
+#if defined(CONFIG_USB_QCOM_MDM_BRIDGE) || \
+ defined(CONFIG_USB_QCOM_MDM_BRIDGE_MODULE)
+
+/* Bridge APIs called by gadget driver */
+int ctrl_bridge_open(struct bridge *);
+void ctrl_bridge_close(unsigned int);
+int ctrl_bridge_write(unsigned int, char *, size_t);
+int ctrl_bridge_set_cbits(unsigned int, unsigned int);
+unsigned int ctrl_bridge_get_cbits_tohost(unsigned int);
+int data_bridge_open(struct bridge *brdg);
+void data_bridge_close(unsigned int);
+int data_bridge_write(unsigned int , struct sk_buff *);
+int data_bridge_unthrottle_rx(unsigned int);
+
+/* defined in control bridge */
+int ctrl_bridge_probe(struct usb_interface *, struct usb_host_endpoint *, int);
+void ctrl_bridge_disconnect(unsigned int);
+int ctrl_bridge_resume(unsigned int);
+int ctrl_bridge_suspend(unsigned int);
+
+#else
+
+static inline int __maybe_unused ctrl_bridge_open(struct bridge *brdg)
+{
+ return -ENODEV;
+}
+
+static inline void __maybe_unused ctrl_bridge_close(unsigned int id) { }
+
+static inline int __maybe_unused ctrl_bridge_write(unsigned int id,
+ char *data, size_t size)
+{
+ return -ENODEV;
+}
+
+static inline int __maybe_unused ctrl_bridge_set_cbits(unsigned int id,
+ unsigned int cbits)
+{
+ return -ENODEV;
+}
+
+static inline unsigned int __maybe_unused
+ctrl_bridge_get_cbits_tohost(unsigned int id)
+{
+ return -ENODEV;
+}
+
+static inline int __maybe_unused data_bridge_open(struct bridge *brdg)
+{
+ return -ENODEV;
+}
+
+static inline void __maybe_unused data_bridge_close(unsigned int id) { }
+
+static inline int __maybe_unused data_bridge_write(unsigned int id,
+ struct sk_buff *skb)
+{
+ return -ENODEV;
+}
+
+static inline int __maybe_unused data_bridge_unthrottle_rx(unsigned int id)
+{
+ return -ENODEV;
+}
+
+#endif
+#endif
diff --git a/arch/arm/mach-msm/include/mach/usb_dun_bridge.h b/arch/arm/mach-msm/include/mach/usb_dun_bridge.h
deleted file mode 100644
index b4a8eef..0000000
--- a/arch/arm/mach-msm/include/mach/usb_dun_bridge.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/* 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.
- */
-
-#ifndef __USB_DUN_BRIDGE_H
-#define __USB_DUN_BRIDGE_H
-
-/**
- * struct dun_bridge_ops - context and callbacks for DUN bridge
- *
- * @ctxt: caller private context
- * @read_complete: called when read is completed. buf and len correspond
- * to original passed-in values. actual length of buffer returned, or
- * negative error value.
- * @write_complete: called when write is completed. buf and len correspond
- * to original passed-in values. actual length of buffer returned, or
- * negative error value.
- * @ctrl_status: asynchronous notification of control status. ctrl_bits
- * is a bitfield of CDC ACM control status bits.
- */
-struct dun_bridge_ops {
- void *ctxt;
- void (*read_complete)(void *ctxt, char *buf, size_t len, size_t actual);
- void (*write_complete)(void *ctxt, char *buf,
- size_t len, size_t actual);
- void (*ctrl_status)(void *ctxt, unsigned int ctrl_bits);
-};
-
-#ifdef CONFIG_USB_QCOM_DUN_BRIDGE
-
-/**
- * dun_bridge_open - Open the DUN bridge
- *
- * @ops: pointer to ops struct containing private context and callback
- * pointers
- */
-int dun_bridge_open(struct dun_bridge_ops *ops);
-
-/**
- * dun_bridge_close - Closes the DUN bridge
- */
-int dun_bridge_close(void);
-
-/**
- * dun_bridge_read - Request to read data from the DUN bridge. This call is
- * asynchronous: user's read callback (ops->read_complete) will be called
- * when data is returned.
- *
- * @data: pointer to caller-allocated buffer to fill in
- * @len: size of the buffer
- */
-int dun_bridge_read(void *data, int len);
-
-/**
- * dun_bridge_write - Request to write data to the DUN bridge. This call is
- * asynchronous: user's write callback (ops->write_complete) will be called
- * upon completion of the write indicating status and number of bytes
- * written.
- *
- * @data: pointer to caller-allocated buffer to write
- * @len: length of the data in buffer
- */
-int dun_bridge_write(void *data, int len);
-
-/**
- * dun_bridge_send_ctrl_bits - Request to write line control data to the DUN
- * bridge. This call is asynchronous, however no callback will be issued
- * upon completion.
- *
- * @ctrl_bits: CDC ACM line control bits
- */
-int dun_bridge_send_ctrl_bits(unsigned ctrl_bits);
-
-#else
-
-#include <linux/errno.h>
-
-static int __maybe_unused dun_bridge_open(struct dun_bridge_ops *ops)
-{
- return -ENODEV;
-}
-
-static int __maybe_unused dun_bridge_close(void)
-{
- return -ENODEV;
-}
-
-static int __maybe_unused dun_bridge_read(void *data, int len)
-{
- return -ENODEV;
-}
-
-static int __maybe_unused dun_bridge_write(void *data, int len)
-{
- return -ENODEV;
-}
-
-static int __maybe_unused dun_bridge_send_ctrl_bits(unsigned ctrl_bits)
-{
- return -ENODEV;
-}
-
-#endif
-
-#endif
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index c5dcdbf..13828e0 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -283,27 +283,13 @@
To compile this driver as a module, choose M here: the
module will be called diag_bridge_test. If unsure, choose N.
-config USB_QCOM_DUN_BRIDGE
- tristate "USB Qualcomm modem DUN bridge driver"
+config USB_QCOM_MDM_BRIDGE
+ tristate "USB Qualcomm modem bridge driver for DUN and RMNET"
depends on USB
help
Say Y here if you have a Qualcomm modem device connected via USB that
- will be bridged in kernel space. This driver will enable bridging
- with the gadget serial driver for use in dial-up networking. This is
- not the same as the qcserial driver that exposes a TTY interface to
- userspace.
-
+ will be bridged in kernel space. This driver works as a bridge to pass
+ control and data packets between the modem and peripheral usb gadget
+ driver for dial up network and RMNET.
To compile this driver as a module, choose M here: the module
- will be called dun_bridge.
-
-config USB_QCOM_DUN_BRIDGE_TEST
- tristate "USB Qualcomm modem DUN bridge driver test"
- depends on USB && USB_QCOM_DUN_BRIDGE && !USB_SERIAL_QUALCOMM
- help
- Say Y here if you want to enable the test hook for the
- Qualcomm modem bridge driver. When enabled, this will create
- a debugfs file entry named "dun_bridge_test" which can be used
- to read and write directly to the modem.
-
- To compile this driver as a module, choose M here: the module
- will be called dun_bridge_test.
+ will be called mdm_bridge. If unsure, choose N.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index bb69a02..b4aee65 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -31,5 +31,5 @@
obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_bridge.o
obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE_TEST) += diag_bridge_test.o
-obj-$(CONFIG_USB_QCOM_DUN_BRIDGE) += dun_bridge.o
-obj-$(CONFIG_USB_QCOM_DUN_BRIDGE_TEST) += dun_bridge_test.o
+mdm_bridge-y := mdm_ctrl_bridge.o mdm_data_bridge.o
+obj-$(CONFIG_USB_QCOM_MDM_BRIDGE) += mdm_bridge.o
diff --git a/drivers/usb/misc/dun_bridge.c b/drivers/usb/misc/dun_bridge.c
deleted file mode 100644
index aca7714..0000000
--- a/drivers/usb/misc/dun_bridge.c
+++ /dev/null
@@ -1,520 +0,0 @@
-/* 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/errno.h>
-#include <linux/init.h>
-#include <linux/slab.h>
-#include <linux/module.h>
-#include <linux/kref.h>
-#include <linux/platform_device.h>
-#include <linux/usb.h>
-#include <linux/usb/cdc.h>
-#include <linux/usb/ch9.h>
-#include <asm/unaligned.h>
-#include <mach/usb_dun_bridge.h>
-
-#define DRIVER_DESC "Qualcomm USB DUN bridge driver"
-#define DRIVER_VERSION "1.0"
-
-struct dun_bridge {
- struct usb_device *udev;
- struct usb_interface *intf;
- struct usb_anchor submitted;
- u8 int_in_epaddr;
- unsigned in, out; /* bulk in/out pipes */
-
- struct urb *inturb;
- struct usb_ctrlrequest cmd;
- u8 *ctrl_buf;
-
- struct kref kref;
- struct platform_device *pdev;
-
- struct dun_bridge_ops *ops;
-};
-
-static struct dun_bridge *__dev;
-
-/* This assumes that __dev has already been initialized by probe(). */
-int dun_bridge_open(struct dun_bridge_ops *ops)
-{
- struct dun_bridge *dev = __dev;
- int ret = 0;
-
- if (!dev) {
- err("%s: dev is null", __func__);
- return -ENODEV;
- }
-
- if (!ops || !ops->read_complete || !ops->write_complete)
- return -EINVAL;
-
- dev->ops = ops;
- if (ops->ctrl_status) {
- ret = usb_submit_urb(dev->inturb, GFP_KERNEL);
- if (ret)
- pr_err("%s: submitting int urb failed: %d\n",
- __func__, ret);
- }
-
- return ret;
-}
-EXPORT_SYMBOL(dun_bridge_open);
-
-int dun_bridge_close(void)
-{
- struct dun_bridge *dev = __dev;
- if (!dev)
- return -ENODEV;
-
- dev_dbg(&dev->udev->dev, "%s:", __func__);
- usb_unlink_anchored_urbs(&dev->submitted);
- usb_unlink_urb(dev->inturb);
- dev->ops = NULL;
-
- return 0;
-}
-EXPORT_SYMBOL(dun_bridge_close);
-
-static void read_cb(struct urb *urb)
-{
- struct dun_bridge *dev = urb->context;
- struct dun_bridge_ops *ops;
-
- if (!dev || !dev->intf) {
- pr_err("%s: device is disconnected\n", __func__);
- kfree(urb->transfer_buffer);
- return;
- }
-
- dev_dbg(&dev->udev->dev, "%s: status:%d actual:%d\n", __func__,
- urb->status, urb->actual_length);
-
- usb_autopm_put_interface(dev->intf);
- ops = dev->ops;
- if (ops)
- ops->read_complete(ops->ctxt,
- urb->transfer_buffer,
- urb->transfer_buffer_length,
- /* callback must check this value for error */
- urb->status < 0 ?
- urb->status : urb->actual_length);
- else {
- /* can't call back, free buffer on caller's behalf */
- dev_err(&dev->udev->dev, "cannot complete read callback\n");
- kfree(urb->transfer_buffer);
- }
-}
-
-int dun_bridge_read(void *data, int len)
-{
- struct dun_bridge *dev = __dev;
- struct urb *urb;
- int ret;
-
- if (!dev || !dev->ops)
- return -ENODEV;
-
- if (!dev->intf) {
- pr_err("%s: device is disconnected\n", __func__);
- return -ENODEV;
- }
-
- if (!len) {
- dev_err(&dev->udev->dev, "%s: invalid len:%d\n", __func__, len);
- return -EINVAL;
- }
-
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb) {
- dev_err(&dev->udev->dev, "%s: Unable to alloc urb\n", __func__);
- return -ENOMEM;
- }
-
- usb_fill_bulk_urb(urb, dev->udev, dev->in,
- data, len, read_cb, dev);
- usb_anchor_urb(urb, &dev->submitted);
-
- usb_autopm_get_interface(dev->intf);
- ret = usb_submit_urb(urb, GFP_KERNEL);
- if (ret) {
- dev_err(&dev->udev->dev, "%s: submit urb err:%d\n",
- __func__, ret);
- usb_unanchor_urb(urb);
- usb_autopm_put_interface(dev->intf);
- }
-
- usb_free_urb(urb);
- return ret;
-}
-EXPORT_SYMBOL(dun_bridge_read);
-
-static void write_cb(struct urb *urb)
-{
- struct dun_bridge *dev = urb->context;
- struct dun_bridge_ops *ops;
-
- if (!dev || !dev->intf) {
- pr_err("%s: device is disconnected\n", __func__);
- kfree(urb->transfer_buffer);
- return;
- }
-
- dev_dbg(&dev->udev->dev, "%s: status:%d actual:%d\n", __func__,
- urb->status, urb->actual_length);
-
- usb_autopm_put_interface(dev->intf);
- ops = dev->ops;
- if (ops)
- ops->write_complete(ops->ctxt,
- urb->transfer_buffer,
- urb->transfer_buffer_length,
- /* callback must check this value for error */
- urb->status < 0 ?
- urb->status : urb->actual_length);
- else {
- /* can't call back, free buffer on caller's behalf */
- dev_err(&dev->udev->dev, "cannot complete write callback\n");
- kfree(urb->transfer_buffer);
- }
-}
-
-int dun_bridge_write(void *data, int len)
-{
- struct dun_bridge *dev = __dev;
- struct urb *urb;
- int ret;
-
- if (!dev || !dev->ops)
- return -ENODEV;
-
- if (!dev->intf) {
- pr_err("%s: device is disconnected\n", __func__);
- return -ENODEV;
- }
-
- if (!len) {
- dev_err(&dev->udev->dev, "%s: invalid len:%d\n", __func__, len);
- return -EINVAL;
- }
-
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb) {
- dev_err(&dev->udev->dev, "%s: Unable to alloc urb\n", __func__);
- return -ENOMEM;
- }
-
- usb_fill_bulk_urb(urb, dev->udev, dev->out,
- data, len, write_cb, dev);
- usb_anchor_urb(urb, &dev->submitted);
-
- usb_autopm_get_interface(dev->intf);
- ret = usb_submit_urb(urb, GFP_KERNEL);
- if (ret) {
- dev_err(&dev->udev->dev, "%s: submit urb err:%d\n",
- __func__, ret);
- usb_unanchor_urb(urb);
- usb_autopm_put_interface(dev->intf);
- }
-
- usb_free_urb(urb);
- return ret;
-}
-EXPORT_SYMBOL(dun_bridge_write);
-
-static void ctrl_cb(struct urb *urb)
-{
- struct dun_bridge *dev = urb->context;
- usb_autopm_put_interface(dev->intf);
-}
-
-int dun_bridge_send_ctrl_bits(unsigned ctrl_bits)
-{
- struct dun_bridge *dev = __dev;
- struct urb *urb = NULL;
- int ret;
-
- if (!dev || !dev->intf) {
- pr_err("%s: device is disconnected\n", __func__);
- return -ENODEV;
- }
-
- dev_dbg(&dev->udev->dev, "%s: %#x", __func__, ctrl_bits);
-
- dev->cmd.bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
- dev->cmd.bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE;
- dev->cmd.wValue = cpu_to_le16(ctrl_bits);
- dev->cmd.wIndex = cpu_to_le16(dev->int_in_epaddr);
- dev->cmd.wLength = 0;
-
- urb = usb_alloc_urb(0, GFP_ATOMIC);
- if (!urb) {
- dev_err(&dev->udev->dev, "%s: Unable to alloc urb\n", __func__);
- return -ENOMEM;
- }
-
- usb_fill_control_urb(urb, dev->udev, usb_sndctrlpipe(dev->udev, 0),
- (unsigned char *)&dev->cmd, NULL, 0,
- ctrl_cb, dev);
-
- usb_autopm_get_interface(dev->intf);
- ret = usb_submit_urb(urb, GFP_ATOMIC);
- if (ret) {
- dev_err(&dev->udev->dev, "%s: submit urb err:%d\n",
- __func__, ret);
- usb_autopm_put_interface(dev->intf);
- }
-
- usb_free_urb(urb);
- return ret;
-}
-EXPORT_SYMBOL(dun_bridge_send_ctrl_bits);
-
-static void int_cb(struct urb *urb)
-{
- struct dun_bridge *dev = urb->context;
- struct usb_cdc_notification *dr = urb->transfer_buffer;
- unsigned char *data;
- unsigned int ctrl_bits;
- int status = urb->status;
-
- if (!dev || !dev->intf) {
- pr_err("%s: device is disconnected\n", __func__);
- return;
- }
-
- switch (status) {
- case 0:
- /* success */
- break;
- case -ECONNRESET:
- case -ENOENT:
- case -ESHUTDOWN:
- /* this urb is terminated, clean up */
- dev_err(&dev->udev->dev,
- "%s - urb shutting down with status: %d\n",
- __func__, status);
- return;
- default:
- dev_err(&dev->udev->dev,
- "%s - nonzero urb status received: %d\n",
- __func__, status);
- goto resubmit_urb;
- }
-
- data = (unsigned char *)(dr + 1);
- switch (dr->bNotificationType) {
- case USB_CDC_NOTIFY_NETWORK_CONNECTION:
- dev_dbg(&dev->udev->dev, "%s network\n", dr->wValue ?
- "connected to" : "disconnected from");
- break;
-
- case USB_CDC_NOTIFY_SERIAL_STATE:
- ctrl_bits = get_unaligned_le16(data);
- dev_dbg(&dev->udev->dev, "serial state: %d\n", ctrl_bits);
- if (dev->ops && dev->ops->ctrl_status)
- dev->ops->ctrl_status(dev->ops->ctxt, ctrl_bits);
- break;
-
- default:
- dev_err(&dev->udev->dev, "unknown notification %d received: "
- "index %d len %d data0 %d data1 %d\n",
- dr->bNotificationType, dr->wIndex,
- dr->wLength, data[0], data[1]);
- break;
- }
-resubmit_urb:
- status = usb_submit_urb(dev->inturb, GFP_ATOMIC);
- if (status)
- dev_err(&dev->udev->dev, "%s: submit urb err:%d\n",
- __func__, status);
-}
-
-static void dun_bridge_delete(struct kref *kref)
-{
- struct dun_bridge *dev = container_of(kref, struct dun_bridge, kref);
-
- __dev = NULL;
- usb_put_dev(dev->udev);
- usb_free_urb(dev->inturb);
- kfree(dev->ctrl_buf);
- kfree(dev);
-}
-
-static int
-dun_bridge_probe(struct usb_interface *intf, const struct usb_device_id *id)
-{
- struct dun_bridge *dev;
- struct usb_host_interface *iface_desc;
- struct usb_endpoint_descriptor *epd;
- __u8 iface_num;
- int i;
- int ctrlsize = 0;
- int ret = -ENOMEM;
-
- iface_desc = intf->cur_altsetting;
- iface_num = iface_desc->desc.bInterfaceNumber;
-
- /* is this interface supported? */
- if (iface_num != id->driver_info)
- return -ENODEV;
-
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev) {
- pr_err("%s: unable to allocate dev\n", __func__);
- goto error;
- }
-
- dev->pdev = platform_device_alloc("dun_bridge", 0);
- if (!dev->pdev) {
- pr_err("%s: unable to allocate platform device\n", __func__);
- kfree(dev);
- return -ENOMEM;
- }
- __dev = dev;
-
- kref_init(&dev->kref);
- dev->udev = usb_get_dev(interface_to_usbdev(intf));
- dev->intf = intf;
-
- init_usb_anchor(&dev->submitted);
- dev->inturb = usb_alloc_urb(0, GFP_KERNEL);
- if (!dev->inturb) {
- ret = -ENOMEM;
- goto error;
- }
-
- for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
- epd = &iface_desc->endpoint[i].desc;
-
- if (usb_endpoint_is_int_in(epd)) {
- dev->int_in_epaddr = epd->bEndpointAddress;
- ctrlsize = le16_to_cpu(epd->wMaxPacketSize);
-
- dev->ctrl_buf = kzalloc(ctrlsize, GFP_KERNEL);
- if (!dev->ctrl_buf) {
- ret = -ENOMEM;
- goto error;
- }
-
- usb_fill_int_urb(dev->inturb, dev->udev,
- usb_rcvintpipe(dev->udev,
- dev->int_in_epaddr),
- dev->ctrl_buf, ctrlsize,
- int_cb, dev, epd->bInterval);
-
- } else if (usb_endpoint_is_bulk_in(epd))
- dev->in = usb_rcvbulkpipe(dev->udev,
- epd->bEndpointAddress &
- USB_ENDPOINT_NUMBER_MASK);
-
- else if (usb_endpoint_is_bulk_out(epd))
- dev->out = usb_sndbulkpipe(dev->udev,
- epd->bEndpointAddress &
- USB_ENDPOINT_NUMBER_MASK);
- }
-
- if (!dev->int_in_epaddr && !dev->in && !dev->out) {
- dev_err(&dev->udev->dev, "%s: could not find all endpoints\n",
- __func__);
- ret = -ENODEV;
- goto error;
- }
-
- usb_set_intfdata(intf, dev);
- platform_device_add(dev->pdev);
- return 0;
-error:
- if (dev)
- kref_put(&dev->kref, dun_bridge_delete);
- return ret;
-}
-
-static void dun_bridge_disconnect(struct usb_interface *intf)
-{
- struct dun_bridge *dev = usb_get_intfdata(intf);
-
- platform_device_del(dev->pdev);
- usb_set_intfdata(intf, NULL);
- dev->intf = NULL;
-
- kref_put(&dev->kref, dun_bridge_delete);
-
- pr_debug("%s: DUN Bridge now disconnected\n", __func__);
-}
-
-static int dun_bridge_suspend(struct usb_interface *intf, pm_message_t message)
-{
- struct dun_bridge *dev = usb_get_intfdata(intf);
-
- dev_dbg(&dev->udev->dev, "%s:", __func__);
- usb_unlink_anchored_urbs(&dev->submitted);
- usb_unlink_urb(dev->inturb);
-
- return 0;
-}
-
-static int dun_bridge_resume(struct usb_interface *intf)
-{
- struct dun_bridge *dev = usb_get_intfdata(intf);
- int ret = 0;
-
- if (dev->ops && dev->ops->ctrl_status) {
- ret = usb_submit_urb(dev->inturb, GFP_KERNEL);
- if (ret)
- dev_err(&dev->udev->dev, "%s: submit int urb err: %d\n",
- __func__, ret);
- }
-
- return ret;
-}
-
-#define VALID_INTERFACE_NUM 2
-static const struct usb_device_id id_table[] = {
- { USB_DEVICE(0x05c6, 0x9001), /* Generic QC Modem device */
- .driver_info = VALID_INTERFACE_NUM },
- { } /* Terminating entry */
-};
-MODULE_DEVICE_TABLE(usb, id_table);
-
-static struct usb_driver dun_bridge_driver = {
- .name = "dun_usb_bridge",
- .probe = dun_bridge_probe,
- .disconnect = dun_bridge_disconnect,
- .id_table = id_table,
- .suspend = dun_bridge_suspend,
- .resume = dun_bridge_resume,
- .supports_autosuspend = true,
-};
-
-static int __init dun_bridge_init(void)
-{
- int ret;
-
- ret = usb_register(&dun_bridge_driver);
- if (ret)
- pr_err("%s: unable to register dun_bridge_driver\n", __func__);
-
- return ret;
-}
-
-static void __exit dun_bridge_exit(void)
-{
- usb_deregister(&dun_bridge_driver);
-}
-
-module_init(dun_bridge_init);
-module_exit(dun_bridge_exit);
-
-MODULE_DESCRIPTION(DRIVER_DESC);
-MODULE_LICENSE("GPL V2");
diff --git a/drivers/usb/misc/dun_bridge_test.c b/drivers/usb/misc/dun_bridge_test.c
deleted file mode 100644
index d545e13..0000000
--- a/drivers/usb/misc/dun_bridge_test.c
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * 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/slab.h>
-#include <linux/kernel.h>
-#include <linux/device.h>
-#include <linux/debugfs.h>
-#include <linux/usb/cdc.h>
-#include <linux/uaccess.h>
-#include <mach/usb_dun_bridge.h>
-
-#define RD_BUF_SIZE 2048
-#define DUN_TEST_CONNECTED 0
-
-
-struct dun_bridge_test_dev {
- char *read_buf;
- size_t buflen;
- struct work_struct read_w;
- unsigned long flags;
-
- struct dun_bridge_ops ops;
-};
-static struct dun_bridge_test_dev *__dev;
-
-static struct dentry *dfile;
-
-static void
-dun_bridge_test_read_complete(void *d, char *buf, size_t size, size_t actual)
-{
- if (actual < 0) {
- pr_err("%s: read complete err\n", __func__);
- return;
- }
-
- __dev->buflen = actual;
- buf[actual] = 0;
-
- pr_info("%s: %s\n", __func__, buf);
-
- if (test_bit(DUN_TEST_CONNECTED, &__dev->flags))
- schedule_work(&__dev->read_w);
-}
-
-static void dun_bridge_test_read_work(struct work_struct *w)
-{
- struct dun_bridge_test_dev *dev =
- container_of(w, struct dun_bridge_test_dev, read_w);
-
- dun_bridge_read(dev->read_buf, RD_BUF_SIZE);
-}
-
-static void
-dun_bridge_test_write_complete(void *d, char *buf, size_t size, size_t actual)
-{
- struct dun_bridge_test_dev *dev = d;
-
- if (actual > 0)
- schedule_work(&dev->read_w);
-
- kfree(buf);
-}
-
-#if defined(CONFIG_DEBUG_FS)
-#define DEBUG_BUF_SIZE 1024
-
-#define ACM_CTRL_DTR 0x01
-#define ACM_CTRL_RTS 0x02
-
-static int debug_open(struct inode *inode, struct file *file)
-{
- struct dun_bridge_test_dev *dev = __dev;
- int ret = 0;
-
- if (!dev)
- return -ENODEV;
-
- if (!test_bit(DUN_TEST_CONNECTED, &dev->flags)) {
- ret = dun_bridge_open(&dev->ops);
- if (ret)
- return ret;
- set_bit(DUN_TEST_CONNECTED, &dev->flags);
- dun_bridge_send_ctrl_bits(ACM_CTRL_DTR | ACM_CTRL_RTS);
- }
-
- return ret;
-}
-
-static ssize_t debug_read(struct file *file, char __user *ubuf,
- size_t count, loff_t *ppos)
-{
- struct dun_bridge_test_dev *dev = __dev;
- return simple_read_from_buffer(ubuf, count, ppos,
- dev->read_buf, dev->buflen);
-}
-
-static ssize_t debug_write(struct file *file, const char __user *ubuf,
- size_t count, loff_t *ppos)
-{
- struct dun_bridge_test_dev *dev = __dev;
- unsigned char *buf;
- int ret;
-
- if (!dev)
- return -ENODEV;
-
- buf = kmalloc(count, GFP_KERNEL);
- if (!buf) {
- pr_err("%s: unable to allocate mem for writing\n", __func__);
- return -ENOMEM;
- }
-
- if (!copy_from_user(buf, ubuf, count)) {
- ret = dun_bridge_write(buf, count);
- if (ret < 0) {
- pr_err("%s: error writing to dun_bridge\n", __func__);
- kfree(buf);
- return ret;
- }
- } else {
- pr_err("%s: error copying for writing\n", __func__);
- kfree(buf);
- }
-
- return count;
-}
-
-const struct file_operations dun_bridge_test_debug_ops = {
- .open = debug_open,
- .read = debug_read,
- .write = debug_write,
-};
-
-static void dun_bridge_test_debug_init(void)
-{
- dfile = debugfs_create_file("dun_bridge_test", 0555, NULL,
- NULL, &dun_bridge_test_debug_ops);
-}
-#else
-static void dun_bridge_test_debug_init(void) { }
-#endif
-
-static int __init dun_bridge_test_init(void)
-{
- struct dun_bridge_test_dev *dev;
-
- pr_info("%s\n", __func__);
-
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
- if (!dev)
- return -ENOMEM;
-
- __dev = dev;
-
- dev->ops.read_complete = dun_bridge_test_read_complete;
- dev->ops.write_complete = dun_bridge_test_write_complete;
- dev->read_buf = kmalloc(RD_BUF_SIZE, GFP_KERNEL);
- if (!dev->read_buf) {
- pr_err("%s: unable to allocate read buffer\n", __func__);
- kfree(dev);
- return -ENOMEM;
- }
-
- dev->ops.ctxt = dev;
- INIT_WORK(&dev->read_w, dun_bridge_test_read_work);
-
- dun_bridge_test_debug_init();
-
- return 0;
-}
-
-static void __exit dun_bridge_test_exit(void)
-{
- struct dun_bridge_test_dev *dev = __dev;
-
- pr_info("%s:\n", __func__);
-
- if (test_bit(DUN_TEST_CONNECTED, &dev->flags))
- dun_bridge_close();
-
- debugfs_remove(dfile);
-
- kfree(dev->read_buf);
- kfree(dev);
-}
-
-module_init(dun_bridge_test_init);
-module_exit(dun_bridge_test_exit);
-
-MODULE_DESCRIPTION(DRIVER_DESC);
-MODULE_LICENSE("GPL V2");
diff --git a/drivers/usb/misc/mdm_ctrl_bridge.c b/drivers/usb/misc/mdm_ctrl_bridge.c
new file mode 100644
index 0000000..87adf2e
--- /dev/null
+++ b/drivers/usb/misc/mdm_ctrl_bridge.c
@@ -0,0 +1,729 @@
+/* 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/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/ratelimit.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/cdc.h>
+#include <linux/termios.h>
+#include <asm/unaligned.h>
+#include <mach/usb_bridge.h>
+
+static const char *ctrl_bridge_names[] = {
+ "dun_ctrl_hsic0",
+ "rmnet_ctrl_hsic0"
+};
+
+/* polling interval for Interrupt ep */
+#define HS_INTERVAL 7
+#define FS_LS_INTERVAL 3
+
+#define ACM_CTRL_DTR (1 << 0)
+#define DEFAULT_READ_URB_LENGTH 4096
+
+struct ctrl_bridge {
+
+ struct usb_device *udev;
+ struct usb_interface *intf;
+
+ unsigned int int_pipe;
+ struct urb *inturb;
+ void *intbuf;
+
+ struct urb *readurb;
+ void *readbuf;
+
+ struct usb_anchor tx_submitted;
+ struct usb_ctrlrequest *in_ctlreq;
+
+ struct bridge *brdg;
+ struct platform_device *pdev;
+
+ /* input control lines (DSR, CTS, CD, RI) */
+ unsigned int cbits_tohost;
+
+ /* output control lines (DTR, RTS) */
+ unsigned int cbits_tomdm;
+
+ /* counters */
+ unsigned int snd_encap_cmd;
+ unsigned int get_encap_res;
+ unsigned int resp_avail;
+ unsigned int set_ctrl_line_sts;
+ unsigned int notify_ser_state;
+
+};
+
+static struct ctrl_bridge *__dev[MAX_BRIDGE_DEVICES];
+
+/* counter used for indexing ctrl bridge devices */
+static int ch_id;
+
+unsigned int ctrl_bridge_get_cbits_tohost(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ return dev->cbits_tohost;
+}
+EXPORT_SYMBOL(ctrl_bridge_get_cbits_tohost);
+
+int ctrl_bridge_set_cbits(unsigned int id, unsigned int cbits)
+{
+ struct ctrl_bridge *dev;
+ struct bridge *brdg;
+ int retval;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ pr_debug("%s: dev[id] =%u cbits : %u\n", __func__, id, cbits);
+
+ brdg = dev->brdg;
+ if (!brdg)
+ return -ENODEV;
+
+ dev->cbits_tomdm = cbits;
+
+ retval = ctrl_bridge_write(id, NULL, 0);
+
+ /* if DTR is high, update latest modem info to host */
+ if (brdg && (cbits & ACM_CTRL_DTR) && brdg->ops.send_cbits)
+ brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost);
+
+ return retval;
+}
+EXPORT_SYMBOL(ctrl_bridge_set_cbits);
+
+static void resp_avail_cb(struct urb *urb)
+{
+ struct ctrl_bridge *dev = urb->context;
+ struct usb_device *udev;
+ int status = 0;
+ int resubmit_urb = 1;
+ struct bridge *brdg = dev->brdg;
+
+ udev = interface_to_usbdev(dev->intf);
+ switch (urb->status) {
+ case 0:
+ /*success*/
+ dev->get_encap_res++;
+ if (brdg && brdg->ops.send_pkt)
+ brdg->ops.send_pkt(brdg->ctx, urb->transfer_buffer,
+ urb->actual_length);
+ break;
+
+ /*do not resubmit*/
+ case -ESHUTDOWN:
+ case -ENOENT:
+ case -ECONNRESET:
+ /* unplug */
+ case -EPROTO:
+ /*babble error*/
+ resubmit_urb = 0;
+ /*resubmit*/
+ case -EOVERFLOW:
+ default:
+ dev_dbg(&udev->dev, "%s: non zero urb status = %d\n",
+ __func__, urb->status);
+ }
+
+ if (resubmit_urb) {
+ /*re- submit int urb to check response available*/
+ status = usb_submit_urb(dev->inturb, GFP_ATOMIC);
+ if (status)
+ dev_err(&udev->dev,
+ "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+ }
+}
+
+static void notification_available_cb(struct urb *urb)
+{
+ int status;
+ struct usb_cdc_notification *ctrl;
+ struct usb_device *udev;
+ struct ctrl_bridge *dev = urb->context;
+ struct bridge *brdg = dev->brdg;
+ unsigned int ctrl_bits;
+ unsigned char *data;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ switch (urb->status) {
+ case 0:
+ /*success*/
+ break;
+ case -ESHUTDOWN:
+ case -ENOENT:
+ case -ECONNRESET:
+ case -EPROTO:
+ /* unplug */
+ return;
+ case -EPIPE:
+ dev_err(&udev->dev, "%s: stall on int endpoint\n", __func__);
+ /* TBD : halt to be cleared in work */
+ case -EOVERFLOW:
+ default:
+ pr_debug_ratelimited("%s: non zero urb status = %d\n",
+ __func__, urb->status);
+ goto resubmit_int_urb;
+ }
+
+ ctrl = (struct usb_cdc_notification *)urb->transfer_buffer;
+ data = (unsigned char *)(ctrl + 1);
+
+ switch (ctrl->bNotificationType) {
+ case USB_CDC_NOTIFY_RESPONSE_AVAILABLE:
+ dev->resp_avail++;
+ usb_fill_control_urb(dev->readurb, udev,
+ usb_rcvctrlpipe(udev, 0),
+ (unsigned char *)dev->in_ctlreq,
+ dev->readbuf,
+ DEFAULT_READ_URB_LENGTH,
+ resp_avail_cb, dev);
+
+ status = usb_submit_urb(dev->readurb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&udev->dev,
+ "%s: Error submitting Read URB %d\n",
+ __func__, status);
+ goto resubmit_int_urb;
+ }
+ return;
+ case USB_CDC_NOTIFY_NETWORK_CONNECTION:
+ dev_dbg(&udev->dev, "%s network\n", ctrl->wValue ?
+ "connected to" : "disconnected from");
+ break;
+ case USB_CDC_NOTIFY_SERIAL_STATE:
+ dev->notify_ser_state++;
+ ctrl_bits = get_unaligned_le16(data);
+ dev_dbg(&udev->dev, "serial state: %d\n", ctrl_bits);
+ dev->cbits_tohost = ctrl_bits;
+ if (brdg && brdg->ops.send_cbits)
+ brdg->ops.send_cbits(brdg->ctx, ctrl_bits);
+ break;
+ default:
+ dev_err(&udev->dev, "%s: unknown notification %d received:"
+ "index %d len %d data0 %d data1 %d",
+ __func__, ctrl->bNotificationType, ctrl->wIndex,
+ ctrl->wLength, data[0], data[1]);
+ }
+
+resubmit_int_urb:
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status)
+ dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+}
+
+int ctrl_bridge_start_read(struct ctrl_bridge *dev)
+{
+ int retval = 0;
+ struct usb_device *udev;
+
+ udev = interface_to_usbdev(dev->intf);
+
+ retval = usb_autopm_get_interface_async(dev->intf);
+ if (retval < 0) {
+ dev_err(&udev->dev, "%s resumption fail\n", __func__);
+ goto done_nopm;
+ }
+
+ retval = usb_submit_urb(dev->inturb, GFP_KERNEL);
+ if (retval < 0)
+ dev_err(&udev->dev, "%s intr submit %d\n", __func__, retval);
+
+ usb_autopm_put_interface_async(dev->intf);
+done_nopm:
+ return retval;
+}
+
+static int ctrl_bridge_stop_read(struct ctrl_bridge *dev)
+{
+ if (dev->readurb) {
+ dev_dbg(&dev->udev->dev, "killing rcv urb\n");
+ usb_unlink_urb(dev->readurb);
+ }
+
+ if (dev->inturb) {
+ dev_dbg(&dev->udev->dev, "killing int urb\n");
+ usb_unlink_urb(dev->inturb);
+ }
+
+ return 0;
+}
+
+int ctrl_bridge_open(struct bridge *brdg)
+{
+ struct ctrl_bridge *dev;
+
+ if (!brdg) {
+ err("bridge is null\n");
+ return -EINVAL;
+ }
+
+ if (brdg->ch_id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[brdg->ch_id];
+ if (!dev) {
+ err("dev is null\n");
+ return -ENODEV;
+ }
+
+ dev->brdg = brdg;
+ dev->snd_encap_cmd = 0;
+ dev->get_encap_res = 0;
+ dev->resp_avail = 0;
+ dev->set_ctrl_line_sts = 0;
+ dev->notify_ser_state = 0;
+
+ return ctrl_bridge_start_read(dev);
+}
+EXPORT_SYMBOL(ctrl_bridge_open);
+
+void ctrl_bridge_close(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return;
+
+ dev = __dev[id];
+ if (!dev && !dev->brdg)
+ return;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ ctrl_bridge_set_cbits(dev->brdg->ch_id, 0);
+ usb_unlink_anchored_urbs(&dev->tx_submitted);
+ ctrl_bridge_stop_read(dev);
+
+ dev->brdg = NULL;
+}
+EXPORT_SYMBOL(ctrl_bridge_close);
+
+static void ctrl_write_callback(struct urb *urb)
+{
+
+ if (urb->status) {
+ pr_debug("Write status/size %d/%d\n",
+ urb->status, urb->actual_length);
+ }
+
+ kfree(urb->transfer_buffer);
+ kfree(urb->setup_packet);
+ usb_free_urb(urb);
+}
+
+int ctrl_bridge_write(unsigned int id, char *data, size_t size)
+{
+ int result;
+ struct urb *writeurb;
+ struct usb_ctrlrequest *out_ctlreq;
+ struct usb_device *udev;
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES) {
+ result = -EINVAL;
+ goto free_data;
+ }
+
+ dev = __dev[id];
+
+ if (!dev) {
+ result = -ENODEV;
+ goto free_data;
+ }
+
+ udev = interface_to_usbdev(dev->intf);
+
+ dev_dbg(&udev->dev, "%s:[id]:%u: write (%d bytes)\n",
+ __func__, id, size);
+
+ writeurb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!writeurb) {
+ dev_err(&udev->dev, "%s: error allocating read urb\n",
+ __func__);
+ result = -ENOMEM;
+ goto free_data;
+ }
+
+ out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_ATOMIC);
+ if (!out_ctlreq) {
+ dev_err(&udev->dev,
+ "%s: error allocating setup packet buffer\n",
+ __func__);
+ result = -ENOMEM;
+ goto free_urb;
+ }
+
+ /* CDC Send Encapsulated Request packet */
+ out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE);
+ if (!data && !size) {
+ out_ctlreq->bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE;
+ out_ctlreq->wValue = dev->cbits_tomdm;
+ dev->set_ctrl_line_sts++;
+ } else {
+ out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND;
+ out_ctlreq->wValue = 0;
+ dev->snd_encap_cmd++;
+ }
+ out_ctlreq->wIndex =
+ dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ out_ctlreq->wLength = cpu_to_le16(size);
+
+ usb_fill_control_urb(writeurb, udev,
+ usb_sndctrlpipe(udev, 0),
+ (unsigned char *)out_ctlreq,
+ (void *)data, size,
+ ctrl_write_callback, NULL);
+
+ result = usb_autopm_get_interface_async(dev->intf);
+ if (result < 0) {
+ dev_err(&udev->dev, "%s: unable to resume interface: %d\n",
+ __func__, result);
+
+ /*
+ * Revisit: if (result == -EPERM)
+ * bridge_suspend(dev->intf, PMSG_SUSPEND);
+ */
+
+ goto free_ctrlreq;
+ }
+
+ usb_anchor_urb(writeurb, &dev->tx_submitted);
+ result = usb_submit_urb(writeurb, GFP_ATOMIC);
+ if (result < 0) {
+ dev_err(&udev->dev, "%s: submit URB error %d\n",
+ __func__, result);
+ usb_autopm_put_interface_async(dev->intf);
+ goto unanchor_urb;
+ }
+
+ return size;
+
+unanchor_urb:
+ usb_unanchor_urb(writeurb);
+free_ctrlreq:
+ kfree(out_ctlreq);
+free_urb:
+ usb_free_urb(writeurb);
+free_data:
+ kfree(data);
+
+ return result;
+}
+EXPORT_SYMBOL(ctrl_bridge_write);
+
+int ctrl_bridge_suspend(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+
+ return ctrl_bridge_stop_read(dev);
+}
+
+int ctrl_bridge_resume(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ return ctrl_bridge_start_read(dev);
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t ctrl_bridge_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct ctrl_bridge *dev;
+ char *buf;
+ int ret;
+ int i;
+ int temp = 0;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < ch_id; i++) {
+ dev = __dev[i];
+ if (!dev)
+ continue;
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "\nName#%s dev %p\n"
+ "snd encap cmd cnt: %u\n"
+ "get encap res cnt: %u\n"
+ "res available cnt: %u\n"
+ "set ctrlline sts cnt: %u\n"
+ "notify ser state cnt: %u\n"
+ "cbits_tomdm: %d\n"
+ "cbits_tohost: %d\n",
+ dev->pdev->name, dev,
+ dev->snd_encap_cmd,
+ dev->get_encap_res,
+ dev->resp_avail,
+ dev->set_ctrl_line_sts,
+ dev->notify_ser_state,
+ dev->cbits_tomdm,
+ dev->cbits_tohost);
+
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t ctrl_bridge_reset_stats(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct ctrl_bridge *dev;
+ int i;
+
+ for (i = 0; i < ch_id; i++) {
+ dev = __dev[i];
+ if (!dev)
+ continue;
+
+ dev->snd_encap_cmd = 0;
+ dev->get_encap_res = 0;
+ dev->resp_avail = 0;
+ dev->set_ctrl_line_sts = 0;
+ dev->notify_ser_state = 0;
+ }
+ return count;
+}
+
+const struct file_operations ctrl_stats_ops = {
+ .read = ctrl_bridge_read_stats,
+ .write = ctrl_bridge_reset_stats,
+};
+
+struct dentry *ctrl_dent;
+struct dentry *ctrl_dfile;
+static void ctrl_bridge_debugfs_init(void)
+{
+ ctrl_dent = debugfs_create_dir("ctrl_hsic_bridge", 0);
+ if (IS_ERR(ctrl_dent))
+ return;
+
+ ctrl_dfile =
+ debugfs_create_file("status", 0644, ctrl_dent, 0,
+ &ctrl_stats_ops);
+ if (!ctrl_dfile || IS_ERR(ctrl_dfile))
+ debugfs_remove(ctrl_dent);
+}
+
+static void ctrl_bridge_debugfs_exit(void)
+{
+ debugfs_remove(ctrl_dfile);
+ debugfs_remove(ctrl_dent);
+}
+
+#else
+static void ctrl_bridge_debugfs_init(void) { }
+static void ctrl_bridge_debugfs_exit(void) { }
+#endif
+
+int
+ctrl_bridge_probe(struct usb_interface *ifc, struct usb_host_endpoint *int_in,
+ int id)
+{
+ struct ctrl_bridge *dev;
+ struct usb_device *udev;
+ struct usb_endpoint_descriptor *ep;
+ u16 wMaxPacketSize;
+ int retval = 0;
+ int interval;
+
+ udev = interface_to_usbdev(ifc);
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ dev_err(&udev->dev, "%s: unable to allocate dev\n",
+ __func__);
+ return -ENOMEM;
+ }
+ dev->pdev = platform_device_alloc(ctrl_bridge_names[id], id);
+ if (!dev->pdev) {
+ dev_err(&dev->udev->dev,
+ "%s: unable to allocate platform device\n", __func__);
+ retval = -ENOMEM;
+ goto nomem;
+ }
+
+ dev->udev = udev;
+ dev->int_pipe = usb_rcvintpipe(udev,
+ int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ dev->intf = ifc;
+
+ init_usb_anchor(&dev->tx_submitted);
+
+ /*use max pkt size from ep desc*/
+ ep = &dev->intf->cur_altsetting->endpoint[0].desc;
+
+ dev->inturb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->inturb) {
+ dev_err(&udev->dev, "%s: error allocating int urb\n", __func__);
+ retval = -ENOMEM;
+ goto pdev_del;
+ }
+
+ wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize);
+
+ dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL);
+ if (!dev->intbuf) {
+ dev_err(&udev->dev, "%s: error allocating int buffer\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_inturb;
+ }
+
+ interval =
+ (udev->speed == USB_SPEED_HIGH) ? HS_INTERVAL : FS_LS_INTERVAL;
+
+ usb_fill_int_urb(dev->inturb, udev, dev->int_pipe,
+ dev->intbuf, wMaxPacketSize,
+ notification_available_cb, dev, interval);
+
+ dev->readurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->readurb) {
+ dev_err(&udev->dev, "%s: error allocating read urb\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_intbuf;
+ }
+
+ dev->readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL);
+ if (!dev->readbuf) {
+ dev_err(&udev->dev, "%s: error allocating read buffer\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_rurb;
+ }
+
+ dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL);
+ if (!dev->in_ctlreq) {
+ dev_err(&udev->dev,
+ "%s:error allocating setup packet buffer\n",
+ __func__);
+ retval = -ENOMEM;
+ goto free_rbuf;
+ }
+
+ dev->in_ctlreq->bRequestType =
+ (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE);
+ dev->in_ctlreq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE;
+ dev->in_ctlreq->wValue = 0;
+ dev->in_ctlreq->wIndex =
+ dev->intf->cur_altsetting->desc.bInterfaceNumber;
+ dev->in_ctlreq->wLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH);
+
+ __dev[id] = dev;
+
+ platform_device_add(dev->pdev);
+
+ ch_id++;
+
+ return retval;
+
+free_rbuf:
+ kfree(dev->readbuf);
+free_rurb:
+ usb_free_urb(dev->readurb);
+free_intbuf:
+ kfree(dev->intbuf);
+free_inturb:
+ usb_free_urb(dev->inturb);
+pdev_del:
+ platform_device_del(dev->pdev);
+nomem:
+ kfree(dev);
+
+ return retval;
+}
+
+void ctrl_bridge_disconnect(unsigned int id)
+{
+ struct ctrl_bridge *dev = __dev[id];
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ kfree(dev->in_ctlreq);
+ kfree(dev->readbuf);
+ kfree(dev->intbuf);
+
+ usb_free_urb(dev->readurb);
+ usb_free_urb(dev->inturb);
+
+ platform_device_del(dev->pdev);
+ __dev[id] = NULL;
+ ch_id--;
+
+ kfree(dev);
+}
+
+static int __init ctrl_bridge_init(void)
+{
+ ctrl_bridge_debugfs_init();
+
+ return 0;
+}
+module_init(ctrl_bridge_init);
+
+static void __exit ctrl_bridge_exit(void)
+{
+ ctrl_bridge_debugfs_exit();
+}
+module_exit(ctrl_bridge_exit);
+
+MODULE_DESCRIPTION("Qualcomm modem control bridge driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/mdm_data_bridge.c b/drivers/usb/misc/mdm_data_bridge.c
new file mode 100644
index 0000000..c41fcfb
--- /dev/null
+++ b/drivers/usb/misc/mdm_data_bridge.c
@@ -0,0 +1,923 @@
+/* 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/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/ratelimit.h>
+#include <mach/usb_bridge.h>
+
+#define MAX_RX_URBS 50
+#define RMNET_RX_BUFSIZE 2048
+
+#define STOP_SUBMIT_URB_LIMIT 400
+#define FLOW_CTRL_EN_THRESHOLD 500
+#define FLOW_CTRL_DISABLE 300
+#define FLOW_CTRL_SUPPORT 1
+
+static const char *data_bridge_names[] = {
+ "dun_data_hsic0",
+ "rmnet_data_hsic0"
+};
+
+static struct workqueue_struct *bridge_wq;
+
+static unsigned int fctrl_support = FLOW_CTRL_SUPPORT;
+module_param(fctrl_support, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int fctrl_en_thld = FLOW_CTRL_EN_THRESHOLD;
+module_param(fctrl_en_thld, uint, S_IRUGO | S_IWUSR);
+
+static unsigned int fctrl_dis_thld = FLOW_CTRL_DISABLE;
+module_param(fctrl_dis_thld, uint, S_IRUGO | S_IWUSR);
+
+unsigned int max_rx_urbs = MAX_RX_URBS;
+module_param(max_rx_urbs, uint, S_IRUGO | S_IWUSR);
+
+unsigned int stop_submit_urb_limit = STOP_SUBMIT_URB_LIMIT;
+module_param(stop_submit_urb_limit, uint, S_IRUGO | S_IWUSR);
+
+#define TX_HALT BIT(0)
+#define RX_HALT BIT(1)
+#define SUSPENDED BIT(2)
+
+struct data_bridge {
+ struct usb_interface *intf;
+ struct usb_device *udev;
+ unsigned int bulk_in;
+ unsigned int bulk_out;
+
+ /* keep track of in-flight URBs */
+ struct usb_anchor tx_active;
+ struct usb_anchor rx_active;
+
+ /* keep track of outgoing URBs during suspend */
+ struct usb_anchor delayed;
+
+ struct list_head rx_idle;
+ struct sk_buff_head rx_done;
+
+ struct workqueue_struct *wq;
+ struct work_struct process_rx_w;
+
+ struct bridge *brdg;
+
+ /* work queue function for handling halt conditions */
+ struct work_struct kevent;
+
+ unsigned long flags;
+
+ struct platform_device *pdev;
+
+ /* counters */
+ atomic_t pending_txurbs;
+ unsigned int txurb_drp_cnt;
+ unsigned long to_host;
+ unsigned long to_modem;
+ unsigned int tx_throttled_cnt;
+ unsigned int tx_unthrottled_cnt;
+ unsigned int rx_throttled_cnt;
+ unsigned int rx_unthrottled_cnt;
+};
+
+static struct data_bridge *__dev[MAX_BRIDGE_DEVICES];
+
+/* counter used for indexing data bridge devices */
+static int ch_id;
+
+static int submit_rx_urb(struct data_bridge *dev, struct urb *urb,
+ gfp_t flags);
+
+static inline bool rx_halted(struct data_bridge *dev)
+{
+ return test_bit(RX_HALT, &dev->flags);
+}
+
+static inline bool rx_throttled(struct bridge *brdg)
+{
+ return test_bit(RX_THROTTLED, &brdg->flags);
+}
+
+int data_bridge_unthrottle_rx(unsigned int id)
+{
+ struct data_bridge *dev;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev && !dev->brdg)
+ return -ENODEV;
+
+ dev->rx_unthrottled_cnt++;
+ queue_work(dev->wq, &dev->process_rx_w);
+
+ return 0;
+}
+EXPORT_SYMBOL(data_bridge_unthrottle_rx);
+
+static void data_bridge_process_rx(struct work_struct *work)
+{
+ int retval;
+ unsigned long flags;
+ struct urb *rx_idle;
+ struct sk_buff *skb;
+ struct data_bridge *dev =
+ container_of(work, struct data_bridge, process_rx_w);
+
+ struct bridge *brdg = dev->brdg;
+
+ if (!brdg || !brdg->ops.send_pkt || rx_halted(dev))
+ return;
+
+ while (!rx_throttled(brdg) && (skb = skb_dequeue(&dev->rx_done))) {
+ dev->to_host++;
+ /* hand off sk_buff to client,they'll need to free it */
+ retval = brdg->ops.send_pkt(brdg->ctx, skb, skb->len);
+ if (retval == -ENOTCONN || retval == -EINVAL) {
+ return;
+ } else if (retval == -EBUSY) {
+ dev->rx_throttled_cnt++;
+ break;
+ }
+ }
+
+ spin_lock_irqsave(&dev->rx_done.lock, flags);
+ if (dev->rx_done.qlen > stop_submit_urb_limit && rx_throttled(brdg)) {
+ spin_unlock_irqrestore(&dev->rx_done.lock, flags);
+ return;
+ }
+
+ while (!list_empty(&dev->rx_idle)) {
+
+ rx_idle = list_first_entry(&dev->rx_idle, struct urb, urb_list);
+ list_del(&rx_idle->urb_list);
+ spin_unlock_irqrestore(&dev->rx_done.lock, flags);
+ retval = submit_rx_urb(dev, rx_idle, GFP_KERNEL);
+ spin_lock_irqsave(&dev->rx_done.lock, flags);
+ if (retval)
+ break;
+ }
+ spin_unlock_irqrestore(&dev->rx_done.lock, flags);
+}
+
+static void data_bridge_read_cb(struct urb *urb)
+{
+ struct bridge *brdg;
+ struct sk_buff *skb = urb->context;
+ struct data_bridge *dev = *(struct data_bridge **)skb->cb;
+ bool queue = 0;
+
+ brdg = dev->brdg;
+
+ skb_put(skb, urb->actual_length);
+
+ switch (urb->status) {
+ case 0: /* success */
+ queue = 1;
+ spin_lock(&dev->rx_done.lock);
+ __skb_queue_tail(&dev->rx_done, skb);
+ spin_unlock(&dev->rx_done.lock);
+ break;
+
+ /*do not resubmit*/
+ case -EPIPE:
+ set_bit(RX_HALT, &dev->flags);
+ dev_err(&dev->udev->dev, "%s: epout halted\n", __func__);
+ schedule_work(&dev->kevent);
+ /* FALLTHROUGH */
+ case -ESHUTDOWN:
+ case -ENOENT: /* suspended */
+ case -ECONNRESET: /* unplug */
+ case -EPROTO:
+ dev_kfree_skb_any(skb);
+ break;
+
+ /*resubmit */
+ case -EOVERFLOW: /*babble error*/
+ default:
+ queue = 1;
+ dev_kfree_skb_any(skb);
+ pr_debug_ratelimited("%s: non zero urb status = %d\n",
+ __func__, urb->status);
+ break;
+ }
+
+ spin_lock(&dev->rx_done.lock);
+ list_add_tail(&urb->urb_list, &dev->rx_idle);
+ spin_unlock(&dev->rx_done.lock);
+
+ if (queue)
+ queue_work(dev->wq, &dev->process_rx_w);
+}
+
+static int submit_rx_urb(struct data_bridge *dev, struct urb *rx_urb,
+ gfp_t flags)
+{
+ struct sk_buff *skb;
+ int retval = -EINVAL;
+
+ skb = alloc_skb(RMNET_RX_BUFSIZE, flags);
+ if (!skb) {
+ usb_free_urb(rx_urb);
+ return -ENOMEM;
+ }
+
+ *((struct data_bridge **)skb->cb) = dev;
+
+ usb_fill_bulk_urb(rx_urb, dev->udev, dev->bulk_in,
+ skb->data, RMNET_RX_BUFSIZE,
+ data_bridge_read_cb, skb);
+
+ if (test_bit(SUSPENDED, &dev->flags))
+ goto suspended;
+
+ usb_anchor_urb(rx_urb, &dev->rx_active);
+ retval = usb_submit_urb(rx_urb, flags);
+ if (retval)
+ goto fail;
+
+ return 0;
+fail:
+ usb_unanchor_urb(rx_urb);
+suspended:
+ dev_kfree_skb_any(skb);
+ usb_free_urb(rx_urb);
+ return retval;
+}
+
+static int data_bridge_prepare_rx(struct data_bridge *dev)
+{
+ int i;
+ struct urb *rx_urb;
+
+ for (i = 0; i < max_rx_urbs; i++) {
+ rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!rx_urb)
+ return -ENOMEM;
+
+ list_add_tail(&rx_urb->urb_list, &dev->rx_idle);
+ }
+ return 0;
+}
+
+int data_bridge_open(struct bridge *brdg)
+{
+ struct data_bridge *dev;
+
+ if (!brdg) {
+ err("bridge is null\n");
+ return -EINVAL;
+ }
+
+ if (brdg->ch_id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[brdg->ch_id];
+ if (!dev) {
+ err("dev is null\n");
+ return -ENODEV;
+ }
+
+ dev_dbg(&dev->udev->dev, "%s: dev:%p\n", __func__, dev);
+
+ dev->brdg = brdg;
+ atomic_set(&dev->pending_txurbs, 0);
+ dev->to_host = 0;
+ dev->to_modem = 0;
+ dev->txurb_drp_cnt = 0;
+ dev->tx_throttled_cnt = 0;
+ dev->tx_unthrottled_cnt = 0;
+ dev->rx_throttled_cnt = 0;
+ dev->rx_unthrottled_cnt = 0;
+
+ queue_work(dev->wq, &dev->process_rx_w);
+
+ return 0;
+}
+EXPORT_SYMBOL(data_bridge_open);
+
+void data_bridge_close(unsigned int id)
+{
+ struct data_bridge *dev;
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return;
+
+ dev = __dev[id];
+ if (!dev && !dev->brdg)
+ return;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ usb_unlink_anchored_urbs(&dev->tx_active);
+ usb_unlink_anchored_urbs(&dev->rx_active);
+ usb_unlink_anchored_urbs(&dev->delayed);
+
+ spin_lock_irqsave(&dev->rx_done.lock, flags);
+ while ((skb = __skb_dequeue(&dev->rx_done)))
+ dev_kfree_skb_any(skb);
+ spin_unlock_irqrestore(&dev->rx_done.lock, flags);
+
+ dev->brdg = NULL;
+}
+EXPORT_SYMBOL(data_bridge_close);
+
+static void defer_kevent(struct work_struct *work)
+{
+ int status;
+ struct data_bridge *dev =
+ container_of(work, struct data_bridge, kevent);
+
+ if (!dev)
+ return;
+
+ if (test_bit(TX_HALT, &dev->flags)) {
+ usb_unlink_anchored_urbs(&dev->tx_active);
+
+ status = usb_autopm_get_interface(dev->intf);
+ if (status < 0) {
+ dev_err(&dev->udev->dev,
+ "can't acquire interface, status %d\n", status);
+ return;
+ }
+
+ status = usb_clear_halt(dev->udev, dev->bulk_out);
+ usb_autopm_put_interface(dev->intf);
+ if (status < 0 && status != -EPIPE && status != -ESHUTDOWN)
+ dev_err(&dev->udev->dev,
+ "can't clear tx halt, status %d\n", status);
+ else
+ clear_bit(TX_HALT, &dev->flags);
+ }
+
+ if (test_bit(RX_HALT, &dev->flags)) {
+ usb_unlink_anchored_urbs(&dev->rx_active);
+
+ status = usb_autopm_get_interface(dev->intf);
+ if (status < 0) {
+ dev_err(&dev->udev->dev,
+ "can't acquire interface, status %d\n", status);
+ return;
+ }
+
+ status = usb_clear_halt(dev->udev, dev->bulk_in);
+ usb_autopm_put_interface(dev->intf);
+ if (status < 0 && status != -EPIPE && status != -ESHUTDOWN)
+ dev_err(&dev->udev->dev,
+ "can't clear rx halt, status %d\n", status);
+ else {
+ clear_bit(RX_HALT, &dev->flags);
+ if (dev->brdg)
+ queue_work(dev->wq, &dev->process_rx_w);
+ }
+ }
+}
+
+static void data_bridge_write_cb(struct urb *urb)
+{
+ struct sk_buff *skb = urb->context;
+ struct data_bridge *dev = *(struct data_bridge **)skb->cb;
+ struct bridge *brdg = dev->brdg;
+ int pending;
+
+ pr_debug("%s: dev:%p\n", __func__, dev);
+
+ switch (urb->status) {
+ case 0: /*success*/
+ break;
+ case -EPIPE:
+ set_bit(TX_HALT, &dev->flags);
+ dev_err(&dev->udev->dev, "%s: epout halted\n", __func__);
+ schedule_work(&dev->kevent);
+ /* FALLTHROUGH */
+ case -ESHUTDOWN:
+ case -ENOENT: /* suspended */
+ case -ECONNRESET: /* unplug */
+ case -EOVERFLOW: /*babble error*/
+ /* FALLTHROUGH */
+ default:
+ pr_debug_ratelimited("%s: non zero urb status = %d\n",
+ __func__, urb->status);
+ }
+
+ usb_free_urb(urb);
+ dev_kfree_skb_any(skb);
+
+ pending = atomic_dec_return(&dev->pending_txurbs);
+
+ /*flow ctrl*/
+ if (brdg && fctrl_support && pending <= fctrl_dis_thld &&
+ test_and_clear_bit(TX_THROTTLED, &brdg->flags)) {
+ pr_debug_ratelimited("%s: disable flow ctrl: pend urbs:%u\n",
+ __func__, pending);
+ dev->tx_unthrottled_cnt++;
+ if (brdg->ops.unthrottle_tx)
+ brdg->ops.unthrottle_tx(brdg->ctx);
+ }
+
+ usb_autopm_put_interface_async(dev->intf);
+}
+
+int data_bridge_write(unsigned int id, struct sk_buff *skb)
+{
+ int result;
+ int size = skb->len;
+ int pending;
+ struct urb *txurb;
+ struct data_bridge *dev = __dev[id];
+ struct bridge *brdg;
+
+ if (!dev || !dev->brdg || !usb_get_intfdata(dev->intf))
+ return -ENODEV;
+
+ brdg = dev->brdg;
+
+ dev_dbg(&dev->udev->dev, "%s: write (%d bytes)\n", __func__, skb->len);
+
+ result = usb_autopm_get_interface(dev->intf);
+ if (result < 0) {
+ dev_err(&dev->udev->dev, "%s: resume failure\n", __func__);
+ goto error;
+ }
+
+ txurb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!txurb) {
+ dev_err(&dev->udev->dev, "%s: error allocating read urb\n",
+ __func__);
+ result = -ENOMEM;
+ goto error;
+ }
+
+ /* store dev pointer in skb */
+ *((struct data_bridge **)skb->cb) = dev;
+
+ usb_fill_bulk_urb(txurb, dev->udev, dev->bulk_out,
+ skb->data, skb->len, data_bridge_write_cb, skb);
+
+ if (test_bit(SUSPENDED, &dev->flags)) {
+ usb_anchor_urb(txurb, &dev->delayed);
+ goto free_urb;
+ }
+
+ pending = atomic_inc_return(&dev->pending_txurbs);
+ usb_anchor_urb(txurb, &dev->tx_active);
+
+ result = usb_submit_urb(txurb, GFP_KERNEL);
+ if (result < 0) {
+ usb_unanchor_urb(txurb);
+ atomic_dec(&dev->pending_txurbs);
+ dev_err(&dev->udev->dev, "%s: submit URB error %d\n",
+ __func__, result);
+ goto free_urb;
+ }
+
+ dev->to_modem++;
+ dev_dbg(&dev->udev->dev, "%s: pending_txurbs: %u\n", __func__, pending);
+
+ /* flow control: last urb submitted but return -EBUSY */
+ if (fctrl_support && pending > fctrl_en_thld) {
+ set_bit(TX_THROTTLED, &brdg->flags);
+ dev->tx_throttled_cnt++;
+ pr_debug_ratelimited("%s: enable flow ctrl pend txurbs:%u\n",
+ __func__, pending);
+ return -EBUSY;
+ }
+
+ return size;
+
+free_urb:
+ usb_free_urb(txurb);
+error:
+ dev->txurb_drp_cnt++;
+ usb_autopm_put_interface(dev->intf);
+
+ return result;
+}
+EXPORT_SYMBOL(data_bridge_write);
+
+static int data_bridge_resume(struct data_bridge *dev)
+{
+ struct urb *urb;
+ int retval;
+
+ while ((urb = usb_get_from_anchor(&dev->delayed))) {
+ usb_anchor_urb(urb, &dev->tx_active);
+ atomic_inc(&dev->pending_txurbs);
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval < 0) {
+ atomic_dec(&dev->pending_txurbs);
+ usb_unanchor_urb(urb);
+
+ /* TODO: need to free urb data */
+ usb_scuttle_anchored_urbs(&dev->delayed);
+ break;
+ }
+ dev->to_modem++;
+ dev->txurb_drp_cnt--;
+ }
+
+ clear_bit(SUSPENDED, &dev->flags);
+
+ if (dev->brdg)
+ queue_work(dev->wq, &dev->process_rx_w);
+
+ return 0;
+}
+
+static int bridge_resume(struct usb_interface *iface)
+{
+ int retval = 0;
+ int oldstate;
+ struct data_bridge *dev = usb_get_intfdata(iface);
+ struct bridge *brdg = dev->brdg;
+
+ oldstate = iface->dev.power.power_state.event;
+ iface->dev.power.power_state.event = PM_EVENT_ON;
+
+ retval = data_bridge_resume(dev);
+ if (!retval) {
+ if (oldstate & PM_EVENT_SUSPEND && brdg)
+ retval = ctrl_bridge_resume(brdg->ch_id);
+ }
+ return retval;
+}
+
+static int data_bridge_suspend(struct data_bridge *dev, pm_message_t message)
+{
+ if (atomic_read(&dev->pending_txurbs) &&
+ (message.event & PM_EVENT_AUTO))
+ return -EBUSY;
+
+ set_bit(SUSPENDED, &dev->flags);
+
+ usb_kill_anchored_urbs(&dev->tx_active);
+ usb_kill_anchored_urbs(&dev->rx_active);
+
+ return 0;
+}
+
+static int bridge_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ int retval;
+ struct data_bridge *dev = usb_get_intfdata(intf);
+ struct bridge *brdg = dev->brdg;
+
+ retval = data_bridge_suspend(dev, message);
+ if (!retval) {
+ if (message.event & PM_EVENT_SUSPEND) {
+ if (brdg)
+ retval = ctrl_bridge_suspend(brdg->ch_id);
+ intf->dev.power.power_state.event = message.event;
+ }
+ } else {
+ dev_dbg(&dev->udev->dev, "%s: device is busy,cannot suspend\n",
+ __func__);
+ }
+ return retval;
+}
+
+static int data_bridge_probe(struct usb_interface *iface,
+ struct usb_host_endpoint *bulk_in,
+ struct usb_host_endpoint *bulk_out, int id)
+{
+ struct data_bridge *dev;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ err("%s: unable to allocate dev\n", __func__);
+ return -ENOMEM;
+ }
+
+ dev->pdev = platform_device_alloc(data_bridge_names[id], id);
+ if (!dev->pdev) {
+ err("%s: unable to allocate platform device\n", __func__);
+ kfree(dev);
+ return -ENOMEM;
+ }
+
+ init_usb_anchor(&dev->tx_active);
+ init_usb_anchor(&dev->rx_active);
+ init_usb_anchor(&dev->delayed);
+
+ INIT_LIST_HEAD(&dev->rx_idle);
+ skb_queue_head_init(&dev->rx_done);
+
+ dev->wq = bridge_wq;
+
+ dev->udev = interface_to_usbdev(iface);
+ dev->intf = iface;
+
+ dev->bulk_in = usb_rcvbulkpipe(dev->udev,
+ bulk_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+ dev->bulk_out = usb_sndbulkpipe(dev->udev,
+ bulk_out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+ usb_set_intfdata(iface, dev);
+
+ INIT_WORK(&dev->kevent, defer_kevent);
+ INIT_WORK(&dev->process_rx_w, data_bridge_process_rx);
+
+ __dev[id] = dev;
+
+ /*allocate list of rx urbs*/
+ data_bridge_prepare_rx(dev);
+
+ platform_device_add(dev->pdev);
+
+ return 0;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t data_bridge_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct data_bridge *dev;
+ char *buf;
+ int ret;
+ int i;
+ int temp = 0;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ for (i = 0; i < ch_id; i++) {
+ dev = __dev[i];
+ if (!dev)
+ continue;
+
+ temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp,
+ "\nName#%s dev %p\n"
+ "pending tx urbs: %u\n"
+ "tx urb drp cnt: %u\n"
+ "to host: %lu\n"
+ "to mdm: %lu\n"
+ "tx throttled cnt: %u\n"
+ "tx unthrottled cnt: %u\n"
+ "rx throttled cnt: %u\n"
+ "rx unthrottled cnt: %u\n"
+ "rx done skb qlen: %u\n"
+ "suspended: %d\n"
+ "TX_HALT: %d\n"
+ "RX_HALT: %d\n",
+ dev->pdev->name, dev,
+ atomic_read(&dev->pending_txurbs),
+ dev->txurb_drp_cnt,
+ dev->to_host,
+ dev->to_modem,
+ dev->tx_throttled_cnt,
+ dev->tx_unthrottled_cnt,
+ dev->rx_throttled_cnt,
+ dev->rx_unthrottled_cnt,
+ dev->rx_done.qlen,
+ test_bit(SUSPENDED, &dev->flags),
+ test_bit(TX_HALT, &dev->flags),
+ test_bit(RX_HALT, &dev->flags));
+
+ }
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp);
+
+ kfree(buf);
+
+ return ret;
+}
+
+static ssize_t data_bridge_reset_stats(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos)
+{
+ struct data_bridge *dev;
+ int i;
+
+ for (i = 0; i < ch_id; i++) {
+ dev = __dev[i];
+ if (!dev)
+ continue;
+
+ dev->to_host = 0;
+ dev->to_modem = 0;
+ dev->txurb_drp_cnt = 0;
+ dev->tx_throttled_cnt = 0;
+ dev->tx_unthrottled_cnt = 0;
+ dev->rx_throttled_cnt = 0;
+ dev->rx_unthrottled_cnt = 0;
+ }
+ return count;
+}
+
+const struct file_operations data_stats_ops = {
+ .read = data_bridge_read_stats,
+ .write = data_bridge_reset_stats,
+};
+
+struct dentry *data_dent;
+struct dentry *data_dfile;
+static void data_bridge_debugfs_init(void)
+{
+ data_dent = debugfs_create_dir("data_hsic_bridge", 0);
+ if (IS_ERR(data_dent))
+ return;
+
+ data_dfile = debugfs_create_file("status", 0644, data_dent, 0,
+ &data_stats_ops);
+ if (!data_dfile || IS_ERR(data_dfile))
+ debugfs_remove(data_dent);
+}
+
+static void data_bridge_debugfs_exit(void)
+{
+ debugfs_remove(data_dfile);
+ debugfs_remove(data_dent);
+}
+
+#else
+static void data_bridge_debugfs_init(void) { }
+static void data_bridge_debugfs_exit(void) { }
+#endif
+
+static int __devinit
+bridge_probe(struct usb_interface *iface, const struct usb_device_id *id)
+{
+ struct usb_host_endpoint *endpoint = NULL;
+ struct usb_host_endpoint *bulk_in = NULL;
+ struct usb_host_endpoint *bulk_out = NULL;
+ struct usb_host_endpoint *int_in = NULL;
+ struct usb_device *udev;
+ int i;
+ int status = 0;
+ int numends;
+ int iface_num;
+
+ iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
+
+ if (iface->num_altsetting != 1) {
+ err("%s invalid num_altsetting %u\n",
+ __func__, iface->num_altsetting);
+ return -EINVAL;
+ }
+
+ udev = interface_to_usbdev(iface);
+ usb_get_dev(udev);
+
+ if (iface_num != DUN_IFACE_NUM && iface_num != TETHERED_RMNET_IFACE_NUM)
+ return 0;
+
+ numends = iface->cur_altsetting->desc.bNumEndpoints;
+ for (i = 0; i < numends; i++) {
+ endpoint = iface->cur_altsetting->endpoint + i;
+ if (!endpoint) {
+ dev_err(&udev->dev, "%s: invalid endpoint %u\n",
+ __func__, i);
+ status = -EINVAL;
+ goto out;
+ }
+
+ if (usb_endpoint_is_bulk_in(&endpoint->desc))
+ bulk_in = endpoint;
+ else if (usb_endpoint_is_bulk_out(&endpoint->desc))
+ bulk_out = endpoint;
+ else if (usb_endpoint_is_int_in(&endpoint->desc))
+ int_in = endpoint;
+ }
+
+ if (!bulk_in || !bulk_out || !int_in) {
+ dev_err(&udev->dev, "%s: invalid endpoints\n", __func__);
+ status = -EINVAL;
+ goto out;
+ }
+
+ status = data_bridge_probe(iface, bulk_in, bulk_out, ch_id);
+ if (status < 0) {
+ dev_err(&udev->dev, "data_bridge_probe failed %d\n", status);
+ goto out;
+ }
+
+ status = ctrl_bridge_probe(iface, int_in, ch_id);
+ if (status < 0) {
+ dev_err(&udev->dev, "ctrl_bridge_probe failed %d\n", status);
+ goto free_data_bridge;
+ }
+ ch_id++;
+
+ return 0;
+
+free_data_bridge:
+ platform_device_del(__dev[ch_id]->pdev);
+ usb_set_intfdata(iface, NULL);
+ kfree(__dev[ch_id]);
+ __dev[ch_id] = NULL;
+out:
+ usb_put_dev(udev);
+
+ return status;
+}
+
+static void bridge_disconnect(struct usb_interface *intf)
+{
+ struct data_bridge *dev = usb_get_intfdata(intf);
+ struct list_head *head;
+ struct urb *rx_urb;
+ unsigned long flags;
+ int iface_num;
+
+ if (!dev) {
+ err("%s: data device not found\n", __func__);
+ return;
+ }
+
+ iface_num = intf->cur_altsetting->desc.bInterfaceNumber;
+ if (iface_num != DUN_IFACE_NUM && iface_num != TETHERED_RMNET_IFACE_NUM)
+ return;
+
+ ch_id--;
+ ctrl_bridge_disconnect(ch_id);
+ platform_device_del(dev->pdev);
+ usb_set_intfdata(intf, NULL);
+ __dev[ch_id] = NULL;
+
+ cancel_work_sync(&dev->process_rx_w);
+ cancel_work_sync(&dev->kevent);
+
+ /*free rx urbs*/
+ head = &dev->rx_idle;
+ spin_lock_irqsave(&dev->rx_done.lock, flags);
+ while (!list_empty(head)) {
+ rx_urb = list_entry(head->next, struct urb, urb_list);
+ list_del(&rx_urb->urb_list);
+ usb_free_urb(rx_urb);
+ }
+ spin_unlock_irqrestore(&dev->rx_done.lock, flags);
+
+ usb_put_dev(dev->udev);
+ kfree(dev);
+}
+
+static const struct usb_device_id bridge_ids[] = {
+ { USB_DEVICE(0x5c6, 0x9001) },
+};
+
+MODULE_DEVICE_TABLE(usb, bridge_ids);
+
+static struct usb_driver bridge_driver = {
+ .name = "mdm_bridge",
+ .probe = bridge_probe,
+ .disconnect = bridge_disconnect,
+ .id_table = bridge_ids,
+ .suspend = bridge_suspend,
+ .resume = bridge_resume,
+ .supports_autosuspend = 1,
+};
+
+static int __init bridge_init(void)
+{
+ int ret;
+
+ ret = usb_register(&bridge_driver);
+ if (ret) {
+ err("%s: unable to register mdm_bridge driver", __func__);
+ return ret;
+ }
+
+ bridge_wq = create_singlethread_workqueue("mdm_bridge");
+ if (!bridge_wq) {
+ usb_deregister(&bridge_driver);
+ pr_err("%s: Unable to create workqueue:bridge\n", __func__);
+ return -ENOMEM;
+ }
+
+ data_bridge_debugfs_init();
+
+ return 0;
+}
+
+static void __exit bridge_exit(void)
+{
+ data_bridge_debugfs_exit();
+ destroy_workqueue(bridge_wq);
+ usb_deregister(&bridge_driver);
+}
+
+module_init(bridge_init);
+module_exit(bridge_exit);
+
+MODULE_DESCRIPTION("Qualcomm modem data bridge driver");
+MODULE_LICENSE("GPL v2");