aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/misc
diff options
context:
space:
mode:
authorcodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
committercodeworkx <daniel.hillenbrand@codeworkx.de>2012-06-02 13:09:29 +0200
commitc6da2cfeb05178a11c6d062a06f8078150ee492f (patch)
treef3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/usb/misc
parentc6d7c4dbff353eac7919342ae6b3299a378160a6 (diff)
downloadkernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz
kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2
samsung update 1
Diffstat (limited to 'drivers/usb/misc')
-rw-r--r--drivers/usb/misc/Kconfig42
-rw-r--r--drivers/usb/misc/Makefile6
-rw-r--r--drivers/usb/misc/diag_bridge.c485
-rw-r--r--drivers/usb/misc/diag_bridge_test.c207
-rw-r--r--drivers/usb/misc/exynos-usb-switch.c642
-rw-r--r--drivers/usb/misc/exynos-usb-switch.h63
-rw-r--r--drivers/usb/misc/mdm_ctrl_bridge.c768
-rw-r--r--drivers/usb/misc/mdm_data_bridge.c1079
8 files changed, 3292 insertions, 0 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 1bfcd02..b2a96a5 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -244,3 +244,45 @@ config USB_YUREX
To compile this driver as a module, choose M here: the
module will be called yurex.
+config USB_EXYNOS_SWITCH
+ bool "EXYNOS USB SWITCH driver"
+ depends on USB_EHCI_S5P || USB_OHCI_S5P || USB_XHCI_EXYNOS
+ depends on USB_SUSPEND
+ depends on S5P_DEV_USB_SWITCH
+ help
+ This driver is for switching host & device controller software.
+
+config USB_QCOM_DIAG_BRIDGE
+ tristate "USB Qualcomm diagnostic bridge driver"
+ 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 communicates with the
+ diagnostic interface and allows for bridging with the diag forwarding
+ driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called diag_bridge. If unsure, choose N.
+
+config USB_QCOM_DIAG_BRIDGE_TEST
+ tristate "USB Qualcomm diagnostic bridge driver test"
+ depends on USB && USB_QCOM_DIAG_BRIDGE
+ help
+ Say Y here if you want to enable the test hook for the
+ Qualcomm diag bridge driver. When enabled, this will create
+ a debugfs file entry named "diag_bridge_test" which can be used
+ to send a ping command to the diag port of the modem over USB.
+
+ 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_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 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 mdm_bridge. If unsure, choose N.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 796ce7e..1a00ac5 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -25,5 +25,11 @@ obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o
obj-$(CONFIG_USB_USS720) += uss720.o
obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o
obj-$(CONFIG_USB_YUREX) += yurex.o
+obj-$(CONFIG_USB_EXYNOS_SWITCH) += exynos-usb-switch.o
obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
+
+obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_bridge.o
+obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE_TEST) += diag_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/diag_bridge.c b/drivers/usb/misc/diag_bridge.c
new file mode 100644
index 0000000..9794918
--- /dev/null
+++ b/drivers/usb/misc/diag_bridge.c
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2011-2012, 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/uaccess.h>
+#include <linux/usb.h>
+#include <linux/debugfs.h>
+#include <mach/diag_bridge.h>
+
+#define DRIVER_DESC "USB host diag bridge driver"
+#define DRIVER_VERSION "1.0"
+
+struct diag_bridge {
+ struct usb_device *udev;
+ struct usb_interface *ifc;
+ struct usb_anchor submitted;
+ __u8 in_epAddr;
+ __u8 out_epAddr;
+ int err;
+ struct kref kref;
+ struct diag_bridge_ops *ops;
+ struct platform_device *pdev;
+
+ /* debugging counters */
+ unsigned long bytes_to_host;
+ unsigned long bytes_to_mdm;
+ unsigned pending_reads;
+ unsigned pending_writes;
+};
+struct diag_bridge *__dev;
+
+int diag_bridge_open(struct diag_bridge_ops *ops)
+{
+ struct diag_bridge *dev = __dev;
+
+ if (!dev) {
+ err("dev is null");
+ return -ENODEV;
+ }
+
+ dev->ops = ops;
+ dev->err = 0;
+
+ return 0;
+}
+EXPORT_SYMBOL(diag_bridge_open);
+
+void diag_bridge_close(void)
+{
+ struct diag_bridge *dev = __dev;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ usb_kill_anchored_urbs(&dev->submitted);
+
+ dev->ops = 0;
+}
+EXPORT_SYMBOL(diag_bridge_close);
+
+static void diag_bridge_read_cb(struct urb *urb)
+{
+ struct diag_bridge *dev = urb->context;
+ struct diag_bridge_ops *cbs = dev->ops;
+
+ dev_dbg(&dev->udev->dev, "%s: status:%d actual:%d\n", __func__,
+ urb->status, urb->actual_length);
+
+ if (urb->status == -EPROTO) {
+ dev_err(&dev->udev->dev, "%s: proto error\n", __func__);
+ /* save error so that subsequent read/write returns ESHUTDOWN */
+ dev->err = urb->status;
+ return;
+ }
+
+ cbs->read_complete_cb(cbs->ctxt,
+ urb->transfer_buffer,
+ urb->transfer_buffer_length,
+ urb->status < 0 ? urb->status : urb->actual_length);
+
+ dev->bytes_to_host += urb->actual_length;
+ dev->pending_reads--;
+}
+
+int diag_bridge_read(char *data, int size)
+{
+ struct urb *urb = NULL;
+ unsigned int pipe;
+ struct diag_bridge *dev = __dev;
+ int ret;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ if (!size) {
+ dev_err(&dev->udev->dev, "invalid size:%d\n", size);
+ return -EINVAL;
+ }
+
+ if (!dev->ifc) {
+ dev_err(&dev->udev->dev, "device is disconnected\n");
+ return -ENODEV;
+ }
+
+ /* if there was a previous unrecoverable error, just quit */
+ if (dev->err)
+ return -ESHUTDOWN;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ dev_err(&dev->udev->dev, "unable to allocate urb\n");
+ return -ENOMEM;
+ }
+
+ ret = usb_autopm_get_interface(dev->ifc);
+ if (ret < 0) {
+ dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret);
+ usb_free_urb(urb);
+ return ret;
+ }
+
+ pipe = usb_rcvbulkpipe(dev->udev, dev->in_epAddr);
+ usb_fill_bulk_urb(urb, dev->udev, pipe, data, size,
+ diag_bridge_read_cb, dev);
+ usb_anchor_urb(urb, &dev->submitted);
+ dev->pending_reads++;
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret);
+ dev->pending_reads--;
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ usb_autopm_put_interface(dev->ifc);
+ return ret;
+ }
+
+ usb_autopm_put_interface(dev->ifc);
+ usb_free_urb(urb);
+
+ return 0;
+}
+EXPORT_SYMBOL(diag_bridge_read);
+
+static void diag_bridge_write_cb(struct urb *urb)
+{
+ struct diag_bridge *dev = urb->context;
+ struct diag_bridge_ops *cbs = dev->ops;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ usb_autopm_put_interface_async(dev->ifc);
+
+ if (urb->status == -EPROTO) {
+ dev_err(&dev->udev->dev, "%s: proto error\n", __func__);
+ /* save error so that subsequent read/write returns ESHUTDOWN */
+ dev->err = urb->status;
+ return;
+ }
+
+ cbs->write_complete_cb(cbs->ctxt,
+ urb->transfer_buffer,
+ urb->transfer_buffer_length,
+ urb->status < 0 ? urb->status : urb->actual_length);
+
+ dev->bytes_to_mdm += urb->actual_length;
+ dev->pending_writes--;
+}
+
+int diag_bridge_write(char *data, int size)
+{
+ struct urb *urb = NULL;
+ unsigned int pipe;
+ struct diag_bridge *dev = __dev;
+ int ret;
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ if (!size) {
+ dev_err(&dev->udev->dev, "invalid size:%d\n", size);
+ return -EINVAL;
+ }
+
+ if (!dev->ifc) {
+ dev_err(&dev->udev->dev, "device is disconnected\n");
+ return -ENODEV;
+ }
+
+ /* if there was a previous unrecoverable error, just quit */
+ if (dev->err)
+ return -ESHUTDOWN;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ err("unable to allocate urb");
+ return -ENOMEM;
+ }
+
+ ret = usb_autopm_get_interface(dev->ifc);
+ if (ret < 0) {
+ dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret);
+ usb_free_urb(urb);
+ return ret;
+ }
+
+ pipe = usb_sndbulkpipe(dev->udev, dev->out_epAddr);
+ usb_fill_bulk_urb(urb, dev->udev, pipe, data, size,
+ diag_bridge_write_cb, dev);
+ usb_anchor_urb(urb, &dev->submitted);
+ dev->pending_writes++;
+
+ ret = usb_submit_urb(urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret);
+ dev->pending_writes--;
+ usb_unanchor_urb(urb);
+ usb_free_urb(urb);
+ usb_autopm_put_interface(dev->ifc);
+ return ret;
+ }
+
+ usb_free_urb(urb);
+
+ return 0;
+}
+EXPORT_SYMBOL(diag_bridge_write);
+
+static void diag_bridge_delete(struct kref *kref)
+{
+ struct diag_bridge *dev =
+ container_of(kref, struct diag_bridge, kref);
+
+ usb_put_dev(dev->udev);
+ __dev = 0;
+ kfree(dev);
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 512
+static ssize_t diag_read_stats(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct diag_bridge *dev = __dev;
+ char *buf;
+ int ret;
+
+ buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = scnprintf(buf, DEBUG_BUF_SIZE,
+ "epin:%d, epout:%d\n"
+ "bytes to host: %lu\n"
+ "bytes to mdm: %lu\n"
+ "pending reads: %u\n"
+ "pending writes: %u\n"
+ "last error: %d\n",
+ dev->in_epAddr, dev->out_epAddr,
+ dev->bytes_to_host, dev->bytes_to_mdm,
+ dev->pending_reads, dev->pending_writes,
+ dev->err);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret);
+ kfree(buf);
+ return ret;
+}
+
+static ssize_t diag_reset_stats(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct diag_bridge *dev = __dev;
+
+ dev->bytes_to_host = dev->bytes_to_mdm = 0;
+ dev->pending_reads = dev->pending_writes = 0;
+
+ return count;
+}
+
+const struct file_operations diag_stats_ops = {
+ .read = diag_read_stats,
+ .write = diag_reset_stats,
+};
+
+static struct dentry *dent;
+
+static void diag_bridge_debugfs_init(void)
+{
+ struct dentry *dfile;
+
+ dent = debugfs_create_dir("diag_bridge", 0);
+ if (IS_ERR(dent))
+ return;
+
+ dfile = debugfs_create_file("status", 0444, dent, 0, &diag_stats_ops);
+ if (!dfile || IS_ERR(dfile))
+ debugfs_remove(dent);
+}
+
+static void diag_bridge_debugfs_cleanup(void)
+{
+ if (dent) {
+ debugfs_remove_recursive(dent);
+ dent = NULL;
+ }
+}
+#else
+static inline void diag_bridge_debugfs_init(void) { }
+static inline void diag_bridge_debugfs_cleanup(void) { }
+#endif
+
+static int
+diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id)
+{
+ struct diag_bridge *dev;
+ struct usb_host_interface *ifc_desc;
+ struct usb_endpoint_descriptor *ep_desc;
+ int i;
+ int ret = -ENOMEM;
+ __u8 ifc_num;
+
+ dbg("%s: id:%lu", __func__, id->driver_info);
+
+ ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber;
+
+ /* is this interface supported ? */
+ if (ifc_num != id->driver_info)
+ return -ENODEV;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ pr_err("%s: unable to allocate dev\n", __func__);
+ return -ENOMEM;
+ }
+ dev->pdev = platform_device_alloc("diag_bridge", -1);
+ if (!dev->pdev) {
+ pr_err("%s: unable to allocate platform device\n", __func__);
+ kfree(dev);
+ return -ENOMEM;
+ }
+ __dev = dev;
+
+ dev->udev = usb_get_dev(interface_to_usbdev(ifc));
+ dev->ifc = ifc;
+ kref_init(&dev->kref);
+ init_usb_anchor(&dev->submitted);
+
+ ifc_desc = ifc->cur_altsetting;
+ for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) {
+ ep_desc = &ifc_desc->endpoint[i].desc;
+
+ if (!dev->in_epAddr && usb_endpoint_is_bulk_in(ep_desc))
+ dev->in_epAddr = ep_desc->bEndpointAddress;
+
+ if (!dev->out_epAddr && usb_endpoint_is_bulk_out(ep_desc))
+ dev->out_epAddr = ep_desc->bEndpointAddress;
+ }
+
+ if (!(dev->in_epAddr && dev->out_epAddr)) {
+ err("could not find bulk in and bulk out endpoints");
+ ret = -ENODEV;
+ goto error;
+ }
+
+ usb_set_intfdata(ifc, dev);
+ diag_bridge_debugfs_init();
+ platform_device_add(dev->pdev);
+
+ dev_dbg(&dev->udev->dev, "%s: complete\n", __func__);
+
+ return 0;
+
+error:
+ if (dev)
+ kref_put(&dev->kref, diag_bridge_delete);
+
+ return ret;
+}
+
+static void diag_bridge_disconnect(struct usb_interface *ifc)
+{
+ struct diag_bridge *dev = usb_get_intfdata(ifc);
+
+ dev_dbg(&dev->udev->dev, "%s:\n", __func__);
+
+ platform_device_del(dev->pdev);
+ diag_bridge_debugfs_cleanup();
+ kref_put(&dev->kref, diag_bridge_delete);
+ usb_set_intfdata(ifc, NULL);
+}
+
+static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message)
+{
+ struct diag_bridge *dev = usb_get_intfdata(ifc);
+ struct diag_bridge_ops *cbs = dev->ops;
+ int ret = 0;
+
+ if (cbs && cbs->suspend) {
+ ret = cbs->suspend(cbs->ctxt);
+ if (ret) {
+ dev_dbg(&dev->udev->dev,
+ "%s: diag veto'd suspend\n", __func__);
+ return ret;
+ }
+
+ usb_kill_anchored_urbs(&dev->submitted);
+ }
+
+ return ret;
+}
+
+static int diag_bridge_resume(struct usb_interface *ifc)
+{
+ struct diag_bridge *dev = usb_get_intfdata(ifc);
+ struct diag_bridge_ops *cbs = dev->ops;
+
+
+ if (cbs && cbs->resume)
+ cbs->resume(cbs->ctxt);
+
+ return 0;
+}
+
+#define VALID_INTERFACE_NUM 0
+static const struct usb_device_id diag_bridge_ids[] = {
+ { USB_DEVICE(0x5c6, 0x9001),
+ .driver_info = VALID_INTERFACE_NUM, },
+ { USB_DEVICE(0x5c6, 0x9034),
+ .driver_info = VALID_INTERFACE_NUM, },
+ { USB_DEVICE(0x5c6, 0x9048),
+ .driver_info = VALID_INTERFACE_NUM, },
+ { USB_DEVICE(0x5c6, 0x904C),
+ .driver_info = VALID_INTERFACE_NUM, },
+
+ {} /* terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, diag_bridge_ids);
+
+static struct usb_driver diag_bridge_driver = {
+ .name = "diag_bridge",
+ .probe = diag_bridge_probe,
+ .disconnect = diag_bridge_disconnect,
+ .suspend = diag_bridge_suspend,
+ .resume = diag_bridge_resume,
+ .id_table = diag_bridge_ids,
+ .supports_autosuspend = 1,
+};
+
+static int __init diag_bridge_init(void)
+{
+ int ret;
+
+ ret = usb_register(&diag_bridge_driver);
+ if (ret) {
+ err("%s: unable to register diag driver", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit diag_bridge_exit(void)
+{
+ usb_deregister(&diag_bridge_driver);
+}
+
+module_init(diag_bridge_init);
+module_exit(diag_bridge_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/diag_bridge_test.c b/drivers/usb/misc/diag_bridge_test.c
new file mode 100644
index 0000000..5bc0828
--- /dev/null
+++ b/drivers/usb/misc/diag_bridge_test.c
@@ -0,0 +1,207 @@
+/*
+ * 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/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/crc-ccitt.h>
+#include <mach/diag_bridge.h>
+
+#define DRIVER_DESC "USB host diag bridge driver test"
+#define DRIVER_VERSION "1.0"
+
+#define RD_BUF_SIZE 2048
+#define DIAG_TEST_CONNECTED 0
+
+struct diag_test_dev {
+ char *read_buf;
+ struct work_struct read_w;
+ unsigned long flags;
+
+ struct diag_bridge_ops ops;
+};
+static struct diag_test_dev *__dev;
+static struct dentry *dent;
+
+static void
+diag_test_read_complete_cb(void *d, char *buf, size_t size, size_t actual)
+{
+ if (actual < 0) {
+ pr_err("%s: read complete err\n", __func__);
+ return;
+ }
+
+ print_hex_dump(KERN_INFO, "to_host:", 0, 1, 1, buf, actual, false);
+}
+static void diag_test_read_work(struct work_struct *w)
+{
+ struct diag_test_dev *dev =
+ container_of(w, struct diag_test_dev, read_w);
+
+ memset(dev->read_buf, 0, RD_BUF_SIZE);
+ diag_bridge_read(dev->read_buf, RD_BUF_SIZE);
+}
+
+static void
+diag_test_write_complete_cb(void *d, char *buf, size_t size, size_t actual)
+{
+ struct diag_test_dev *dev = d;
+
+ if (actual > 0)
+ schedule_work(&dev->read_w);
+}
+
+#if defined(CONFIG_DEBUG_FS)
+#define DEBUG_BUF_SIZE 1024
+static ssize_t send_ping_cmd(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct diag_test_dev *dev = __dev;
+ unsigned char *buf;
+ int temp = sizeof(unsigned char) * 4;
+
+ if (!dev)
+ return -ENODEV;
+
+ buf = kmalloc(temp, GFP_KERNEL);
+ if (!buf) {
+ pr_err("%s: unable to allocate mem for ping cmd\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ /* hdlc encoded ping command */
+ buf[0] = 0x0C;
+ buf[1] = 0x14;
+ buf[2] = 0x3A;
+ buf[3] = 0x7E;
+
+ diag_bridge_write(buf, temp);
+
+ return count;
+}
+
+const struct file_operations diag_test_ping_ops = {
+ .write = send_ping_cmd,
+};
+
+static void diag_test_debug_init(void)
+{
+ struct dentry *dfile;
+
+ dent = debugfs_create_dir("diag_test", 0);
+ if (IS_ERR(dent))
+ return;
+
+ dfile = debugfs_create_file("send_ping", 0444, dent,
+ 0, &diag_test_ping_ops);
+ if (!dfile || IS_ERR(dfile))
+ debugfs_remove(dent);
+}
+#else
+static void diag_test_debug_init(void) { }
+#endif
+
+static int diag_test_remove(struct platform_device *pdev)
+{
+ diag_bridge_close();
+
+ if (dent) {
+ debugfs_remove_recursive(dent);
+ dent = NULL;
+ }
+
+ return 0;
+}
+
+static int diag_test_probe(struct platform_device *pdev)
+{
+ struct diag_test_dev *dev = __dev;
+ int ret = 0;
+
+ pr_info("%s:\n", __func__);
+
+ ret = diag_bridge_open(&dev->ops);
+ if (ret)
+ pr_err("diag open failed: %d", ret);
+
+
+ diag_test_debug_init();
+
+ return ret;
+}
+
+static struct platform_driver diag_test = {
+ .remove = diag_test_remove,
+ .probe = diag_test_probe,
+ .driver = {
+ .name = "diag_bridge",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init diag_test_init(void)
+{
+ struct diag_test_dev *dev;
+ int ret = 0;
+
+ pr_info("%s\n", __func__);
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ __dev = dev;
+
+ dev->ops.read_complete_cb = diag_test_read_complete_cb;
+ dev->ops.write_complete_cb = diag_test_write_complete_cb;
+ 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, diag_test_read_work);
+
+ ret = platform_driver_register(&diag_test);
+ if (ret)
+ pr_err("%s: platform driver %s register failed %d\n",
+ __func__, diag_test.driver.name, ret);
+
+ return ret;
+}
+
+static void __exit diag_test_exit(void)
+{
+ struct diag_test_dev *dev = __dev;
+
+ pr_info("%s:\n", __func__);
+
+ if (test_bit(DIAG_TEST_CONNECTED, &dev->flags))
+ diag_bridge_close();
+
+ kfree(dev->read_buf);
+ kfree(dev);
+
+}
+
+module_init(diag_test_init);
+module_exit(diag_test_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/exynos-usb-switch.c b/drivers/usb/misc/exynos-usb-switch.c
new file mode 100644
index 0000000..519c845
--- /dev/null
+++ b/drivers/usb/misc/exynos-usb-switch.c
@@ -0,0 +1,642 @@
+/*
+ * exynos-usb-switch.c - USB switch driver for Exynos
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * Yulgon Kim <yulgon.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <plat/devs.h>
+#include <plat/ehci.h>
+#include <plat/usbgadget.h>
+#include <plat/usb-switch.h>
+
+#include <mach/regs-clock.h>
+
+#include "../gadget/s3c_udc.h"
+#include "../gadget/exynos_ss_udc.h"
+#include "exynos-usb-switch.h"
+
+#define DRIVER_DESC "Exynos USB Switch Driver"
+#define SWITCH_WAIT_TIME 500
+#define WAIT_TIMES 10
+
+static const char switch_name[] = "exynos_usb_switch";
+static struct exynos_usb_switch *our_switch;
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+void exynos_usb_cable_connect(void)
+{
+ samsung_cable_check_status(1);
+}
+
+void exynos_usb_cable_disconnect(void)
+{
+ samsung_cable_check_status(0);
+}
+#endif
+
+static int is_host_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_host_detect)
+ return 0;
+ return !gpio_get_value(usb_switch->gpio_host_detect);
+}
+
+static int is_device_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_device_detect)
+ return 0;
+ return gpio_get_value(usb_switch->gpio_device_detect);
+}
+
+static int is_drd_host_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_drd_host_detect)
+ return 0;
+ return !gpio_get_value(usb_switch->gpio_drd_host_detect);
+}
+
+static int is_drd_device_detect(struct exynos_usb_switch *usb_switch)
+{
+ if (!usb_switch->gpio_drd_device_detect)
+ return 0;
+ return gpio_get_value(usb_switch->gpio_drd_device_detect);
+}
+
+static void set_host_vbus(struct exynos_usb_switch *usb_switch, int value)
+{
+ gpio_set_value(usb_switch->gpio_host_vbus, value);
+}
+
+static int exynos_change_usb_mode(struct exynos_usb_switch *usb_switch,
+ enum usb_cable_status mode)
+{
+ struct s3c_udc *udc;
+ struct exynos_ss_udc *ss_udc;
+ unsigned long cur_mode = usb_switch->connect;
+ int ret = 0;
+
+ if (test_bit(USB_DEVICE_ATTACHED, &cur_mode) ||
+ test_bit(USB_HOST_ATTACHED, &cur_mode)) {
+ if (mode == USB_DEVICE_ATTACHED ||
+ mode == USB_HOST_ATTACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ } else if (test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode) ||
+ test_bit(USB_DRD_HOST_ATTACHED, &cur_mode)) {
+ if (mode == USB_DRD_DEVICE_ATTACHED ||
+ mode == USB_DRD_HOST_ATTACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ }
+
+ if (!test_bit(USB_DEVICE_ATTACHED, &cur_mode) &&
+ mode == USB_DEVICE_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ } else if (!test_bit(USB_HOST_ATTACHED, &cur_mode) &&
+ mode == USB_HOST_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ } else if (!test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode) &&
+ mode == USB_DRD_DEVICE_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ } else if (!test_bit(USB_DRD_HOST_ATTACHED, &cur_mode) &&
+ mode == USB_DRD_HOST_DETACHED) {
+ printk(KERN_DEBUG "Skip request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+
+ switch (mode) {
+ case USB_DEVICE_DETACHED:
+ if (test_bit(USB_HOST_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ udc = dev_get_drvdata(usb_switch->s3c_udc_dev);
+ if (udc && udc->gadget.ops && udc->gadget.ops->vbus_session)
+ udc->gadget.ops->vbus_session(&udc->gadget, 0);
+ clear_bit(USB_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DEVICE_ATTACHED:
+ udc = dev_get_drvdata(usb_switch->s3c_udc_dev);
+ if (udc && udc->gadget.ops && udc->gadget.ops->vbus_session)
+ udc->gadget.ops->vbus_session(&udc->gadget, 1);
+ set_bit(USB_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_HOST_DETACHED:
+ if (test_bit(USB_DEVICE_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+ if (usb_switch->ohci_dev)
+ pm_runtime_put(usb_switch->ohci_dev);
+ if (usb_switch->ehci_dev)
+ pm_runtime_put(usb_switch->ehci_dev);
+ if (usb_switch->gpio_host_vbus)
+ set_host_vbus(usb_switch, 0);
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+ exynos_usb_cable_disconnect();
+#endif
+ clear_bit(USB_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_HOST_ATTACHED:
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+ exynos_usb_cable_connect();
+#endif
+ if (usb_switch->gpio_host_vbus)
+ set_host_vbus(usb_switch, 1);
+
+ if (usb_switch->ehci_dev)
+ pm_runtime_get_sync(usb_switch->ehci_dev);
+ if (usb_switch->ohci_dev)
+ pm_runtime_get_sync(usb_switch->ohci_dev);
+ set_bit(USB_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_DEVICE_DETACHED:
+ if (test_bit(USB_DRD_HOST_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+
+ ss_udc = dev_get_drvdata(usb_switch->exynos_udc_dev);
+ if (ss_udc && ss_udc->gadget.ops &&
+ ss_udc->gadget.ops->vbus_session)
+ ss_udc->gadget.ops->vbus_session(&ss_udc->gadget, 0);
+ clear_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_DEVICE_ATTACHED:
+ ss_udc = dev_get_drvdata(usb_switch->exynos_udc_dev);
+ if (ss_udc && ss_udc->gadget.ops &&
+ ss_udc->gadget.ops->vbus_session)
+ ss_udc->gadget.ops->vbus_session(&ss_udc->gadget, 1);
+ set_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_HOST_DETACHED:
+ if (test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode)) {
+ printk(KERN_ERR "Abnormal request %d, current %lu\n",
+ mode, cur_mode);
+ return -EPERM;
+ }
+
+ if (usb_switch->xhci_dev)
+ pm_runtime_put_sync(usb_switch->xhci_dev);
+#if defined(CONFIG_BATTERY_SAMSUNG)
+ exynos_usb_cable_disconnect();
+#endif
+ clear_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ case USB_DRD_HOST_ATTACHED:
+#if defined(CONFIG_BATTERY_SAMSUNG)
+ exynos_usb_cable_connect();
+#endif
+ if (usb_switch->xhci_dev)
+ pm_runtime_get_sync(usb_switch->xhci_dev);
+ set_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect);
+ break;
+ default:
+ printk(KERN_ERR "Does not changed\n");
+ }
+ printk(KERN_INFO "usb cable = %d\n", mode);
+
+ return ret;
+}
+
+static void exnos_usb_switch_worker(struct work_struct *work)
+{
+ struct exynos_usb_switch *usb_switch =
+ container_of(work, struct exynos_usb_switch, switch_work);
+ int cnt = 0;
+
+ mutex_lock(&usb_switch->mutex);
+ /* If already device detached or host_detected, */
+ if (!is_device_detect(usb_switch) || is_host_detect(usb_switch))
+ goto done;
+ if (!usb_switch->ehci_dev || !usb_switch->ohci_dev)
+ goto detect;
+
+ while (!pm_runtime_suspended(usb_switch->ehci_dev) ||
+ !pm_runtime_suspended(usb_switch->ohci_dev)) {
+
+ mutex_unlock(&usb_switch->mutex);
+ msleep(SWITCH_WAIT_TIME);
+ mutex_lock(&usb_switch->mutex);
+
+ /* If already device detached or host_detected, */
+ if (!is_device_detect(usb_switch) || is_host_detect(usb_switch))
+ goto done;
+
+ if (cnt++ > WAIT_TIMES) {
+ printk(KERN_ERR "%s:device not attached by host\n",
+ __func__);
+ goto done;
+ }
+
+ }
+
+ if (cnt > 1)
+ printk(KERN_INFO "Device wait host power during %d\n", (cnt-1));
+detect:
+ /* Check Device, VBUS PIN high active */
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED);
+done:
+ mutex_unlock(&usb_switch->mutex);
+}
+
+static void exnos_usb_drd_switch_worker(struct work_struct *work)
+{
+ struct exynos_usb_switch *usb_switch =
+ container_of(work, struct exynos_usb_switch, switch_drd_work);
+ int cnt = 0;
+
+ mutex_lock(&usb_switch->mutex);
+ /* If already device detached or host_detected, */
+ if (!is_drd_device_detect(usb_switch) || is_drd_host_detect(usb_switch))
+ goto done;
+ if (!usb_switch->xhci_dev)
+ goto detect;
+
+ while (!pm_runtime_suspended(usb_switch->xhci_dev) ||
+ usb_switch->xhci_dev->power.is_suspended) {
+ mutex_unlock(&usb_switch->mutex);
+ msleep(SWITCH_WAIT_TIME);
+ mutex_lock(&usb_switch->mutex);
+
+ /* If already device detached or host_detected, */
+ if (!is_drd_device_detect(usb_switch) ||
+ is_drd_host_detect(usb_switch))
+ goto done;
+
+ if (cnt++ > WAIT_TIMES) {
+ printk(KERN_ERR "%s:device not attached by host\n",
+ __func__);
+ goto done;
+ }
+
+ }
+
+ if (cnt > 1)
+ printk(KERN_INFO "Device wait host power during %d\n", (cnt-1));
+
+detect:
+ /* Check Device, VBUS PIN high active */
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_ATTACHED);
+done:
+ mutex_unlock(&usb_switch->mutex);
+}
+
+static irqreturn_t exynos_host_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ if (is_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED);
+ else
+ exynos_change_usb_mode(usb_switch, USB_HOST_DETACHED);
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos_device_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ /* Debounce connect delay */
+ msleep(20);
+
+ if (is_host_detect(usb_switch))
+ printk(KERN_DEBUG "Not expected situation\n");
+ else if (is_device_detect(usb_switch)) {
+ if (usb_switch->gpio_host_vbus)
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED);
+ else
+ queue_work(usb_switch->workqueue, &usb_switch->switch_work);
+ } else {
+ /* VBUS PIN low */
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_DETACHED);
+ }
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos_drd_host_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ if (is_drd_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch, USB_DRD_HOST_ATTACHED);
+ else
+ exynos_change_usb_mode(usb_switch, USB_DRD_HOST_DETACHED);
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t exynos_drd_device_detect_thread(int irq, void *data)
+{
+ struct exynos_usb_switch *usb_switch = data;
+
+ mutex_lock(&usb_switch->mutex);
+
+ /* Debounce connect delay */
+ msleep(20);
+
+ if (is_drd_host_detect(usb_switch))
+ printk(KERN_DEBUG "Not expected situation\n");
+ else if (is_drd_device_detect(usb_switch))
+ queue_work(usb_switch->workqueue, &usb_switch->switch_drd_work);
+ else {
+ /* VBUS PIN low */
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_DETACHED);
+ }
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return IRQ_HANDLED;
+}
+
+static int exynos_usb_status_init(struct exynos_usb_switch *usb_switch)
+{
+ mutex_lock(&usb_switch->mutex);
+
+ /* 2.0 USB */
+ if (!test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect) ||
+ !test_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect)) {
+ if (is_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED);
+ else if (is_device_detect(usb_switch)) {
+ if (usb_switch->gpio_host_vbus)
+ exynos_change_usb_mode(usb_switch,
+ USB_DEVICE_ATTACHED);
+ else
+ queue_work(usb_switch->workqueue,
+ &usb_switch->switch_work);
+ }
+ }
+
+ /* 3.0 USB */
+ if (!test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect) ||
+ !test_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect)) {
+ if (is_drd_host_detect(usb_switch))
+ exynos_change_usb_mode(usb_switch,
+ USB_DRD_HOST_ATTACHED);
+ else if (is_drd_device_detect(usb_switch))
+ queue_work(usb_switch->workqueue,
+ &usb_switch->switch_drd_work);
+ }
+
+ mutex_unlock(&usb_switch->mutex);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos_usbswitch_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ mutex_lock(&usb_switch->mutex);
+ if (test_bit(USB_DEVICE_ATTACHED, &usb_switch->connect))
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_DETACHED);
+
+ if (test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect))
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_DETACHED);
+
+ usb_switch->connect = 0;
+ mutex_unlock(&usb_switch->mutex);
+
+ return 0;
+}
+
+static int exynos_usbswitch_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev);
+
+ dev_dbg(dev, "%s\n", __func__);
+ exynos_usb_status_init(usb_switch);
+
+ return 0;
+}
+#else
+#define exynos_usbswitch_suspend NULL
+#define exynos_usbswitch_resume NULL
+#endif
+
+static int __devinit exynos_usbswitch_probe(struct platform_device *pdev)
+{
+ struct s5p_usbswitch_platdata *pdata = dev_get_platdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct exynos_usb_switch *usb_switch;
+ int irq;
+ int ret = 0;
+
+ usb_switch = kzalloc(sizeof(struct exynos_usb_switch), GFP_KERNEL);
+ if (!usb_switch) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ our_switch = usb_switch;
+ mutex_init(&usb_switch->mutex);
+ usb_switch->workqueue = create_singlethread_workqueue("usb_switch");
+ INIT_WORK(&usb_switch->switch_work, exnos_usb_switch_worker);
+ INIT_WORK(&usb_switch->switch_drd_work, exnos_usb_drd_switch_worker);
+
+ usb_switch->gpio_host_detect = pdata->gpio_host_detect;
+ usb_switch->gpio_device_detect = pdata->gpio_device_detect;
+ usb_switch->gpio_host_vbus = pdata->gpio_host_vbus;
+ usb_switch->gpio_drd_host_detect = pdata->gpio_drd_host_detect;
+ usb_switch->gpio_drd_device_detect = pdata->gpio_drd_device_detect;
+
+ usb_switch->ehci_dev = pdata->ehci_dev;
+ usb_switch->ohci_dev = pdata->ohci_dev;
+ usb_switch->xhci_dev = pdata->xhci_dev;
+ usb_switch->s3c_udc_dev = pdata->s3c_udc_dev;
+ usb_switch->exynos_udc_dev = pdata->exynos_udc_dev;
+
+ /* USB Device detect IRQ */
+ irq = platform_get_irq(pdev, 1);
+ if (irq > 0 && usb_switch->s3c_udc_dev) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_device_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "DEVICE_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request device irq %d\n", irq);
+ goto fail;
+ }
+ usb_switch->device_detect_irq = irq;
+ } else if (usb_switch->s3c_udc_dev)
+ exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED);
+ else
+ dev_info(dev, "Disable device detect IRQ\n");
+
+ /* USB Host detect IRQ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq > 0 && (usb_switch->ehci_dev || usb_switch->ohci_dev)) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_host_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "HOST_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request host irq %d\n", irq);
+ goto fail_gpio_device_detect;
+ }
+ usb_switch->host_detect_irq = irq;
+ } else if (usb_switch->ehci_dev || usb_switch->ohci_dev)
+ exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED);
+ else
+ dev_info(dev, "Disable host detect IRQ\n");
+
+
+ /* USB DRD Device detect IRQ */
+ irq = platform_get_irq(pdev, 3);
+ if (irq > 0 && usb_switch->exynos_udc_dev) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_drd_device_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "DRD_DEVICE_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request drd device irq %d\n",
+ irq);
+ goto fail_gpio_host_detect;
+ }
+ usb_switch->device_drd_detect_irq = irq;
+ } else if (usb_switch->exynos_udc_dev)
+ exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_ATTACHED);
+ else
+ dev_info(dev, "Disable drd device detect IRQ\n");
+
+ /* USB DRD Host detect IRQ */
+ irq = platform_get_irq(pdev, 2);
+ if (irq > 0 && usb_switch->xhci_dev) {
+ ret = request_threaded_irq(irq, NULL,
+ exynos_drd_host_detect_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "DRD_HOST_DETECT", usb_switch);
+ if (ret) {
+ dev_err(dev, "Failed to request drd host irq %d\n",
+ irq);
+ goto fail_gpio_drd_device_detect;
+ }
+ usb_switch->host_drd_detect_irq = irq;
+ } else if (usb_switch->xhci_dev)
+ exynos_change_usb_mode(usb_switch, USB_DRD_HOST_ATTACHED);
+ else
+ dev_info(dev, "Disable drd host detect IRQ\n");
+
+ exynos_usb_status_init(usb_switch);
+
+ platform_set_drvdata(pdev, usb_switch);
+
+ return ret;
+fail_gpio_drd_device_detect:
+ free_irq(usb_switch->device_drd_detect_irq, usb_switch);
+fail_gpio_host_detect:
+ free_irq(usb_switch->host_detect_irq, usb_switch);
+fail_gpio_device_detect:
+ free_irq(usb_switch->device_detect_irq, usb_switch);
+fail:
+ cancel_work_sync(&usb_switch->switch_work);
+ destroy_workqueue(usb_switch->workqueue);
+ mutex_destroy(&usb_switch->mutex);
+ kfree(usb_switch);
+ return ret;
+}
+
+static int __devexit exynos_usbswitch_remove(struct platform_device *pdev)
+{
+ struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev);
+
+ free_irq(usb_switch->host_drd_detect_irq, usb_switch);
+ free_irq(usb_switch->device_drd_detect_irq, usb_switch);
+ free_irq(usb_switch->host_detect_irq, usb_switch);
+ free_irq(usb_switch->device_detect_irq, usb_switch);
+ platform_set_drvdata(pdev, 0);
+
+ cancel_work_sync(&usb_switch->switch_work);
+ destroy_workqueue(usb_switch->workqueue);
+ mutex_destroy(&usb_switch->mutex);
+ kfree(usb_switch);
+
+ return 0;
+}
+
+static const struct dev_pm_ops exynos_usbswitch_pm_ops = {
+ .suspend = exynos_usbswitch_suspend,
+ .resume = exynos_usbswitch_resume,
+};
+
+static struct platform_driver exynos_usbswitch_driver = {
+ .probe = exynos_usbswitch_probe,
+ .remove = __devexit_p(exynos_usbswitch_remove),
+ .driver = {
+ .name = "exynos-usb-switch",
+ .owner = THIS_MODULE,
+ .pm = &exynos_usbswitch_pm_ops,
+ },
+};
+
+static int __init exynos_usbswitch_init(void)
+{
+ int ret;
+
+ ret = platform_device_register(&s5p_device_usbswitch);
+ if (ret < 0)
+ return ret;
+
+ ret = platform_driver_register(&exynos_usbswitch_driver);
+ if (!ret)
+ printk(KERN_INFO "%s: " DRIVER_DESC "\n", switch_name);
+
+ return ret;
+}
+late_initcall(exynos_usbswitch_init);
+
+static void __exit exynos_usbswitch_exit(void)
+{
+ platform_driver_unregister(&exynos_usbswitch_driver);
+}
+module_exit(exynos_usbswitch_exit);
+
+MODULE_DESCRIPTION("Exynos USB switch driver");
+MODULE_AUTHOR("<yulgon.kim@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/exynos-usb-switch.h b/drivers/usb/misc/exynos-usb-switch.h
new file mode 100644
index 0000000..3749987
--- /dev/null
+++ b/drivers/usb/misc/exynos-usb-switch.h
@@ -0,0 +1,63 @@
+/*
+ * exynos-usb-switch.h - USB switch driver for Exynos
+ *
+ * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd.
+ * Yulgon Kim <yulgon.kim@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+#ifndef __EXYNOS_USB_SWITCH
+#define __EXYNOS_USB_SWITCH
+
+#define SWITCH_WAIT_TIME 500
+#define WAIT_TIMES 10
+
+enum usb_cable_status {
+ USB_DEVICE_ATTACHED,
+ USB_HOST_ATTACHED,
+ USB_DRD_DEVICE_ATTACHED,
+ USB_DRD_HOST_ATTACHED,
+ USB_DEVICE_DETACHED,
+ USB_HOST_DETACHED,
+ USB_DRD_DEVICE_DETACHED,
+ USB_DRD_HOST_DETACHED,
+};
+
+struct exynos_usb_switch {
+ unsigned long connect;
+
+ unsigned int host_detect_irq;
+ unsigned int device_detect_irq;
+ unsigned int host_drd_detect_irq;
+ unsigned int device_drd_detect_irq;
+ unsigned int gpio_host_detect;
+ unsigned int gpio_device_detect;
+ unsigned int gpio_host_vbus;
+ unsigned int gpio_drd_host_detect;
+ unsigned int gpio_drd_device_detect;
+
+ struct device *ehci_dev;
+ struct device *ohci_dev;
+ struct device *xhci_dev;
+
+ struct device *s3c_udc_dev;
+ struct device *exynos_udc_dev;
+
+ struct workqueue_struct *workqueue;
+ struct work_struct switch_work;
+ struct work_struct switch_drd_work;
+ struct mutex mutex;
+ atomic_t usb_status;
+ int (*get_usb_mode)(void);
+ int (*change_usb_mode)(int mode);
+};
+
+extern int s5p_ehci_port_power_off(struct platform_device *pdev);
+extern int s5p_ohci_port_power_off(struct platform_device *pdev);
+
+extern int s5p_ehci_port_power_on(struct platform_device *pdev);
+extern int s5p_ohci_port_power_on(struct platform_device *pdev);
+
+#endif
diff --git a/drivers/usb/misc/mdm_ctrl_bridge.c b/drivers/usb/misc/mdm_ctrl_bridge.c
new file mode 100644
index 0000000..4755790
--- /dev/null
+++ b/drivers/usb/misc/mdm_ctrl_bridge.c
@@ -0,0 +1,768 @@
+/* Copyright (c) 2011-2012, 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 const *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
+
+#define SUSPENDED BIT(0)
+
+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_anchor tx_deferred;
+ struct usb_ctrlrequest *in_ctlreq;
+
+ struct bridge *brdg;
+ struct platform_device *pdev;
+
+ unsigned long flags;
+
+ /* 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*/
+ usb_anchor_urb(dev->inturb, &dev->tx_submitted);
+ status = usb_submit_urb(dev->inturb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&udev->dev,
+ "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+ usb_unanchor_urb(dev->inturb);
+ }
+ }
+}
+
+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);
+
+ usb_anchor_urb(dev->readurb, &dev->tx_submitted);
+ status = usb_submit_urb(dev->readurb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&udev->dev,
+ "%s: Error submitting Read URB %d\n",
+ __func__, status);
+ usb_unanchor_urb(dev->readurb);
+ 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:
+ usb_anchor_urb(urb, &dev->tx_submitted);
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status) {
+ dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n",
+ __func__, status);
+ usb_unanchor_urb(urb);
+ }
+}
+
+int ctrl_bridge_start_read(struct ctrl_bridge *dev)
+{
+ int retval = 0;
+
+ if (!dev->inturb) {
+ dev_err(&dev->udev->dev, "%s: inturb is NULL\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!dev->inturb->anchor) {
+ usb_anchor_urb(dev->inturb, &dev->tx_submitted);
+ retval = usb_submit_urb(dev->inturb, GFP_KERNEL);
+ if (retval < 0) {
+ dev_err(&dev->udev->dev,
+ "%s error submitting int urb %d\n",
+ __func__, retval);
+ usb_unanchor_urb(dev->inturb);
+ }
+ }
+
+ return retval;
+}
+
+int ctrl_bridge_open(struct bridge *brdg)
+{
+ struct ctrl_bridge *dev;
+ int ret;
+
+ 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;
+
+ ret = usb_autopm_get_interface(dev->intf);
+ if (ret < 0) {
+ dev_err(&dev->udev->dev, "%s autopm_get fail: %d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ ret = ctrl_bridge_start_read(dev);
+ usb_autopm_put_interface(dev->intf);
+ return ret;
+}
+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);
+
+ dev->brdg = NULL;
+}
+EXPORT_SYMBOL(ctrl_bridge_close);
+
+static void ctrl_write_callback(struct urb *urb)
+{
+ struct ctrl_bridge *dev = urb->context;
+
+ 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);
+ usb_autopm_put_interface_async(dev->intf);
+}
+
+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, dev);
+
+ 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;
+ }
+
+ if (test_bit(SUSPENDED, &dev->flags)) {
+ usb_anchor_urb(writeurb, &dev->tx_deferred);
+ goto deferred;
+ }
+
+ 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;
+ }
+deferred:
+ 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;
+
+ set_bit(SUSPENDED, &dev->flags);
+ usb_kill_anchored_urbs(&dev->tx_submitted);
+
+ return 0;
+}
+
+int ctrl_bridge_resume(unsigned int id)
+{
+ struct ctrl_bridge *dev;
+ struct urb *urb;
+
+ if (id >= MAX_BRIDGE_DEVICES)
+ return -EINVAL;
+
+ dev = __dev[id];
+ if (!dev)
+ return -ENODEV;
+
+ if (!test_and_clear_bit(SUSPENDED, &dev->flags))
+ return 0;
+
+ /* submit pending write requests */
+ while ((urb = usb_get_from_anchor(&dev->tx_deferred))) {
+ int ret;
+ usb_anchor_urb(urb, &dev->tx_submitted);
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret < 0) {
+ usb_unanchor_urb(urb);
+ kfree(urb->setup_packet);
+ kfree(urb->transfer_buffer);
+ usb_free_urb(urb);
+ usb_autopm_put_interface_async(dev->intf);
+ }
+ }
+
+ /* if the bridge is open, resume reading */
+ if (dev->brdg)
+ return ctrl_bridge_start_read(dev);
+
+ return 0;
+}
+
+#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"
+ "suspended: %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,
+ test_bit(SUSPENDED, &dev->flags));
+ }
+
+ 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);
+ init_usb_anchor(&dev->tx_deferred);
+
+ /*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__);
+
+ platform_device_del(dev->pdev);
+
+ kfree(dev->in_ctlreq);
+ kfree(dev->readbuf);
+ kfree(dev->intbuf);
+
+ usb_free_urb(dev->readurb);
+ usb_free_urb(dev->inturb);
+
+ __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..6af9664
--- /dev/null
+++ b/drivers/usb/misc/mdm_data_bridge.c
@@ -0,0 +1,1079 @@
+/* Copyright (c) 2011-2012, 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 500
+#define FLOW_CTRL_EN_THRESHOLD 500
+#define FLOW_CTRL_DISABLE 300
+#define FLOW_CTRL_SUPPORT 1
+
+static const char const *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);
+
+static unsigned tx_urb_mult = 20;
+module_param(tx_urb_mult, 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;
+ int id;
+
+ unsigned int bulk_in;
+ unsigned int bulk_out;
+ int err;
+
+ /* 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 unsigned int get_timestamp(void);
+static void dbg_timestamp(char *, struct sk_buff *);
+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 timestamp_info *info;
+ 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++;
+ info = (struct timestamp_info *)skb->cb;
+ info->rx_done_sent = get_timestamp();
+ /* 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);
+ while (!list_empty(&dev->rx_idle)) {
+ if (dev->rx_done.qlen > stop_submit_urb_limit)
+ break;
+
+ 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) {
+ list_add_tail(&rx_idle->urb_list, &dev->rx_idle);
+ 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 timestamp_info *info = (struct timestamp_info *)skb->cb;
+ struct data_bridge *dev = info->dev;
+ bool queue = 0;
+
+ brdg = dev->brdg;
+ skb_put(skb, urb->actual_length);
+
+ switch (urb->status) {
+ case 0: /* success */
+ queue = 1;
+ info->rx_done = get_timestamp();
+ 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;
+ struct timestamp_info *info;
+ int retval = -EINVAL;
+ unsigned int created;
+
+ created = get_timestamp();
+ skb = alloc_skb(RMNET_RX_BUFSIZE, flags);
+ if (!skb)
+ return -ENOMEM;
+
+ info = (struct timestamp_info *)skb->cb;
+ info->dev = dev;
+ info->created = created;
+
+ 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);
+ info->rx_queued = get_timestamp();
+ 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);
+
+ 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;
+ dev->err = 0;
+ 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 timestamp_info *info = (struct timestamp_info *)skb->cb;
+ struct data_bridge *dev = info->dev;
+ struct bridge *brdg = dev->brdg;
+ int pending;
+
+ pr_debug("%s: dev:%p\n", __func__, dev);
+
+ switch (urb->status) {
+ case 0: /*success*/
+ dbg_timestamp("UL", skb);
+ break;
+ case -EPROTO:
+ dev->err = -EPROTO;
+ 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 timestamp_info *info = (struct timestamp_info *)skb->cb;
+ struct data_bridge *dev = __dev[id];
+ struct bridge *brdg;
+
+ if (!dev || !dev->brdg || dev->err || !usb_get_intfdata(dev->intf))
+ return -ENODEV;
+
+ brdg = dev->brdg;
+ if (!brdg)
+ return -ENODEV;
+
+ 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 */
+ info->dev = dev;
+ info->tx_queued = get_timestamp();
+
+ 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);
+
+ if (atomic_read(&dev->pending_txurbs) % tx_urb_mult)
+ txurb->transfer_flags |= URB_NO_INTERRUPT;
+
+ 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;
+
+ if (!test_and_clear_bit(SUSPENDED, &dev->flags))
+ return 0;
+
+ 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--;
+ }
+
+ 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);
+
+ oldstate = iface->dev.power.power_state.event;
+ iface->dev.power.power_state.event = PM_EVENT_ON;
+
+ if (oldstate & PM_EVENT_SUSPEND) {
+ retval = data_bridge_resume(dev);
+ if (!retval)
+ retval = ctrl_bridge_resume(dev->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);
+
+ retval = data_bridge_suspend(dev, message);
+ if (!retval) {
+ retval = ctrl_bridge_suspend(dev->id);
+ intf->dev.power.power_state.event = message.event;
+ }
+
+ 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->id = id;
+ 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 unsigned int record_timestamp;
+module_param(record_timestamp, uint, S_IRUGO | S_IWUSR);
+
+static struct timestamp_buf dbg_data = {
+ .idx = 0,
+ .lck = __RW_LOCK_UNLOCKED(lck)
+};
+
+/*get_timestamp - returns time of day in us */
+static unsigned int get_timestamp(void)
+{
+ struct timeval tval;
+ unsigned int stamp;
+
+ if (!record_timestamp)
+ return 0;
+
+ do_gettimeofday(&tval);
+ /* 2^32 = 4294967296. Limit to 4096s. */
+ stamp = tval.tv_sec & 0xFFF;
+ stamp = stamp * 1000000 + tval.tv_usec;
+ return stamp;
+}
+
+static void dbg_inc(unsigned *idx)
+{
+ *idx = (*idx + 1) & (DBG_DATA_MAX-1);
+}
+
+/**
+* dbg_timestamp - Stores timestamp values of a SKB life cycle
+* to debug buffer
+* @event: "UL": Uplink Data
+* @skb: SKB used to store timestamp values to debug buffer
+*/
+static void dbg_timestamp(char *event, struct sk_buff * skb)
+{
+ unsigned long flags;
+ struct timestamp_info *info = (struct timestamp_info *)skb->cb;
+
+ if (!record_timestamp)
+ return;
+
+ write_lock_irqsave(&dbg_data.lck, flags);
+
+ scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG,
+ "%p %u[%s] %u %u %u %u %u %u\n",
+ skb, skb->len, event, info->created, info->rx_queued,
+ info->rx_done, info->rx_done_sent, info->tx_queued,
+ get_timestamp());
+
+ dbg_inc(&dbg_data.idx);
+
+ write_unlock_irqrestore(&dbg_data.lck, flags);
+}
+
+/* show_timestamp: displays the timestamp buffer */
+static ssize_t show_timestamp(struct file *file, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ unsigned long flags;
+ unsigned i;
+ unsigned j = 0;
+ char *buf;
+ int ret = 0;
+
+ if (!record_timestamp)
+ return 0;
+
+ buf = kzalloc(sizeof(char) * 4 * DEBUG_BUF_SIZE, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ read_lock_irqsave(&dbg_data.lck, flags);
+
+ i = dbg_data.idx;
+ for (dbg_inc(&i); i != dbg_data.idx; dbg_inc(&i)) {
+ if (!strnlen(dbg_data.buf[i], DBG_DATA_MSG))
+ continue;
+ j += scnprintf(buf + j, (4 * DEBUG_BUF_SIZE) - j,
+ "%s\n", dbg_data.buf[i]);
+ }
+
+ read_unlock_irqrestore(&dbg_data.lck, flags);
+
+ ret = simple_read_from_buffer(ubuf, count, ppos, buf, j);
+
+ kfree(buf);
+
+ return ret;
+}
+
+const struct file_operations data_timestamp_ops = {
+ .read = show_timestamp,
+};
+
+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"
+ "dev err: %d\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,
+ dev->err,
+ 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,
+};
+
+static struct dentry *data_dent;
+static struct dentry *data_dfile_stats;
+static struct dentry *data_dfile_tstamp;
+
+static void data_bridge_debugfs_init(void)
+{
+ data_dent = debugfs_create_dir("data_hsic_bridge", 0);
+ if (IS_ERR(data_dent))
+ return;
+
+ data_dfile_stats = debugfs_create_file("status", 0644, data_dent, 0,
+ &data_stats_ops);
+ if (!data_dfile_stats || IS_ERR(data_dfile_stats)) {
+ debugfs_remove(data_dent);
+ return;
+ }
+
+ data_dfile_tstamp = debugfs_create_file("timestamp", 0644, data_dent,
+ 0, &data_timestamp_ops);
+ if (!data_dfile_tstamp || IS_ERR(data_dfile_tstamp))
+ debugfs_remove(data_dent);
+}
+
+static void data_bridge_debugfs_exit(void)
+{
+ debugfs_remove(data_dfile_stats);
+ debugfs_remove(data_dfile_tstamp);
+ debugfs_remove(data_dent);
+}
+
+#else
+static void data_bridge_debugfs_init(void) { }
+static void data_bridge_debugfs_exit(void) { }
+static void dbg_timestamp(char *event, struct sk_buff * skb)
+{
+ return;
+}
+
+static unsigned int get_timestamp(void)
+{
+ return 0;
+}
+
+#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;
+ unsigned 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 (!test_bit(iface_num, &id->driver_info))
+ return -ENODEV;
+
+ 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;
+
+ if (!dev) {
+ err("%s: data device not found\n", __func__);
+ 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);
+}
+
+/*bit position represents interface number*/
+#define PID9001_IFACE_MASK 0xC
+#define PID9034_IFACE_MASK 0xC
+#define PID9048_IFACE_MASK 0x18
+#define PID904C_IFACE_MASK 0x28
+
+static const struct usb_device_id bridge_ids[] = {
+ { USB_DEVICE(0x5c6, 0x9001),
+ .driver_info = PID9001_IFACE_MASK,
+ },
+ { USB_DEVICE(0x5c6, 0x9034),
+ .driver_info = PID9034_IFACE_MASK,
+ },
+ { USB_DEVICE(0x5c6, 0x9048),
+ .driver_info = PID9048_IFACE_MASK,
+ },
+ { USB_DEVICE(0x5c6, 0x904c),
+ .driver_info = PID904C_IFACE_MASK,
+ },
+
+ { } /* Terminating entry */
+};
+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");