Merge changes Ifd61b218,I8534a2eb into msm-3.0
* changes:
defconfig: msm8960: enable DUN bridge host driver
usb: Add dial-up networking bridge host driver for PID:9001
diff --git a/arch/arm/configs/msm8960_defconfig b/arch/arm/configs/msm8960_defconfig
index 7564020..bf15477 100755
--- a/arch/arm/configs/msm8960_defconfig
+++ b/arch/arm/configs/msm8960_defconfig
@@ -325,6 +325,7 @@
CONFIG_USB_STORAGE_KARMA=y
CONFIG_USB_STORAGE_CYPRESS_ATACB=y
CONFIG_USB_EHSET_TEST_FIXTURE=y
+CONFIG_USB_QCOM_DUN_BRIDGE=y
CONFIG_USB_GADGET=y
CONFIG_USB_GADGET_DEBUG_FILES=y
CONFIG_USB_GADGET_CI13XXX_MSM=y
diff --git a/arch/arm/mach-msm/include/mach/usb_dun_bridge.h b/arch/arm/mach-msm/include/mach/usb_dun_bridge.h
new file mode 100644
index 0000000..b4a8eef
--- /dev/null
+++ b/arch/arm/mach-msm/include/mach/usb_dun_bridge.h
@@ -0,0 +1,113 @@
+/* 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 527dc85..1135806 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -280,3 +280,28 @@
To compile this driver as a module, choose M here: the
module will be called qcaux. If unsure, choose N.
+
+config USB_QCOM_DUN_BRIDGE
+ tristate "USB Qualcomm modem DUN bridge driver"
+ depends on USB && !USB_SERIAL_QUALCOMM
+ 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.
+
+ 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.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 20ee62a..03568bc 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -31,3 +31,5 @@
obj-$(CONFIG_USB_QCOM_DIAG) += diag_usb.o
obj-$(CONFIG_USB_QCOM_DIAG_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
diff --git a/drivers/usb/misc/dun_bridge.c b/drivers/usb/misc/dun_bridge.c
new file mode 100644
index 0000000..aca7714
--- /dev/null
+++ b/drivers/usb/misc/dun_bridge.c
@@ -0,0 +1,520 @@
+/* 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
new file mode 100644
index 0000000..d545e13
--- /dev/null
+++ b/drivers/usb/misc/dun_bridge_test.c
@@ -0,0 +1,201 @@
+/*
+ * 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");