aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
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
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')
-rw-r--r--drivers/usb/Kconfig10
-rw-r--r--drivers/usb/Makefile1
-rw-r--r--drivers/usb/class/msm-dload.c766
-rw-r--r--drivers/usb/core/Kconfig19
-rw-r--r--drivers/usb/core/devio.c10
-rw-r--r--drivers/usb/core/hub.c60
-rw-r--r--drivers/usb/core/message.c24
-rw-r--r--drivers/usb/core/quirks.c15
-rw-r--r--drivers/usb/gadget/Kconfig170
-rw-r--r--drivers/usb/gadget/Makefile11
-rw-r--r--drivers/usb/gadget/android.c1528
-rw-r--r--drivers/usb/gadget/composite.c370
-rw-r--r--drivers/usb/gadget/epautoconf.c61
-rw-r--r--drivers/usb/gadget/ether.c4
-rw-r--r--drivers/usb/gadget/exynos_ss_udc.c3047
-rw-r--r--drivers/usb/gadget/exynos_ss_udc.h462
-rw-r--r--drivers/usb/gadget/f_accessory.c792
-rw-r--r--drivers/usb/gadget/f_acm.c45
-rw-r--r--drivers/usb/gadget/f_adb.c652
-rw-r--r--drivers/usb/gadget/f_dm.c310
-rw-r--r--drivers/usb/gadget/f_ecm.c116
-rw-r--r--drivers/usb/gadget/f_eem.c72
-rw-r--r--drivers/usb/gadget/f_mass_storage.c152
-rw-r--r--drivers/usb/gadget/f_mtp.c1342
-rw-r--r--drivers/usb/gadget/f_mtp.h56
-rw-r--r--drivers/usb/gadget/f_mtp_samsung.c1449
-rw-r--r--drivers/usb/gadget/f_ncm.c74
-rw-r--r--drivers/usb/gadget/f_rndis.c145
-rw-r--r--drivers/usb/gadget/f_sdb.c770
-rw-r--r--drivers/usb/gadget/f_subset.c76
-rw-r--r--drivers/usb/gadget/gadget_chips.h16
-rw-r--r--drivers/usb/gadget/gadget_gbhc/android.c533
-rw-r--r--drivers/usb/gadget/gadget_gbhc/composite.c1560
-rw-r--r--drivers/usb/gadget/gadget_gbhc/config.c192
-rw-r--r--drivers/usb/gadget/gadget_gbhc/epautoconf.c361
-rw-r--r--drivers/usb/gadget/gadget_gbhc/ether.c421
-rw-r--r--drivers/usb/gadget/gadget_gbhc/f_acm.c842
-rw-r--r--drivers/usb/gadget/gadget_gbhc/f_adb.c661
-rw-r--r--drivers/usb/gadget/gadget_gbhc/f_mass_storage.c3260
-rw-r--r--drivers/usb/gadget/gadget_gbhc/f_mtp.c1277
-rw-r--r--drivers/usb/gadget/gadget_gbhc/f_ncm.c1407
-rw-r--r--drivers/usb/gadget/gadget_gbhc/f_rndis.c935
-rw-r--r--drivers/usb/gadget/gadget_gbhc/file_storage.c3626
-rw-r--r--drivers/usb/gadget/gadget_gbhc/gadget_chips.h265
-rw-r--r--drivers/usb/gadget/gadget_gbhc/inode.c2150
-rw-r--r--drivers/usb/gadget/gadget_gbhc/mass_storage.c190
-rw-r--r--drivers/usb/gadget/gadget_gbhc/ncm.c248
-rw-r--r--drivers/usb/gadget/gadget_gbhc/ndis.h217
-rw-r--r--drivers/usb/gadget/gadget_gbhc/rndis.c1214
-rw-r--r--drivers/usb/gadget/gadget_gbhc/rndis.h268
-rw-r--r--drivers/usb/gadget/gadget_gbhc/storage_common.c797
-rw-r--r--drivers/usb/gadget/gadget_gbhc/u_ether.c1024
-rw-r--r--drivers/usb/gadget/gadget_gbhc/u_ether.h127
-rw-r--r--drivers/usb/gadget/gadget_gbhc/u_serial.c1348
-rw-r--r--drivers/usb/gadget/gadget_gbhc/u_serial.h67
-rw-r--r--drivers/usb/gadget/gadget_gbhc/usbstring.c136
-rw-r--r--drivers/usb/gadget/multi_config.c260
-rw-r--r--drivers/usb/gadget/multi_config.h135
-rw-r--r--drivers/usb/gadget/s3c_udc.h156
-rw-r--r--drivers/usb/gadget/s3c_udc_otg.c1402
-rw-r--r--drivers/usb/gadget/s3c_udc_otg_xfer_dma.c1455
-rw-r--r--drivers/usb/gadget/serial_acm.c171
-rw-r--r--drivers/usb/gadget/storage_common.c119
-rw-r--r--drivers/usb/gadget/u_ether.c87
-rw-r--r--drivers/usb/gadget/u_ether.h9
-rw-r--r--drivers/usb/gadget/u_ncm.c229
-rw-r--r--drivers/usb/gadget/u_serial.c9
-rw-r--r--drivers/usb/host/Kconfig49
-rw-r--r--drivers/usb/host/Makefile5
-rw-r--r--drivers/usb/host/ehci-hcd.c20
-rw-r--r--drivers/usb/host/ehci-hub.c347
-rw-r--r--drivers/usb/host/ehci-q.c33
-rw-r--r--drivers/usb/host/ehci-s5p.c487
-rw-r--r--drivers/usb/host/ehci.h35
-rw-r--r--drivers/usb/host/ohci-hcd.c10
-rw-r--r--drivers/usb/host/ohci-s5p.c485
-rw-r--r--drivers/usb/host/shost/Makefile9
-rw-r--r--drivers/usb/host/shost/shost.h56
-rw-r--r--drivers/usb/host/shost/shost_const.h140
-rw-r--r--drivers/usb/host/shost/shost_debug.h78
-rw-r--r--drivers/usb/host/shost/shost_driver.c450
-rw-r--r--drivers/usb/host/shost/shost_errorcode.h81
-rw-r--r--drivers/usb/host/shost/shost_hcd.c912
-rw-r--r--drivers/usb/host/shost/shost_kal.h361
-rw-r--r--drivers/usb/host/shost/shost_list.h188
-rw-r--r--drivers/usb/host/shost/shost_mem.h155
-rw-r--r--drivers/usb/host/shost/shost_oci.c809
-rw-r--r--drivers/usb/host/shost/shost_oci.h50
-rw-r--r--drivers/usb/host/shost/shost_readyq.c240
-rw-r--r--drivers/usb/host/shost/shost_regs.h747
-rw-r--r--drivers/usb/host/shost/shost_roothub.c579
-rw-r--r--drivers/usb/host/shost/shost_scheduler.c420
-rw-r--r--drivers/usb/host/shost/shost_scheduler.h56
-rw-r--r--drivers/usb/host/shost/shost_schedulerlib.c426
-rw-r--r--drivers/usb/host/shost/shost_struct.h189
-rw-r--r--drivers/usb/host/shost/shost_transfer.c1025
-rw-r--r--drivers/usb/host/shost/shost_transfer.h52
-rw-r--r--drivers/usb/host/shost/shost_transferchecker.c503
-rw-r--r--drivers/usb/host/shost/shost_transferchecker_bulk.c671
-rw-r--r--drivers/usb/host/shost/shost_transferchecker_control.c777
-rw-r--r--drivers/usb/host/shost/shost_transferchecker_interrupt.c617
-rw-r--r--drivers/usb/host/xhci-exynos.c625
-rw-r--r--drivers/usb/host/xhci-mem.c38
-rw-r--r--drivers/usb/host/xhci-pci.c96
-rw-r--r--drivers/usb/host/xhci-ring.c7
-rw-r--r--drivers/usb/host/xhci.c257
-rw-r--r--drivers/usb/host/xhci.h8
-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
-rw-r--r--drivers/usb/notify/Kconfig11
-rw-r--r--drivers/usb/notify/Makefile4
-rw-r--r--drivers/usb/notify/host_notifier.c284
-rw-r--r--drivers/usb/notify/host_notify_class.c262
-rw-r--r--drivers/usb/otg/Kconfig8
-rw-r--r--drivers/usb/otg/Makefile2
-rw-r--r--drivers/usb/otg/otg-wakelock.c169
-rw-r--r--drivers/usb/otg/otg_id.c205
-rw-r--r--drivers/usb/serial/qcserial.c12
-rw-r--r--drivers/usb/serial/usb-wwan.h8
-rw-r--r--drivers/usb/serial/usb_wwan.c7
-rw-r--r--drivers/usb/storage/usb.c3
127 files changed, 55823 insertions, 320 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 48f1781..2f3e764 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -19,6 +19,7 @@ config USB_ARCH_HAS_HCD
boolean
default y if USB_ARCH_HAS_OHCI
default y if USB_ARCH_HAS_EHCI
+ default y if USB_ARCH_HAS_XHCI
default y if PCMCIA && !M32R # sl811_cs
default y if ARM # SL-811
default y if BLACKFIN # SL-811
@@ -42,6 +43,7 @@ config USB_ARCH_HAS_OHCI
default y if ARCH_DAVINCI_DA8XX
default y if ARCH_CNS3XXX
default y if PLAT_SPEAR
+ default y if PLAT_S5P
# PPC:
default y if STB03xxx
default y if PPC_MPC52xx
@@ -71,6 +73,12 @@ config USB_ARCH_HAS_EHCI
default y if SPARC_LEON
default PCI
+# some non-PCI HCDs implement xHCI
+config USB_ARCH_HAS_XHCI
+ boolean
+ default y if ARCH_EXYNOS
+ default PCI
+
# ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface.
config USB
tristate "Support for Host-side USB"
@@ -110,6 +118,8 @@ config USB
source "drivers/usb/core/Kconfig"
+source "drivers/usb/notify/Kconfig"
+
source "drivers/usb/mon/Kconfig"
source "drivers/usb/wusbcore/Kconfig"
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 30ddf8d..57634b5 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -6,6 +6,7 @@
obj-$(CONFIG_USB) += core/
+obj-$(CONFIG_USB_HOST_NOTIFY) += notify/
obj-$(CONFIG_USB_MON) += mon/
obj-$(CONFIG_PCI) += host/
diff --git a/drivers/usb/class/msm-dload.c b/drivers/usb/class/msm-dload.c
new file mode 100644
index 0000000..b1738c1
--- /dev/null
+++ b/drivers/usb/class/msm-dload.c
@@ -0,0 +1,766 @@
+/*
+ * USB Skeleton driver - 2.2
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2.
+ *
+ * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c
+ * but has been rewritten to be easier to read and use.
+ *
+ */
+
+#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/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+
+/* Define these values to match your devices */
+#define USB_MSM_DLOAD_VENDOR_ID 0x05C6
+#define USB_MSM_DLOAD_PRODUCT_ID 0x9008
+
+/* table of devices that work with this driver */
+static const struct usb_device_id skel_table[] = {
+ { USB_DEVICE(USB_MSM_DLOAD_VENDOR_ID, USB_MSM_DLOAD_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, skel_table);
+
+
+/* Get a minor range for your devices from the usb maintainer */
+#define USB_SKEL_MINOR_BASE 192
+
+/* our private defines. if this grows any larger, use your own .h file */
+#define MAX_TRANSFER (PAGE_SIZE - 512)
+/* MAX_TRANSFER is chosen so that the VM is not stressed by
+ allocations > PAGE_SIZE and the number of packets in a page
+ is an integer 512 is the largest possible packet on EHCI */
+#define WRITES_IN_FLIGHT 8
+/* arbitrarily chosen */
+
+/* Structure to hold all of our device specific stuff */
+struct usb_skel {
+ /* the usb device for this device */
+ struct usb_device *udev;
+ /* the interface for this device */
+ struct usb_interface *interface;
+ /* limiting the number of writes in progress */
+ struct semaphore limit_sem;
+ /* in case we need to retract our submissions */
+ struct usb_anchor submitted;
+ /* the urb to read data with */
+ struct urb *bulk_in_urb;
+ /* the buffer to receive data */
+ unsigned char *bulk_in_buffer;
+ /* the size of the receive buffer */
+ size_t bulk_in_size;
+ /* number of bytes in the buffer */
+ size_t bulk_in_filled;
+ /* already copied to user space */
+ size_t bulk_in_copied;
+ /* the address of the bulk in endpoint */
+ __u8 bulk_in_endpointAddr;
+ /* the address of the bulk out endpoint */
+ __u8 bulk_out_endpointAddr;
+ /* the last request tanked */
+ int errors;
+ /* count the number of openers */
+ int open_count;
+ /* a read is going on */
+ bool ongoing_read;
+ /* indicates we haven't processed the urb */
+ bool processed_urb;
+ /* indicates driver registration is finished */
+ bool initialized;
+ /* lock for errors */
+ spinlock_t err_lock;
+ struct kref kref;
+ /* synchronize I/O with disconnect */
+ struct mutex io_mutex;
+ /* to wait for an ongoing read */
+ struct completion bulk_in_completion;
+};
+#define to_skel_dev(d) container_of(d, struct usb_skel, kref)
+
+static struct usb_driver skel_driver;
+static void skel_draw_down(struct usb_skel *dev);
+
+static void skel_delete(struct kref *kref)
+{
+ struct usb_skel *dev = to_skel_dev(kref);
+
+ usb_free_urb(dev->bulk_in_urb);
+ usb_put_dev(dev->udev);
+ kfree(dev->bulk_in_buffer);
+ kfree(dev);
+}
+static int skel_do_read_io(struct usb_skel *dev, size_t count);
+static void skel_read_bulk_callback(struct urb *urb);
+
+static int skel_open(struct inode *inode, struct file *file)
+{
+ struct usb_skel *dev;
+ struct usb_interface *interface;
+ int subminor;
+ int retval = 0;
+
+ subminor = iminor(inode);
+
+ pr_info("%s ++ (%d)\n", __func__, current->pid);
+
+ interface = usb_find_interface(&skel_driver, subminor);
+ if (!interface) {
+ err("%s - error, can't find device for minor %d",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ dev = usb_get_intfdata(interface);
+ if (!dev) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ if (!dev->initialized) {
+ pr_info("%s: not initialized yet\n", __func__);
+ return -ENODEV;
+ }
+
+ /* increment our usage count for the device */
+ kref_get(&dev->kref);
+
+ /* lock the device to allow correctly handling errors
+ * in resumption */
+ mutex_lock(&dev->io_mutex);
+
+ if (!dev->open_count++) {
+ retval = usb_autopm_get_interface(interface);
+ if (retval) {
+ dev->open_count--;
+ mutex_unlock(&dev->io_mutex);
+ kref_put(&dev->kref, skel_delete);
+ goto exit;
+ }
+ } /* else { //uncomment this block if you want exclusive open
+ retval = -EBUSY;
+ dev->open_count--;
+ mutex_unlock(&dev->io_mutex);
+ kref_put(&dev->kref, skel_delete);
+ goto exit;
+ } */
+ /* prevent the device from being autosuspended */
+
+ /* save our object in the file's private structure */
+ file->private_data = dev;
+ mutex_unlock(&dev->io_mutex);
+/*
+ if (skel_do_read_io(dev, dev->bulk_in_size) < 0) {
+ err("%s - failed submitting read urb",
+ __func__);
+ }
+*/
+exit:
+ pr_info("%s -- ret=%d\n", __func__, retval);
+ return retval;
+}
+
+static int skel_release(struct inode *inode, struct file *file)
+{
+ struct usb_skel *dev;
+
+ dev = (struct usb_skel *)file->private_data;
+ if (dev == NULL)
+ return -ENODEV;
+
+ pr_info("%s ++ (%d)\n", __func__, current->pid);
+
+ /* allow the device to be autosuspended */
+ mutex_lock(&dev->io_mutex);
+ if (!--dev->open_count && dev->interface)
+ usb_autopm_put_interface(dev->interface);
+ mutex_unlock(&dev->io_mutex);
+
+ /* decrement the count on our device */
+ kref_put(&dev->kref, skel_delete);
+
+ pr_info("%s --\n", __func__);
+ return 0;
+}
+
+static int skel_flush(struct file *file, fl_owner_t id)
+{
+ struct usb_skel *dev;
+ int res;
+
+ dev = (struct usb_skel *)file->private_data;
+ if (dev == NULL)
+ return -ENODEV;
+
+ pr_info("%s ++ (%d)\n", __func__, current->pid);
+
+ /* wait for io to stop */
+ mutex_lock(&dev->io_mutex);
+ skel_draw_down(dev);
+
+ /* read out errors, leave subsequent opens a clean slate */
+ spin_lock_irq(&dev->err_lock);
+ res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0;
+ dev->errors = 0;
+ spin_unlock_irq(&dev->err_lock);
+
+ mutex_unlock(&dev->io_mutex);
+
+ pr_info("%s --\n", __func__);
+
+ return res;
+}
+
+static void skel_read_bulk_callback(struct urb *urb)
+{
+ struct usb_skel *dev;
+
+ dev = urb->context;
+
+ spin_lock(&dev->err_lock);
+ /* sync/async unlink faults aren't errors */
+ if (urb->status) {
+ if (!(urb->status == -ENOENT ||
+ urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN))
+ err("%s - nonzero write bulk status received: %d",
+ __func__, urb->status);
+
+ dev->errors = urb->status;
+ } else {
+ dev->bulk_in_filled = urb->actual_length;
+ }
+ dev->ongoing_read = 0;
+ spin_unlock(&dev->err_lock);
+
+ complete(&dev->bulk_in_completion);
+}
+
+static int skel_do_read_io(struct usb_skel *dev, size_t count)
+{
+ int rv;
+
+ /* prepare a read */
+ usb_fill_bulk_urb(dev->bulk_in_urb,
+ dev->udev,
+ usb_rcvbulkpipe(dev->udev,
+ dev->bulk_in_endpointAddr),
+ dev->bulk_in_buffer,
+ min(dev->bulk_in_size, count),
+ skel_read_bulk_callback,
+ dev);
+ /* tell everybody to leave the URB alone */
+ spin_lock_irq(&dev->err_lock);
+ dev->ongoing_read = 1;
+ spin_unlock_irq(&dev->err_lock);
+
+ /* do it */
+ rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL);
+ if (rv < 0) {
+ err("%s - failed submitting read urb, error %d",
+ __func__, rv);
+ dev->bulk_in_filled = 0;
+ rv = (rv == -ENOMEM) ? rv : -EIO;
+ spin_lock_irq(&dev->err_lock);
+ dev->ongoing_read = 0;
+ spin_unlock_irq(&dev->err_lock);
+ }
+
+ return rv;
+}
+
+static ssize_t skel_read(struct file *file, char *buffer, size_t count,
+ loff_t *ppos)
+{
+ struct usb_skel *dev;
+ int rv;
+ bool ongoing_io;
+
+ dev = (struct usb_skel *)file->private_data;
+
+ pr_debug("%s ++ (%d)\n", __func__, current->pid);
+
+ /* if we cannot read at all, return EOF */
+ if (!dev->bulk_in_urb || !count)
+ return 0;
+
+ /* no concurrent readers */
+ rv = mutex_lock_interruptible(&dev->io_mutex);
+ if (rv < 0) {
+ err("%s: failed to get mutex", __func__);
+ return rv;
+ }
+
+ if (!dev->interface) { /* disconnect() was called */
+ rv = -ENODEV;
+ goto exit;
+ }
+
+ /* if IO is under way, we must not touch things */
+retry:
+ spin_lock_irq(&dev->err_lock);
+ ongoing_io = dev->ongoing_read;
+ spin_unlock_irq(&dev->err_lock);
+
+ if (ongoing_io) {
+ /* nonblocking IO shall not wait */
+ if (file->f_flags & O_NONBLOCK) {
+ rv = -EAGAIN;
+ goto exit;
+ }
+ /*
+ * IO may take forever
+ * hence wait in an interruptible state
+ */
+ rv = wait_for_completion_interruptible
+ (&dev->bulk_in_completion);
+ if (rv < 0)
+ goto exit;
+ /*
+ * by waiting we also semiprocessed the urb
+ * we must finish now
+ */
+ dev->bulk_in_copied = 0;
+ }
+
+ rv = skel_do_read_io(dev, dev->bulk_in_size);
+ if (rv < 0) {
+ err("%s - failed submitting read urb, error %d", __func__, rv);
+ rv = 0;
+ goto exit;
+ }
+ init_completion(&dev->bulk_in_completion);
+
+ /* errors must be reported */
+ rv = dev->errors;
+ if (rv < 0) {
+ /* any error is reported once */
+ dev->errors = 0;
+ /* to preserve notifications about reset */
+ rv = (rv == -EPIPE) ? rv : -EIO;
+ /* no data to deliver */
+ dev->bulk_in_filled = 0;
+ /* report it */
+ goto exit;
+ }
+
+ /*
+ * if the buffer is filled we may satisfy the read
+ * else we need to start IO
+ */
+
+ if (dev->bulk_in_filled) {
+ /* we had read data */
+ size_t available = dev->bulk_in_filled - dev->bulk_in_copied;
+ size_t chunk = min(available, count);
+
+ if (!available) {
+ rv = 0;
+ goto exit;
+ }
+ /*
+ * data is available
+ * chunk tells us how much shall be copied
+ */
+
+ if (copy_to_user(buffer,
+ dev->bulk_in_buffer + dev->bulk_in_copied,
+ chunk))
+ rv = -EFAULT;
+ else
+ rv = chunk;
+
+ dev->bulk_in_copied += chunk;
+
+ if (dev->bulk_in_filled == dev->bulk_in_copied) {
+ dev->bulk_in_filled = 0;
+ dev->bulk_in_copied = 0;
+ }
+
+ } else {
+ rv = 0;
+ goto exit;
+
+ }
+exit:
+ mutex_unlock(&dev->io_mutex);
+ pr_debug("%s -- ret=%d\n", __func__, rv);
+ return rv;
+}
+
+static void skel_write_bulk_callback(struct urb *urb)
+{
+ struct usb_skel *dev;
+
+ dev = urb->context;
+
+ /* sync/async unlink faults aren't errors */
+ if (urb->status) {
+ if (!(urb->status == -ENOENT ||
+ urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN))
+ err("%s - nonzero write bulk status received: %d",
+ __func__, urb->status);
+
+ spin_lock(&dev->err_lock);
+ dev->errors = urb->status;
+ spin_unlock(&dev->err_lock);
+ }
+
+ /* free up our allocated buffer */
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ up(&dev->limit_sem);
+}
+
+static ssize_t skel_write(struct file *file, const char *user_buffer,
+ size_t count, loff_t *ppos)
+{
+ struct usb_skel *dev;
+ int retval = 0;
+ struct urb *urb = NULL;
+ char *buf = NULL;
+ size_t max_count = MAX_TRANSFER;
+ size_t writesize = min(count, max_count);
+
+ dev = (struct usb_skel *)file->private_data;
+
+ /* verify that we actually have some data to write */
+ if (count == 0)
+ goto exit;
+
+ /*
+ * limit the number of URBs in flight to stop a user from using up all
+ * RAM
+ */
+ if (!(file->f_flags & O_NONBLOCK)) {
+ if (down_interruptible(&dev->limit_sem)) {
+ retval = -ERESTARTSYS;
+ goto exit;
+ }
+ } else {
+ if (down_trylock(&dev->limit_sem)) {
+ retval = -EAGAIN;
+ goto exit;
+ }
+ }
+
+ spin_lock_irq(&dev->err_lock);
+ retval = dev->errors;
+ if (retval < 0) {
+ /* any error is reported once */
+ dev->errors = 0;
+ /* to preserve notifications about reset */
+ retval = (retval == -EPIPE) ? retval : -EIO;
+ }
+ spin_unlock_irq(&dev->err_lock);
+ if (retval < 0)
+ goto error;
+
+ /* create a urb, and a buffer for it, and copy the data to the urb */
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!buf) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ if (copy_from_user(buf, user_buffer, writesize)) {
+ retval = -EFAULT;
+ goto error;
+ }
+
+ /* this lock makes sure we don't submit URBs to gone devices */
+ mutex_lock(&dev->io_mutex);
+ if (!dev->interface) { /* disconnect() was called */
+ mutex_unlock(&dev->io_mutex);
+ retval = -ENODEV;
+ goto error;
+ }
+
+ /* initialize the urb properly */
+ usb_fill_bulk_urb(urb, dev->udev,
+ usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
+ buf, writesize, skel_write_bulk_callback, dev);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ usb_anchor_urb(urb, &dev->submitted);
+
+ /* send the data out the bulk port */
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ mutex_unlock(&dev->io_mutex);
+ if (retval) {
+ err("%s - failed submitting write urb, error %d", __func__,
+ retval);
+ goto error_unanchor;
+ }
+
+ /*
+ * release our reference to this urb, the USB core will eventually free
+ * it entirely
+ */
+ usb_free_urb(urb);
+
+
+ pr_debug("%s success (%d)--\n", __func__, writesize);
+ return writesize;
+
+error_unanchor:
+ usb_unanchor_urb(urb);
+error:
+ if (urb) {
+ usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma);
+ usb_free_urb(urb);
+ }
+ up(&dev->limit_sem);
+
+exit:
+ pr_debug("%s (%d)--\n", __func__, retval);
+ return retval;
+}
+
+static const struct file_operations skel_fops = {
+ .owner = THIS_MODULE,
+ .read = skel_read,
+ .write = skel_write,
+ .open = skel_open,
+ .release = skel_release,
+ .flush = skel_flush,
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver skel_class = {
+ .name = "skel%d",
+ .fops = &skel_fops,
+ .minor_base = USB_SKEL_MINOR_BASE,
+};
+
+static int skel_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_skel *dev;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ size_t buffer_size;
+ int i;
+ int retval = -ENOMEM;
+
+ pr_info("%s ++\n", __func__);
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev) {
+ err("Out of memory");
+ goto error;
+ }
+ kref_init(&dev->kref);
+ sema_init(&dev->limit_sem, WRITES_IN_FLIGHT);
+ mutex_init(&dev->io_mutex);
+ spin_lock_init(&dev->err_lock);
+ init_usb_anchor(&dev->submitted);
+ init_completion(&dev->bulk_in_completion);
+
+ dev->udev = usb_get_dev(interface_to_usbdev(interface));
+ dev->interface = interface;
+
+ /* set up the endpoint information */
+ /* use only the first bulk-in and bulk-out endpoints */
+ iface_desc = interface->cur_altsetting;
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+ endpoint = &iface_desc->endpoint[i].desc;
+
+ if (!dev->bulk_in_endpointAddr &&
+ usb_endpoint_is_bulk_in(endpoint)) {
+ /* we found a bulk in endpoint */
+ buffer_size = le16_to_cpu(endpoint->wMaxPacketSize);
+ dev->bulk_in_size = buffer_size;
+ dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
+ dev_dbg(&interface->dev, "bulk_in_size: %d\n",
+ dev->bulk_in_size);
+ dev_dbg(&interface->dev, "in_endpointAddr: %x\n",
+ dev->bulk_in_endpointAddr);
+ dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
+ if (!dev->bulk_in_buffer) {
+ err("Could not allocate bulk_in_buffer");
+ goto error;
+ }
+ dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->bulk_in_urb) {
+ err("Could not allocate bulk_in_urb");
+ goto error;
+ }
+ }
+
+ if (!dev->bulk_out_endpointAddr &&
+ usb_endpoint_is_bulk_out(endpoint)) {
+ /* we found a bulk out endpoint */
+ dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
+ }
+ }
+ if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
+ err("Could not find both bulk-in and bulk-out endpoints");
+ goto error;
+ }
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, dev);
+
+ /* we can register the device now, as it is ready */
+ retval = usb_register_dev(interface, &skel_class);
+ if (retval) {
+ /* something prevented us from registering this driver */
+ err("Not able to get a minor for this device.");
+ usb_set_intfdata(interface, NULL);
+ goto error;
+ }
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev,
+ "USB Skeleton device now attached to USBSkel-%d",
+ interface->minor);
+
+ mutex_lock(&dev->io_mutex);
+ dev->initialized = true;
+ mutex_unlock(&dev->io_mutex);
+
+ pr_info("%s --\n", __func__);
+ return 0;
+
+error:
+ if (dev)
+ /* this frees allocated memory */
+ kref_put(&dev->kref, skel_delete);
+ return retval;
+}
+
+static void skel_disconnect(struct usb_interface *interface)
+{
+ struct usb_skel *dev;
+ int minor = interface->minor;
+
+ dev = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ /* give back our minor */
+ usb_deregister_dev(interface, &skel_class);
+
+ /* prevent more I/O from starting */
+ mutex_lock(&dev->io_mutex);
+ dev->interface = NULL;
+ dev->initialized = false;
+ mutex_unlock(&dev->io_mutex);
+
+ usb_kill_anchored_urbs(&dev->submitted);
+
+ /* decrement our usage count */
+ kref_put(&dev->kref, skel_delete);
+
+ dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor);
+}
+
+static void skel_draw_down(struct usb_skel *dev)
+{
+ int time;
+
+ pr_info("%s", __func__);
+ time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
+ if (!time)
+ usb_kill_anchored_urbs(&dev->submitted);
+ usb_kill_urb(dev->bulk_in_urb);
+}
+
+static int skel_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usb_skel *dev = usb_get_intfdata(intf);
+
+ pr_info("%s", __func__);
+ if (!dev)
+ return 0;
+ skel_draw_down(dev);
+ return 0;
+}
+
+static int skel_resume(struct usb_interface *intf)
+{
+ return 0;
+}
+
+static int skel_pre_reset(struct usb_interface *intf)
+{
+ struct usb_skel *dev = usb_get_intfdata(intf);
+
+ pr_info("%s", __func__);
+ mutex_lock(&dev->io_mutex);
+ skel_draw_down(dev);
+
+ return 0;
+}
+
+static int skel_post_reset(struct usb_interface *intf)
+{
+ struct usb_skel *dev = usb_get_intfdata(intf);
+
+ pr_info("%s", __func__);
+ /* we are sure no URBs are active - no locking needed */
+ dev->errors = -EPIPE;
+ mutex_unlock(&dev->io_mutex);
+
+ return 0;
+}
+
+static struct usb_driver skel_driver = {
+ .name = "skeleton",
+ .probe = skel_probe,
+ .disconnect = skel_disconnect,
+ .suspend = skel_suspend,
+ .resume = skel_resume,
+ .pre_reset = skel_pre_reset,
+ .post_reset = skel_post_reset,
+ .id_table = skel_table,
+ .supports_autosuspend = 1,
+};
+
+static int __init usb_skel_init(void)
+{
+ int result;
+
+ pr_info("%s", __func__);
+
+ /* register this driver with the USB subsystem */
+ result = usb_register(&skel_driver);
+ if (result)
+ err("usb_register failed. Error number %d", result);
+
+ return result;
+}
+
+static void __exit usb_skel_exit(void)
+{
+ /* deregister this driver with the USB subsystem */
+ usb_deregister(&skel_driver);
+}
+
+module_init(usb_skel_init);
+module_exit(usb_skel_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig
index 18d02e3..6bb3bab 100644
--- a/drivers/usb/core/Kconfig
+++ b/drivers/usb/core/Kconfig
@@ -148,3 +148,22 @@ config USB_OTG_BLACKLIST_HUB
and software costs by not supporting external hubs. So
are "Embedded Hosts" that don't offer OTG support.
+config HOST_COMPLIANT_TEST
+ bool "Embedded High-speed Host Electrical Test Support"
+ depends on USB && USB_ARCH_HAS_HCD && PLAT_SAMSUNG
+ default n
+ help
+ This option is only used if you are developing firmware for
+ an embedded device with a Hi-speed USB Host or OTG port.
+
+ If you say Y here, software support for the Embedded
+ High-speed Host Electrical Tests will be added to the USB
+ Host stack. This is one of the tests performed during
+ High-speed USB Host certification testing.
+
+ Please note that the USB Host Controller Driver must also
+ support this option. For an example of how to add support
+ for this to a USB Host Controller Driver see the EHCI driver.
+
+ If you are at all unsure then say N here.
+
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
index 0ca54e2..9823f18 100644
--- a/drivers/usb/core/devio.c
+++ b/drivers/usb/core/devio.c
@@ -709,10 +709,14 @@ static int usbdev_open(struct inode *inode, struct file *file)
if (dev->state == USB_STATE_NOTATTACHED)
goto out_unlock_device;
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ pr_debug("mif: modem usbdev_open, skip usb_autoresume_device\n");
+ ret = 0;
+#else
ret = usb_autoresume_device(dev);
if (ret)
goto out_unlock_device;
-
+#endif
ps->dev = dev;
ps->file = file;
spin_lock_init(&ps->lock);
@@ -761,7 +765,11 @@ static int usbdev_release(struct inode *inode, struct file *file)
releaseintf(ps, ifnum);
}
destroy_all_async(ps);
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ pr_debug("mif: modem usbdev_open, skip usb_autosuspend_device\n");
+#else
usb_autosuspend_device(dev);
+#endif
usb_unlock_device(dev);
usb_put_dev(dev);
put_pid(ps->disc_pid);
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 210e359..c0c8644 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -863,7 +863,11 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
* If any port-status changes do occur during this delay, khubd
* will see them later and handle them normally.
*/
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ if (need_debounce_delay && type != HUB_RESET_RESUME) {
+#else
if (need_debounce_delay) {
+#endif
delay = HUB_DEBOUNCE_STABLE;
/* Don't do a long sleep inside a workqueue routine */
@@ -1640,20 +1644,22 @@ void usb_disconnect(struct usb_device **pdev)
{
struct usb_device *udev = *pdev;
int i;
- struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+ struct usb_hcd *hcd;
if (!udev) {
pr_debug ("%s nodev\n", __func__);
return;
}
+ hcd = bus_to_hcd(udev->bus);
+
/* mark the device as inactive, so any further urb submissions for
* this device (and any of its children) will fail immediately.
* this quiesces everything except pending urbs.
*/
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
- dev_info(&udev->dev, "USB disconnect, device number %d\n",
- udev->devnum);
+ dev_info(&udev->dev, "USB disconnect, device number %d by %pF\n",
+ udev->devnum, __builtin_return_address(0));
usb_lock_device(udev);
@@ -2424,6 +2430,9 @@ static int finish_port_resume(struct usb_device *udev)
retry_reset_resume:
status = usb_reset_and_verify_device(udev);
+ if (udev->quirks & USB_QUIRK_NO_GET_STATUS)
+ goto done;
+
/* 10.5.4.5 says be sure devices in the tree are still there.
* For now let's assume the device didn't go crazy on resume,
* and device drivers will know about any resume quirks.
@@ -2462,6 +2471,7 @@ static int finish_port_resume(struct usb_device *udev)
}
status = 0;
}
+done:
return status;
}
@@ -2530,7 +2540,12 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
/* drive resume for at least 20 msec */
dev_dbg(&udev->dev, "usb %sresume\n",
(msg.event & PM_EVENT_AUTO ? "auto-" : ""));
- msleep(25);
+
+ /* Add the 5msec delay for XMM6260 resume fail case*/
+ if (udev->quirks & USB_QUIRK_HSIC_TUNE)
+ msleep(30);
+ else
+ msleep(25);
/* Virtual root hubs can trigger on GET_PORT_STATUS to
* stop resume signaling. Then finish the resume
@@ -2540,9 +2555,20 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
/* TRSMRCY = 10 msec */
msleep(10);
+
+ /* If portstatus's still resuming, retry GET_PORT_STATUS */
+ if (udev->quirks & USB_QUIRK_HSIC_TUNE)
+ if (portstatus & USB_PORT_STAT_SUSPEND) {
+ usleep_range(5000, 10000);
+ status = hub_port_status(hub, port1,
+ &portstatus, &portchange);
+ }
}
SuspendCleared:
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ pr_debug("mif: %s: %d, %d\n", __func__, portstatus, portchange);
+#endif
if (status == 0) {
if (hub_is_superspeed(hub->hdev)) {
if (portchange & USB_PORT_STAT_C_LINK_STATE)
@@ -2955,9 +2981,13 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
buf->bMaxPacketSize0;
kfree(buf);
- retval = hub_port_reset(hub, port1, udev, delay);
- if (retval < 0) /* error or disconnect */
- goto fail;
+ if (!(udev->quirks & USB_QUIRK_HSIC_TUNE)) {
+ retval =
+ hub_port_reset(hub, port1, udev, delay);
+ if (retval < 0) /* error or disconnect */
+ goto fail;
+ }
+
if (oldspeed != udev->speed) {
dev_dbg(&udev->dev,
"device reset changed speed!\n");
@@ -4049,3 +4079,19 @@ void usb_queue_reset_device(struct usb_interface *iface)
schedule_work(&iface->reset_ws);
}
EXPORT_SYMBOL_GPL(usb_queue_reset_device);
+
+void usb_force_disconnect(struct usb_device *udev)
+{
+ struct usb_hub *parent_hub;
+ int port1 = udev->portnum;
+
+ if (!udev->parent)
+ return;
+
+ parent_hub = hdev_to_hub(udev->parent);
+ if (!parent_hub)
+ return;
+
+ hub_port_logical_disconnect(parent_hub, port1);
+}
+EXPORT_SYMBOL_GPL(usb_force_disconnect);
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 0b5ec23..0a8da6f 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -135,6 +135,9 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
{
struct usb_ctrlrequest *dr;
int ret;
+#if defined(CONFIG_LINK_DEVICE_HSIC) && defined(CONFIG_UMTS_MODEM_XMM6262)
+ int limit_timeout;
+#endif
dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO);
if (!dr)
@@ -148,8 +151,22 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
/* dbg("usb_control_msg"); */
- ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);
+#if defined(CONFIG_LINK_DEVICE_HSIC) && defined(CONFIG_UMTS_MODEM_XMM6262)
+ /* Sometimes AP can't received the HSIC descriptor when AP L3->L0
+ * reset-resume, then got the dpm_timeout panic caused 5sec * retry
+ * timeout. we can get the cp dump after dpm resume.
+ * portnum 2 is HSIC phy0 for CP.
+ */
+ limit_timeout = (dev->portnum == 2) ? min(timeout, 1500) : timeout;
+
+ /* pr_debug("%s: dev=%s, portnum=%d, timeout=%d\n", __func__,
+ dev_name(&dev->dev), dev->portnum, limit_timeout); */
+ ret = usb_internal_control_msg(dev, pipe, dr, data, size,
+ limit_timeout);
+#else
+ ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout);
+#endif
kfree(dr);
return ret;
@@ -1857,6 +1874,11 @@ free_interfaces:
dev_name(&intf->dev), ret);
continue;
}
+#ifdef CONFIG_HOST_COMPLIANT_TEST
+ if (usb_get_intfdata(intf) == NULL ) {
+ dev_info( &intf->dev, "%s : Not match interface - driver detect fail\n",__func__);
+ }
+#endif
create_intf_ep_devs(intf);
}
diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c
index ecf12e1..72275a6 100644
--- a/drivers/usb/core/quirks.c
+++ b/drivers/usb/core/quirks.c
@@ -147,6 +147,21 @@ static const struct usb_device_id usb_quirk_list[] = {
/* INTEL VALUE SSD */
{ USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME },
+ /* Samsung CMC221 LTE Modem */
+ { USB_DEVICE(0x04e8, 0x6999), .driver_info = USB_QUIRK_NO_GET_STATUS },
+
+ /* IMC_BOOT - XMM6260, XMM6262 */
+ { USB_DEVICE(0x058b, 0x0041), .driver_info = USB_QUIRK_HSIC_TUNE },
+
+ /* IMC_MAIN - XMM6260, XMM6262 */
+ { USB_DEVICE(0x1519, 0x0020), .driver_info = USB_QUIRK_HSIC_TUNE },
+
+ /* STE_BOOT - M7400 */
+ { USB_DEVICE(0x04cc, 0x7400), .driver_info = USB_QUIRK_HSIC_TUNE },
+
+ /* STE_MAIN - M7400 */
+ { USB_DEVICE(0x04cc, 0x2333), .driver_info = USB_QUIRK_HSIC_TUNE },
+
{ } /* terminating entry must be last */
};
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 029e288..3d23ed6 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -314,6 +314,20 @@ config USB_S3C_HSOTG
default USB_GADGET
select USB_GADGET_SELECTED
+config USB_GADGET_EXYNOS_SS_UDC
+ boolean "EXYNOS SuperSpeed USB 3.0 Device controller"
+ depends on EXYNOS_DEV_SS_UDC
+ select USB_GADGET_DUALSPEED
+ help
+ The Samsung Exynos SuperSpeed USB 3.0 device controller
+ integrated into the Exynos5 series SoC.
+
+config USB_EXYNOS_SS_UDC
+ tristate
+ depends on USB_GADGET_EXYNOS_SS_UDC
+ default USB_GADGET
+ select USB_GADGET_SELECTED
+
config USB_GADGET_IMX
boolean "Freescale IMX USB Peripheral Controller"
depends on ARCH_MX1
@@ -373,6 +387,20 @@ config USB_S3C_HSUDC
default USB_GADGET
select USB_GADGET_SELECTED
+config USB_GADGET_S3C_OTGD
+ boolean "S3C HS USB OTG Device"
+ depends on (PLAT_S5P)
+ help
+ Samsung's S3C64XX processors include high speed USB OTG2.0
+ controller. It has 15 configurable endpoints, as well as
+ endpoint zero (for control transfers).
+
+ This driver has been tested on the S3C6410, S5P6440, S5PC100, S5PV210, EXYNOS4210 processor.
+
+ Say "y" to link the driver statically, or "m" to build a
+ dynamically linked module called "s3c-udc-otg" and force all
+ gadget drivers to also be dynamically linked.
+
config USB_GADGET_PXA_U2O
boolean "PXA9xx Processor USB2.0 controller"
select USB_GADGET_DUALSPEED
@@ -633,10 +661,56 @@ config USB_DUMMY_HCD
endchoice
# Selected by UDC drivers that support high-speed operation.
+comment "NOTE: S3C OTG device role enables the controller driver below"
+ depends on USB_GADGET_S3C_OTGD
+
+config USB_S3C_OTGD
+ tristate "S3C high speed(2.0, dual-speed) USB OTG device"
+ depends on USB_GADGET && USB_GADGET_S3C_OTGD
+ default y
+ default USB_GADGET
+ select USB_GADGET_SELECTED
+ select USB_GADGET_DUALSPEED
+ help
+ Say "y" to link the driver statically, or "m" to build a
+ dynamically linked module called "s3c-udc-otg-hs" and force all
+ gadget drivers to also be dynamically linked.
+
+choice
+ prompt "EXYNOS SS UDC speed mode (temporarily)"
+ depends on USB_GADGET_EXYNOS_SS_UDC
+ default USB_GADGET_EXYNOS_SS_UDC_SSMODE
+ help
+ For the present moment gadget layer doesn't support SuperSpeed.
+ This option allows to select speed mode for EXYNOS SS UDC driver.
+
+ NOTE: this is temporarily and will be removed once SuperSpeed support
+ has been added to gadget layer.
+
+config USB_GADGET_EXYNOS_SS_UDC_HSMODE
+ boolean "HighSpeed"
+ help
+ Use HighSpeed mode to work with USB2.0 host and gadgets _without_
+ SuperSpeed support.
+
+config USB_GADGET_EXYNOS_SS_UDC_SSMODE
+ boolean "SuperSpeed"
+ select USB_GADGET_SUPERSPEED
+ help
+ Use SuperSpeed mode to debug/test gadget's SuperSpeed support.
+
+endchoice
+
config USB_GADGET_DUALSPEED
bool
depends on USB_GADGET
+# Selected by UDC drivers that support super-speed opperation
+config USB_GADGET_SUPERSPEED
+ bool
+ depends on USB_GADGET
+ depends on USB_GADGET_DUALSPEED
+
#
# USB Gadget Drivers
#
@@ -935,6 +1009,102 @@ config USB_G_PRINTER
For more information, see Documentation/usb/gadget_printer.txt
which includes sample code for accessing the device file.
+config USB_G_SLP
+ boolean "SLP Gadget based on Android"
+ help
+ The SLP gadget driver supports multiple USB functions.
+ The functions can be configured via a board file and may be
+ enabled and disabled dynamically.
+
+config USB_G_ANDROID
+ boolean "Android Gadget"
+ depends on SWITCH
+ help
+ The Android gadget driver supports multiple USB functions.
+ The functions can be configured via a board file and may be
+ enabled and disabled dynamically.
+
+config USB_ANDROID_SAMSUNG_COMPOSITE
+ boolean "Samsung Composite function"
+ depends on USB_G_ANDROID
+ help
+ Provides SAMSUNG composite driver.
+ It also provides KIES connection and
+ Multi Configuration.
+ If you enable this option, android composite will be changed.
+
+config USB_ANDROID_SAMSUNG_MTP
+ boolean "Samsung MTP function"
+ depends on USB_G_ANDROID
+ help
+ Provides Media Transfer Protocol (MTP) support
+ for samsung gadget driver.
+ If you enable this option,
+ google mtp will be changed to samsung mtp.
+
+config USB_DUN_SUPPORT
+ boolean "DUN support function"
+ depends on USB_G_ANDROID
+ help
+ Provides USB modem serial driver.
+ This function makes connection to acm from data router.
+ It uses misc register.
+ Support fops : open, close, release, read, poll, llseek, ioctl
+
+config USB_ANDROID
+ boolean "Android Gadget for HC&GB"
+ depends on SWITCH
+ help
+ The Android gadget driver supports multiple USB functions.
+ The functions can be configured via a board file and may be
+ enabled and disabled dynamically.
+
+config USB_ANDROID_ACM
+ boolean "Android gadget ACM serial function"
+ depends on USB_ANDROID
+ help
+ Provides ACM serial function for android gadget driver.
+
+config USB_ANDROID_ADB
+ boolean "Android gadget adb function"
+ depends on USB_ANDROID
+ help
+ Provides adb function for android gadget driver.
+
+config USB_ANDROID_MASS_STORAGE
+ boolean "Android gadget mass storage function"
+ depends on USB_ANDROID && SWITCH
+ help
+ Provides USB mass storage function for android gadget driver.
+
+config USB_ANDROID_MTP
+ boolean "Android MTP function"
+ depends on USB_ANDROID
+ help
+ Provides Media Transfer Protocol (MTP) support for android gadget driver.
+
+config USB_ANDROID_RNDIS
+ boolean "Android gadget RNDIS ethernet function"
+ depends on USB_ANDROID
+ help
+ Provides RNDIS ethernet function for android gadget driver.
+
+config USB_ANDROID_RNDIS_WCEIS
+ boolean "Use Windows Internet Sharing Class/SubClass/Protocol"
+ depends on USB_ANDROID_RNDIS
+ help
+ Causes the driver to look like a Windows-compatible Internet
+ Sharing device, so Windows auto-detects it.
+
+ If you enable this option, the device is no longer CDC ethernet
+ compatible.
+
+config USB_ANDROID_ACCESSORY
+ boolean "Android USB accessory function"
+ depends on USB_ANDROID
+ help
+ Provides Android USB Accessory support for android gadget driver.
+
config USB_CDC_COMPOSITE
tristate "CDC Composite Device (Ethernet and ACM)"
depends on NET
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 4fe92b1..bd8b874 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -12,6 +12,8 @@ obj-$(CONFIG_USB_IMX) += imx_udc.o
obj-$(CONFIG_USB_GOKU) += goku_udc.o
obj-$(CONFIG_USB_OMAP) += omap_udc.o
obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o
+obj-$(CONFIG_USB_S3C_OTGD) += s3c_udc_otg.o
+obj-$(CONFIG_USB_EXYNOS_SS_UDC) += exynos_ss_udc.o
obj-$(CONFIG_USB_AT91) += at91_udc.o
obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o
obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o
@@ -49,6 +51,7 @@ g_dbgp-y := dbgp.o
g_nokia-y := nokia.o
g_webcam-y := webcam.o
g_ncm-y := ncm.o
+g_android-y := android.o
obj-$(CONFIG_USB_ZERO) += g_zero.o
obj-$(CONFIG_USB_AUDIO) += g_audio.o
@@ -67,3 +70,11 @@ obj-$(CONFIG_USB_G_MULTI) += g_multi.o
obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o
obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o
obj-$(CONFIG_USB_G_NCM) += g_ncm.o
+obj-$(CONFIG_USB_G_ANDROID) += g_android.o
+obj-$(CONFIG_USB_ANDROID) += gadget_gbhc/android.o
+obj-$(CONFIG_USB_ANDROID_ACM) += gadget_gbhc/f_acm.o gadget_gbhc/u_serial.o
+obj-$(CONFIG_USB_ANDROID_ADB) += gadget_gbhc/f_adb.o
+obj-$(CONFIG_USB_ANDROID_MASS_STORAGE) += gadget_gbhc/f_mass_storage.o
+obj-$(CONFIG_USB_ANDROID_MTP) += gadget_gbhc/f_mtp.o
+obj-$(CONFIG_USB_ANDROID_RNDIS) += gadget_gbhc/f_rndis.o gadget_gbhc/u_ether.o
+obj-$(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE) += multi_config.o
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
new file mode 100644
index 0000000..32074ce
--- /dev/null
+++ b/drivers/usb/gadget/android.c
@@ -0,0 +1,1528 @@
+/*
+ * Gadget Driver for Android
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/platform_device.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/android_composite.h>
+
+#include "gadget_chips.h"
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module. So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+#include "composite.c"
+
+#include "f_mass_storage.c"
+#include "u_serial.c"
+#ifdef CONFIG_USB_DUN_SUPPORT
+#include "serial_acm.c"
+#endif
+#include "f_acm.c"
+#include "f_adb.c"
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_MTP
+#include "f_mtp_samsung.c"
+#else
+#include "f_mtp.c"
+#endif
+#include "f_accessory.c"
+#define USB_ETH_RNDIS y
+#include "f_rndis.c"
+#include "rndis.c"
+#include "f_dm.c"
+
+MODULE_AUTHOR("Mike Lockwood");
+MODULE_DESCRIPTION("Android Composite USB Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+static const char longname[] = "Gadget Android";
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+static int composite_string_index;
+#endif
+/* Default vendor and product IDs, overridden by userspace */
+#define VENDOR_ID 0x18D1
+#define PRODUCT_ID 0x0001
+
+/* DM_PORT NUM : /dev/ttyGS* port number */
+#define DM_PORT_NUM 1
+
+struct android_usb_function {
+ char *name;
+ void *config;
+
+ struct device *dev;
+ char *dev_name;
+ struct device_attribute **attributes;
+
+ /* for android_dev.enabled_functions */
+ struct list_head enabled_list;
+
+ /* Optional: initialization during gadget bind */
+ int (*init)(struct android_usb_function *, struct usb_composite_dev *);
+ /* Optional: cleanup during gadget unbind */
+ void (*cleanup)(struct android_usb_function *);
+
+ int (*bind_config)(struct android_usb_function *, struct usb_configuration *);
+
+ /* Optional: called when the configuration is removed */
+ void (*unbind_config)(struct android_usb_function *, struct usb_configuration *);
+ /* Optional: handle ctrl requests before the device is configured */
+ int (*ctrlrequest)(struct android_usb_function *,
+ struct usb_composite_dev *,
+ const struct usb_ctrlrequest *);
+};
+
+struct android_dev {
+ struct android_usb_function **functions;
+ struct list_head enabled_functions;
+ struct usb_composite_dev *cdev;
+ struct device *dev;
+
+ bool enabled;
+ struct mutex mutex;
+ bool connected;
+ bool sw_connected;
+ struct work_struct work;
+};
+
+static struct class *android_class;
+static struct android_dev *_android_dev;
+static int android_bind_config(struct usb_configuration *c);
+static void android_unbind_config(struct usb_configuration *c);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+static struct android_usb_platform_data *android_usb_pdata;
+#endif
+/* string IDs are assigned dynamically */
+#define STRING_MANUFACTURER_IDX 0
+#define STRING_PRODUCT_IDX 1
+#define STRING_SERIAL_IDX 2
+
+static char manufacturer_string[256];
+static char product_string[256];
+static char serial_string[256];
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+#include "u_ncm.c"
+#endif
+#include "u_ether.c"
+
+/* String Table */
+static struct usb_string strings_dev[] = {
+ [STRING_MANUFACTURER_IDX].s = manufacturer_string,
+ [STRING_PRODUCT_IDX].s = product_string,
+ [STRING_SERIAL_IDX].s = serial_string,
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dev,
+};
+
+static struct usb_gadget_strings *dev_strings[] = {
+ &stringtab_dev,
+ NULL,
+};
+
+static struct usb_device_descriptor device_desc = {
+ .bLength = sizeof(device_desc),
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = __constant_cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_PER_INTERFACE,
+ .idVendor = __constant_cpu_to_le16(VENDOR_ID),
+ .idProduct = __constant_cpu_to_le16(PRODUCT_ID),
+ .bcdDevice = __constant_cpu_to_le16(0xffff),
+ .bNumConfigurations = 1,
+};
+
+static struct usb_configuration android_config_driver = {
+ .label = "android",
+ .unbind = android_unbind_config,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+ .bMaxPower = 0x30, /* 96ma */
+};
+
+static void android_work(struct work_struct *data)
+{
+ struct android_dev *dev = container_of(data, struct android_dev, work);
+ struct usb_composite_dev *cdev = dev->cdev;
+ char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL };
+ char *connected[2] = { "USB_STATE=CONNECTED", NULL };
+ char *configured[2] = { "USB_STATE=CONFIGURED", NULL };
+ char **uevent_envp = NULL;
+ unsigned long flags;
+
+ printk(KERN_DEBUG "usb: %s config=%p,connected=%d,sw_connected=%d\n",
+ __func__, cdev->config, dev->connected,
+ dev->sw_connected);
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ uevent_envp = configured;
+ else if (dev->connected != dev->sw_connected)
+ uevent_envp = dev->connected ? connected : disconnected;
+ dev->sw_connected = dev->connected;
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ if (uevent_envp) {
+ kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp);
+ printk(KERN_DEBUG "usb: %s sent uevent %s\n",
+ __func__, uevent_envp[0]);
+ } else {
+ printk(KERN_DEBUG "usb: %s did not send uevent (%d %d %p)\n",
+ __func__, dev->connected, dev->sw_connected, cdev->config);
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+/* Supported functions initialization */
+
+static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
+{
+ return adb_setup();
+}
+
+static void adb_function_cleanup(struct android_usb_function *f)
+{
+ adb_cleanup();
+}
+
+static int adb_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
+{
+ return adb_bind_config(c);
+}
+
+static struct android_usb_function adb_function = {
+ .name = "adb",
+ .init = adb_function_init,
+ .cleanup = adb_function_cleanup,
+ .bind_config = adb_function_bind_config,
+};
+
+
+#define MAX_ACM_INSTANCES 4
+struct acm_function_config {
+ int instances;
+};
+
+static int acm_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
+{
+ int ret;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ struct acm_function_config *config;
+#endif
+ f->config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL);
+ if (!f->config)
+ return -ENOMEM;
+
+ ret = gserial_setup(cdev->gadget, MAX_ACM_INSTANCES);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* default setting */
+ config = f->config;
+ config->instances = 1;
+#endif
+ return ret;
+}
+
+static void acm_function_cleanup(struct android_usb_function *f)
+{
+ gserial_cleanup();
+ kfree(f->config);
+ f->config = NULL;
+}
+
+static int acm_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
+{
+ int i;
+ int ret = 0;
+ struct acm_function_config *config = f->config;
+
+ for (i = 0; i < config->instances; i++) {
+ ret = acm_bind_config(c, i);
+ if (ret) {
+ pr_err("Could not bind acm%u config\n", i);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static ssize_t acm_instances_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct acm_function_config *config = f->config;
+ return sprintf(buf, "%d\n", config->instances);
+}
+
+static ssize_t acm_instances_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct acm_function_config *config = f->config;
+ int value;
+
+ sscanf(buf, "%d", &value);
+ if (value > MAX_ACM_INSTANCES)
+ value = MAX_ACM_INSTANCES;
+ config->instances = value;
+ return size;
+}
+
+static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, acm_instances_store);
+static struct device_attribute *acm_function_attributes[] = { &dev_attr_instances, NULL };
+
+static struct android_usb_function acm_function = {
+ .name = "acm",
+ .init = acm_function_init,
+ .cleanup = acm_function_cleanup,
+ .bind_config = acm_function_bind_config,
+ .attributes = acm_function_attributes,
+};
+
+
+static int mtp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
+{
+ return mtp_setup();
+}
+
+static void mtp_function_cleanup(struct android_usb_function *f)
+{
+ mtp_cleanup();
+}
+
+static int mtp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
+{
+ return mtp_bind_config(c, false);
+}
+
+static int ptp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
+{
+ /* nothing to do - initialization is handled by mtp_function_init */
+ return 0;
+}
+
+static void ptp_function_cleanup(struct android_usb_function *f)
+{
+ /* nothing to do - cleanup is handled by mtp_function_cleanup */
+}
+
+static int ptp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
+{
+ return mtp_bind_config(c, true);
+}
+
+static int mtp_function_ctrlrequest(struct android_usb_function *f,
+ struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *c)
+{
+ return mtp_ctrlrequest(cdev, c);
+}
+
+static struct android_usb_function mtp_function = {
+ .name = "mtp",
+ .init = mtp_function_init,
+ .cleanup = mtp_function_cleanup,
+ .bind_config = mtp_function_bind_config,
+ .ctrlrequest = mtp_function_ctrlrequest,
+};
+
+/* PTP function is same as MTP with slightly different interface descriptor */
+static struct android_usb_function ptp_function = {
+ .name = "ptp",
+ .init = ptp_function_init,
+ .cleanup = ptp_function_cleanup,
+ .bind_config = ptp_function_bind_config,
+};
+
+
+struct rndis_function_config {
+ u8 ethaddr[ETH_ALEN];
+ u32 vendorID;
+ char manufacturer[256];
+ bool wceis;
+};
+
+static int rndis_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
+{
+ f->config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL);
+ if (!f->config)
+ return -ENOMEM;
+ return 0;
+}
+
+static void rndis_function_cleanup(struct android_usb_function *f)
+{
+ kfree(f->config);
+ f->config = NULL;
+}
+
+static int rndis_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ int ret;
+ struct rndis_function_config *rndis = f->config;
+
+ if (!rndis) {
+ pr_err("%s: rndis_pdata\n", __func__);
+ return -1;
+ }
+
+ ret = gether_setup_name(c->cdev->gadget, rndis->ethaddr, "rndis");
+ if (ret) {
+ pr_err("%s: gether_setup failed\n", __func__);
+ return ret;
+ }
+
+ if (rndis->wceis) {
+ /* "Wireless" RNDIS; auto-detected by Windows */
+ rndis_iad_descriptor.bFunctionClass =
+ USB_CLASS_WIRELESS_CONTROLLER;
+ rndis_iad_descriptor.bFunctionSubClass = 0x01;
+ rndis_iad_descriptor.bFunctionProtocol = 0x03;
+ rndis_control_intf.bInterfaceClass =
+ USB_CLASS_WIRELESS_CONTROLLER;
+ rndis_control_intf.bInterfaceSubClass = 0x01;
+ rndis_control_intf.bInterfaceProtocol = 0x03;
+ }
+
+ return rndis_bind_config(c, rndis->ethaddr, rndis->vendorID,
+ rndis->manufacturer);
+}
+
+static void rndis_function_unbind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ gether_cleanup();
+}
+
+static ssize_t rndis_manufacturer_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *config = f->config;
+ return sprintf(buf, "%s\n", config->manufacturer);
+}
+
+static ssize_t rndis_manufacturer_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *config = f->config;
+
+ if (size >= sizeof(config->manufacturer))
+ return -EINVAL;
+ if (sscanf(buf, "%s", config->manufacturer) == 1)
+ return size;
+ return -1;
+}
+
+static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show,
+ rndis_manufacturer_store);
+
+static ssize_t rndis_wceis_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *config = f->config;
+ return sprintf(buf, "%d\n", config->wceis);
+}
+
+static ssize_t rndis_wceis_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *config = f->config;
+ int value;
+
+ if (sscanf(buf, "%d", &value) == 1) {
+ config->wceis = value;
+ return size;
+ }
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show,
+ rndis_wceis_store);
+
+static ssize_t rndis_ethaddr_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *rndis = f->config;
+ return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2],
+ rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]);
+}
+
+static ssize_t rndis_ethaddr_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *rndis = f->config;
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ int i;
+ char *src;
+ for (i = 0; i < ETH_ALEN; i++)
+ rndis->ethaddr[i] = 0;
+ /* create a fake MAC address from our serial number.
+ * first byte is 0x02 to signify locally administered.
+ */
+ rndis->ethaddr[0] = 0x02;
+ src = serial_string;
+ for (i = 0; (i < 256) && *src; i++) {
+ /* XOR the USB serial across the remaining bytes */
+ rndis->ethaddr[i % (ETH_ALEN - 1) + 1] ^= *src++;
+ }
+ return size;
+#else
+ if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n",
+ (int *)&rndis->ethaddr[0], (int *)&rndis->ethaddr[1],
+ (int *)&rndis->ethaddr[2], (int *)&rndis->ethaddr[3],
+ (int *)&rndis->ethaddr[4],
+ (int *)&rndis->ethaddr[5]) == 6)
+ return size;
+ return -EINVAL;
+#endif
+}
+
+static DEVICE_ATTR(ethaddr, S_IRUGO | S_IWUSR, rndis_ethaddr_show,
+ rndis_ethaddr_store);
+
+static ssize_t rndis_vendorID_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *config = f->config;
+ return sprintf(buf, "%04x\n", config->vendorID);
+}
+
+static ssize_t rndis_vendorID_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct rndis_function_config *config = f->config;
+ int value;
+
+ if (sscanf(buf, "%04x", &value) == 1) {
+ config->vendorID = value;
+ return size;
+ }
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show,
+ rndis_vendorID_store);
+
+static struct device_attribute *rndis_function_attributes[] = {
+ &dev_attr_manufacturer,
+ &dev_attr_wceis,
+ &dev_attr_ethaddr,
+ &dev_attr_vendorID,
+ NULL
+};
+
+static struct android_usb_function rndis_function = {
+ .name = "rndis",
+ .init = rndis_function_init,
+ .cleanup = rndis_function_cleanup,
+ .bind_config = rndis_function_bind_config,
+ .unbind_config = rndis_function_unbind_config,
+ .attributes = rndis_function_attributes,
+};
+
+
+struct mass_storage_function_config {
+ struct fsg_config fsg;
+ struct fsg_common *common;
+};
+
+static int mass_storage_function_init(struct android_usb_function *f,
+ struct usb_composite_dev *cdev)
+{
+ struct mass_storage_function_config *config;
+ struct fsg_common *common;
+ int err;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ int i;
+#endif
+ config = kzalloc(sizeof(struct mass_storage_function_config),
+ GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (android_usb_pdata && android_usb_pdata->nluns != 0) {
+ /* Some device use sd card or not.
+ * If you want to modify nluns,
+ * please change nluns of standard android USB platform data
+ * Please do not modify nluns directly in this function.
+ * Every model uses same android file.
+ */
+ printk(KERN_DEBUG "usb: %s pdata->nluns=%d\n", __func__,
+ android_usb_pdata->nluns);
+ config->fsg.nluns = android_usb_pdata->nluns;
+ for (i = 0; i < android_usb_pdata->nluns; i++) {
+#if defined(CONFIG_MACH_M0_CTC)
+ /*FOR CTC PC-MODEM START*/
+ if (2 == i) {
+ printk(KERN_DEBUG "%s Enable cdfs\n", __func__);
+ config->fsg.luns[i].ro = 1;
+ config->fsg.luns[i].cdrom = 1;
+ }
+ /*FOR CTC PC-MODEM END*/
+#endif
+ config->fsg.luns[i].removable = 1;
+ config->fsg.luns[i].nofua = 1;
+ }
+
+ common = fsg_common_init(NULL, cdev, &config->fsg);
+ if (IS_ERR(common)) {
+ kfree(config);
+ return PTR_ERR(common);
+ }
+
+ for (i = 0; i < android_usb_pdata->nluns; i++) {
+ char luns[5];
+ err = snprintf(luns, 5, "lun%d", i);
+ if (err == 0) {
+ printk(KERN_ERR "usb: %s snprintf error\n",
+ __func__);
+ kfree(config);
+ return err;
+ }
+ err = sysfs_create_link(&f->dev->kobj,
+ &common->luns[i].dev.kobj,
+ luns);
+ if (err) {
+ kfree(config);
+ return err;
+ }
+ }
+ } else {
+#endif
+ /* original mainline code */
+ printk(KERN_DEBUG "usb: %s pdata is not available. nluns=1\n",
+ __func__);
+ config->fsg.nluns = 1;
+ config->fsg.luns[0].removable = 1;
+
+ common = fsg_common_init(NULL, cdev, &config->fsg);
+ if (IS_ERR(common)) {
+ kfree(config);
+ return PTR_ERR(common);
+ }
+
+ err = sysfs_create_link(&f->dev->kobj,
+ &common->luns[0].dev.kobj,
+ "lun");
+ if (err) {
+ kfree(config);
+ return err;
+ }
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ }
+#endif
+ config->common = common;
+ f->config = config;
+ return 0;
+}
+
+static void mass_storage_function_cleanup(struct android_usb_function *f)
+{
+ kfree(f->config);
+ f->config = NULL;
+}
+
+static int mass_storage_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ struct mass_storage_function_config *config = f->config;
+ return fsg_bind_config(c->cdev, c, config->common);
+}
+
+static ssize_t mass_storage_inquiry_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct mass_storage_function_config *config = f->config;
+ return sprintf(buf, "%s\n", config->common->inquiry_string);
+}
+
+static ssize_t mass_storage_inquiry_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct mass_storage_function_config *config = f->config;
+ if (size >= sizeof(config->common->inquiry_string))
+ return -EINVAL;
+ if (sscanf(buf, "%s", config->common->inquiry_string) != 1)
+ return -EINVAL;
+ return size;
+}
+
+static DEVICE_ATTR(inquiry_string, S_IRUGO | S_IWUSR,
+ mass_storage_inquiry_show,
+ mass_storage_inquiry_store);
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+static ssize_t mass_storage_vendor_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct mass_storage_function_config *config = f->config;
+ return sprintf(buf, "%s\n", config->common->vendor_string);
+}
+
+static ssize_t mass_storage_vendor_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct mass_storage_function_config *config = f->config;
+
+ if (size >= sizeof(config->common->vendor_string))
+ return -EINVAL;
+ if (sscanf(buf, "%s", config->common->vendor_string) != 1)
+ return -EINVAL;
+
+ printk(KERN_DEBUG "%s: vendor %s", __func__,
+ config->common->vendor_string);
+ return size;
+}
+
+static DEVICE_ATTR(vendor_string, S_IRUGO | S_IWUSR,
+ mass_storage_vendor_show,
+ mass_storage_vendor_store);
+
+static ssize_t mass_storage_product_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct mass_storage_function_config *config = f->config;
+ return sprintf(buf, "%s\n", config->common->product_string);
+}
+
+static ssize_t mass_storage_product_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct mass_storage_function_config *config = f->config;
+
+ if (size >= sizeof(config->common->product_string))
+ return -EINVAL;
+ if (sscanf(buf, "%s", config->common->product_string) != 1)
+ return -EINVAL;
+
+ printk(KERN_DEBUG "%s: product %s", __func__,
+ config->common->product_string);
+ return size;
+}
+
+static DEVICE_ATTR(product_string, S_IRUGO | S_IWUSR,
+ mass_storage_product_show,
+ mass_storage_product_store);
+#endif
+
+static struct device_attribute *mass_storage_function_attributes[] = {
+ &dev_attr_inquiry_string,
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ &dev_attr_vendor_string,
+ &dev_attr_product_string,
+#endif
+ NULL
+};
+
+static struct android_usb_function mass_storage_function = {
+ .name = "mass_storage",
+ .init = mass_storage_function_init,
+ .cleanup = mass_storage_function_cleanup,
+ .bind_config = mass_storage_function_bind_config,
+ .attributes = mass_storage_function_attributes,
+};
+
+
+static int accessory_function_init(struct android_usb_function *f,
+ struct usb_composite_dev *cdev)
+{
+ return acc_setup();
+}
+
+static void accessory_function_cleanup(struct android_usb_function *f)
+{
+ acc_cleanup();
+}
+
+static int accessory_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ return acc_bind_config(c);
+}
+
+static int accessory_function_ctrlrequest(struct android_usb_function *f,
+ struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *c)
+{
+ return acc_ctrlrequest(cdev, c);
+}
+
+static struct android_usb_function accessory_function = {
+ .name = "accessory",
+ .init = accessory_function_init,
+ .cleanup = accessory_function_cleanup,
+ .bind_config = accessory_function_bind_config,
+ .ctrlrequest = accessory_function_ctrlrequest,
+};
+
+
+static int dm_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ return dm_bind_config(c, DM_PORT_NUM);
+}
+
+static struct android_usb_function dm_function = {
+ .name = "dm",
+ .bind_config = dm_function_bind_config,
+};
+
+static struct android_usb_function *supported_functions[] = {
+ &adb_function,
+ &acm_function,
+ &mtp_function,
+ &ptp_function,
+ &rndis_function,
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ &ncm_function,
+#endif
+ &mass_storage_function,
+ &accessory_function,
+ &dm_function,
+ NULL
+};
+
+
+static int android_init_functions(struct android_usb_function **functions,
+ struct usb_composite_dev *cdev)
+{
+ struct android_dev *dev = _android_dev;
+ struct android_usb_function *f;
+ struct device_attribute **attrs;
+ struct device_attribute *attr;
+ int err = 0;
+ int index = 0;
+
+ for (; (f = *functions++); index++) {
+ f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name);
+ f->dev = device_create(android_class, dev->dev,
+ MKDEV(0, index), f, f->dev_name);
+ if (IS_ERR(f->dev)) {
+ pr_err("%s: Failed to create dev %s", __func__,
+ f->dev_name);
+ err = PTR_ERR(f->dev);
+ goto err_create;
+ }
+
+ if (f->init) {
+ err = f->init(f, cdev);
+ if (err) {
+ pr_err("%s: Failed to init %s", __func__,
+ f->name);
+ goto err_out;
+ }
+ }
+
+ attrs = f->attributes;
+ if (attrs) {
+ while ((attr = *attrs++) && !err)
+ err = device_create_file(f->dev, attr);
+ }
+ if (err) {
+ pr_err("%s: Failed to create function %s attributes",
+ __func__, f->name);
+ goto err_out;
+ }
+ }
+ return 0;
+
+err_out:
+ device_destroy(android_class, f->dev->devt);
+err_create:
+ kfree(f->dev_name);
+ return err;
+}
+
+static void android_cleanup_functions(struct android_usb_function **functions)
+{
+ struct android_usb_function *f;
+
+ while (*functions) {
+ f = *functions++;
+
+ if (f->dev) {
+ device_destroy(android_class, f->dev->devt);
+ kfree(f->dev_name);
+ }
+
+ if (f->cleanup)
+ f->cleanup(f);
+ }
+}
+
+static int
+android_bind_enabled_functions(struct android_dev *dev,
+ struct usb_configuration *c)
+{
+ struct android_usb_function *f;
+ int ret;
+
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ printk(KERN_DEBUG "usb: %s f:%s\n", __func__, f->name);
+ ret = f->bind_config(f, c);
+ if (ret) {
+ pr_err("%s: %s failed", __func__, f->name);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void
+android_unbind_enabled_functions(struct android_dev *dev,
+ struct usb_configuration *c)
+{
+ struct android_usb_function *f;
+
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ if (f->unbind_config)
+ f->unbind_config(f, c);
+ }
+}
+
+static int android_enable_function(struct android_dev *dev, char *name)
+{
+ struct android_usb_function **functions = dev->functions;
+ struct android_usb_function *f;
+ printk(KERN_DEBUG "usb: %s name=%s\n", __func__, name);
+ while ((f = *functions++)) {
+ if (!strcmp(name, f->name)) {
+ list_add_tail(&f->enabled_list, &dev->enabled_functions);
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+/*-------------------------------------------------------------------------*/
+/* /sys/class/android_usb/android%d/ interface */
+
+static ssize_t
+functions_show(struct device *pdev, struct device_attribute *attr, char *buf)
+{
+ struct android_dev *dev = dev_get_drvdata(pdev);
+ struct android_usb_function *f;
+ char *buff = buf;
+
+ mutex_lock(&dev->mutex);
+
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ printk(KERN_DEBUG "usb: %s enabled_func=%s\n",
+ __func__, f->name);
+ buff += sprintf(buff, "%s,", f->name);
+ }
+
+ mutex_unlock(&dev->mutex);
+
+ if (buff != buf)
+ *(buff-1) = '\n';
+
+ return buff - buf;
+}
+
+static ssize_t
+functions_store(struct device *pdev, struct device_attribute *attr,
+ const char *buff, size_t size)
+{
+ struct android_dev *dev = dev_get_drvdata(pdev);
+ char *name;
+ char buf[256], *b;
+ int err;
+
+ mutex_lock(&dev->mutex);
+
+ if (dev->enabled) {
+ mutex_unlock(&dev->mutex);
+ return -EBUSY;
+ }
+
+ INIT_LIST_HEAD(&dev->enabled_functions);
+
+ printk(KERN_DEBUG "usb: %s buff=%s\n", __func__, buff);
+ strncpy(buf, buff, sizeof(buf));
+ b = strim(buf);
+
+ while (b) {
+ name = strsep(&b, ",");
+ if (name) {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* Enable ncm function */
+ if (is_ncm_ready(name)) {
+ printk(KERN_DEBUG "usb: %s ncm on\n",
+ __func__);
+ err = android_enable_function(dev,
+ "ncm");
+ continue;
+ }
+#endif
+ err = android_enable_function(dev, name);
+ if (err)
+ pr_err("android_usb: Cannot enable '%s'", name);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* Enable ACM function, if MTP is enabled. */
+ if (!strcmp(name, "mtp")) {
+ err = android_enable_function(dev, "acm");
+ if (err)
+ pr_err(
+ "android_usb: Cannot enable '%s'",
+ name);
+ }
+#endif
+ }
+ }
+
+ mutex_unlock(&dev->mutex);
+
+ return size;
+}
+
+static ssize_t enable_show(struct device *pdev, struct device_attribute *attr,
+ char *buf)
+{
+ struct android_dev *dev = dev_get_drvdata(pdev);
+ printk(KERN_DEBUG "usb: %s dev->enabled=%d\n", __func__, dev->enabled);
+ return sprintf(buf, "%d\n", dev->enabled);
+}
+
+static ssize_t enable_store(struct device *pdev, struct device_attribute *attr,
+ const char *buff, size_t size)
+{
+ struct android_dev *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev = dev->cdev;
+ int enabled = 0;
+
+ mutex_lock(&dev->mutex);
+
+ sscanf(buff, "%d", &enabled);
+ printk(KERN_DEBUG "usb: %s enabled=%d, !dev->enabled=%d\n",
+ __func__, enabled, !dev->enabled);
+ if (enabled && !dev->enabled) {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ struct android_usb_function *f;
+ cdev->next_string_id = composite_string_index;
+#else
+ cdev->next_string_id = 0;
+#endif
+ /* update values in composite driver's copy of device descriptor */
+ cdev->desc.idVendor = device_desc.idVendor;
+ cdev->desc.idProduct = device_desc.idProduct;
+ cdev->desc.bcdDevice = device_desc.bcdDevice;
+ cdev->desc.bDeviceClass = device_desc.bDeviceClass;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ printk(KERN_DEBUG "usb: %s f:%s\n", __func__, f->name);
+ if (!strcmp(f->name, "acm")) {
+ printk(KERN_DEBUG "usb: acm is enabled. (bcdDevice=0x400)\n");
+ /* Samsung KIES needs fixed bcdDevice number */
+ cdev->desc.bcdDevice = cpu_to_le16(0x0400);
+ }
+ if (is_ncm_ready(f->name))
+ set_ncm_device_descriptor(&cdev->desc);
+ }
+ strncpy(manufacturer_string, "SAMSUNG",
+ sizeof(manufacturer_string) - 1);
+ strncpy(product_string, "SAMSUNG_Android",
+ sizeof(product_string) - 1);
+#endif
+ cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass;
+ cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol;
+ printk(KERN_DEBUG "usb: %s vendor=%x,product=%x,bcdDevice=%x",
+ __func__, cdev->desc.idVendor,
+ cdev->desc.idProduct, cdev->desc.bcdDevice);
+ printk(KERN_DEBUG ",Class=%x,SubClass=%x,Protocol=%x\n",
+ cdev->desc.bDeviceClass,
+ cdev->desc.bDeviceSubClass,
+ cdev->desc.bDeviceProtocol);
+ printk(KERN_DEBUG "usb: %s next cmd : usb_add_config\n",
+ __func__);
+ usb_add_config(cdev, &android_config_driver,
+ android_bind_config);
+ usb_gadget_connect(cdev->gadget);
+ dev->enabled = true;
+ } else if (!enabled && dev->enabled) {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* avoid sending a disconnect switch event
+ * until after we disconnect.
+ */
+ cdev->mute_switch = true;
+#endif
+ usb_gadget_disconnect(cdev->gadget);
+ /* Cancel pending control requests */
+ usb_ep_dequeue(cdev->gadget->ep0, cdev->req);
+ usb_remove_config(cdev, &android_config_driver);
+ dev->enabled = false;
+ mdelay(5);
+ } else if (!enabled) {
+ usb_gadget_disconnect(cdev->gadget);
+ dev->enabled = false;
+ } else {
+ pr_err("android_usb: already %s\n",
+ dev->enabled ? "enabled" : "disabled");
+ }
+
+ mutex_unlock(&dev->mutex);
+ return size;
+}
+
+static ssize_t state_show(struct device *pdev, struct device_attribute *attr,
+ char *buf)
+{
+ struct android_dev *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev = dev->cdev;
+ char *state = "DISCONNECTED";
+ unsigned long flags;
+
+ if (!cdev)
+ goto out;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ state = "CONFIGURED";
+ else if (dev->connected)
+ state = "CONNECTED";
+ spin_unlock_irqrestore(&cdev->lock, flags);
+out:
+ printk(KERN_DEBUG "usb: %s buf=%s\n", __func__, state);
+ return sprintf(buf, "%s\n", state);
+}
+
+#define DESCRIPTOR_ATTR(field, format_string) \
+static ssize_t \
+field ## _show(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return sprintf(buf, format_string, device_desc.field); \
+} \
+static ssize_t \
+field ## _store(struct device *dev, struct device_attribute *attr, \
+ const char *buf, size_t size) \
+{ \
+ int value; \
+ if (sscanf(buf, format_string, &value) == 1) { \
+ device_desc.field = value; \
+ return size; \
+ } \
+ return -1; \
+} \
+static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store);
+
+#define DESCRIPTOR_STRING_ATTR(field, buffer) \
+static ssize_t \
+field ## _show(struct device *dev, struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return sprintf(buf, "%s", buffer); \
+} \
+static ssize_t \
+field ## _store(struct device *dev, struct device_attribute *attr, \
+ const char *buf, size_t size) \
+{ \
+ if (size >= sizeof(buffer)) return -EINVAL; \
+ if (sscanf(buf, "%s", buffer) == 1) { \
+ return size; \
+ } \
+ return -1; \
+} \
+static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store);
+
+
+DESCRIPTOR_ATTR(idVendor, "%04x\n")
+DESCRIPTOR_ATTR(idProduct, "%04x\n")
+DESCRIPTOR_ATTR(bcdDevice, "%04x\n")
+DESCRIPTOR_ATTR(bDeviceClass, "%d\n")
+DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n")
+DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n")
+DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string)
+DESCRIPTOR_STRING_ATTR(iProduct, product_string)
+DESCRIPTOR_STRING_ATTR(iSerial, serial_string)
+
+static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show, functions_store);
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store);
+static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
+
+static struct device_attribute *android_usb_attributes[] = {
+ &dev_attr_idVendor,
+ &dev_attr_idProduct,
+ &dev_attr_bcdDevice,
+ &dev_attr_bDeviceClass,
+ &dev_attr_bDeviceSubClass,
+ &dev_attr_bDeviceProtocol,
+ &dev_attr_iManufacturer,
+ &dev_attr_iProduct,
+ &dev_attr_iSerial,
+ &dev_attr_functions,
+ &dev_attr_enable,
+ &dev_attr_state,
+ NULL
+};
+
+/*-------------------------------------------------------------------------*/
+/* Composite driver */
+
+static int android_bind_config(struct usb_configuration *c)
+{
+ struct android_dev *dev = _android_dev;
+ int ret = 0;
+
+ ret = android_bind_enabled_functions(dev, c);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void android_unbind_config(struct usb_configuration *c)
+{
+ struct android_dev *dev = _android_dev;
+
+ android_unbind_enabled_functions(dev, c);
+}
+
+static int android_bind(struct usb_composite_dev *cdev)
+{
+ struct android_dev *dev = _android_dev;
+ struct usb_gadget *gadget = cdev->gadget;
+ int gcnum, id, ret;
+
+ printk(KERN_DEBUG "usb: %s disconnect\n", __func__);
+ usb_gadget_disconnect(gadget);
+
+ ret = android_init_functions(dev->functions, cdev);
+ if (ret)
+ return ret;
+
+ /* Allocate string descriptor numbers ... note that string
+ * contents can be overridden by the composite_dev glue.
+ */
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_MANUFACTURER_IDX].id = id;
+ device_desc.iManufacturer = id;
+
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_PRODUCT_IDX].id = id;
+ device_desc.iProduct = id;
+
+ /* Default strings - should be updated by userspace */
+ strncpy(manufacturer_string, "Android", sizeof(manufacturer_string) - 1);
+ strncpy(product_string, "Android", sizeof(product_string) - 1);
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ sprintf(serial_string,
+ "%08X%08X", system_serial_high, system_serial_low);
+#else
+ strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1);
+#endif
+
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_SERIAL_IDX].id = id;
+ device_desc.iSerialNumber = id;
+
+ gcnum = usb_gadget_controller_number(gadget);
+ if (gcnum >= 0)
+ device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
+ else {
+ /* gadget zero is so simple (for now, no altsettings) that
+ * it SHOULD NOT have problems with bulk-capable hardware.
+ * so just warn about unrcognized controllers -- don't panic.
+ *
+ * things like configuration and altsetting numbering
+ * can need hardware-specific attention though.
+ */
+ pr_warning("%s: controller '%s' not recognized\n",
+ longname, gadget->name);
+ device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
+ }
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ composite_string_index = 4;
+#endif
+ usb_gadget_set_selfpowered(gadget);
+ dev->cdev = cdev;
+
+ return 0;
+}
+
+static int android_usb_unbind(struct usb_composite_dev *cdev)
+{
+ struct android_dev *dev = _android_dev;
+ printk(KERN_DEBUG "usb: %s\n", __func__);
+ cancel_work_sync(&dev->work);
+ android_cleanup_functions(dev->functions);
+ return 0;
+}
+
+static struct usb_composite_driver android_usb_driver = {
+ .name = "android_usb",
+ .dev = &device_desc,
+ .strings = dev_strings,
+ .unbind = android_usb_unbind,
+};
+
+static int
+android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c)
+{
+ struct android_dev *dev = _android_dev;
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct usb_request *req = cdev->req;
+ struct android_usb_function *f;
+ int value = -EOPNOTSUPP;
+ unsigned long flags;
+
+ req->zero = 0;
+ req->complete = composite_setup_complete;
+ req->length = 0;
+ gadget->ep0->driver_data = cdev;
+
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ if (f->ctrlrequest) {
+ value = f->ctrlrequest(f, cdev, c);
+ if (value >= 0)
+ break;
+ }
+ }
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (value < 0)
+ value = terminal_ctrl_request(cdev, c);
+#endif
+
+ /* Special case the accessory function.
+ * It needs to handle control requests before it is enabled.
+ */
+ if (value < 0)
+ value = acc_ctrlrequest(cdev, c);
+
+ if (value < 0)
+ value = composite_setup(gadget, c);
+
+ spin_lock_irqsave(&cdev->lock, flags);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
+ cdev->mute_switch == true)
+ cdev->mute_switch = false;
+#endif
+ if (!dev->connected) {
+ dev->connected = 1;
+ schedule_work(&dev->work);
+ }
+ else if (c->bRequest == USB_REQ_SET_CONFIGURATION && cdev->config) {
+ schedule_work(&dev->work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ return value;
+}
+
+static void android_disconnect(struct usb_gadget *gadget)
+{
+ struct android_dev *dev = _android_dev;
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ unsigned long flags;
+
+ composite_disconnect(gadget);
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ dev->connected = 0;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ printk(KERN_DEBUG "usb: %s con(%d), sw(%d)\n",
+ __func__, dev->connected, dev->sw_connected);
+ /* avoid sending a disconnect switch event
+ * until after we disconnect.
+ */
+ if (cdev->mute_switch) {
+ dev->sw_connected = dev->connected;
+ printk(KERN_DEBUG "usb: %s mute_switch con(%d) sw(%d)\n",
+ __func__, dev->connected, dev->sw_connected);
+ } else {
+ set_ncm_ready(false);
+ if (cdev->force_disconnect) {
+ dev->sw_connected = 1;
+ printk(KERN_DEBUG "usb: %s force_disconnect\n",
+ __func__);
+ cdev->force_disconnect = 0;
+ }
+ printk(KERN_DEBUG "usb: %s schedule_work con(%d) sw(%d)\n",
+ __func__, dev->connected, dev->sw_connected);
+ schedule_work(&dev->work);
+ }
+#else
+ schedule_work(&dev->work);
+#endif
+ spin_unlock_irqrestore(&cdev->lock, flags);
+}
+
+static int android_create_device(struct android_dev *dev)
+{
+ struct device_attribute **attrs = android_usb_attributes;
+ struct device_attribute *attr;
+ int err;
+
+ dev->dev = device_create(android_class, NULL,
+ MKDEV(0, 0), NULL, "android0");
+ if (IS_ERR(dev->dev))
+ return PTR_ERR(dev->dev);
+
+ dev_set_drvdata(dev->dev, dev);
+
+ while ((attr = *attrs++)) {
+ err = device_create_file(dev->dev, attr);
+ if (err) {
+ device_destroy(android_class, dev->dev->devt);
+ return err;
+ }
+ }
+ return 0;
+}
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+static int android_probe(struct platform_device *pdev)
+{
+ android_usb_pdata = pdev->dev.platform_data;
+
+ printk(KERN_INFO "usb: %s pdata: %p\n", __func__, android_usb_pdata);
+
+ if (android_usb_pdata) {
+ printk(KERN_INFO "usb: %s nluns=%d\n", __func__,
+ android_usb_pdata->nluns);
+ } else {
+ printk(KERN_INFO "usb: %s pdata is not available\n", __func__);
+ }
+ return 0;
+}
+
+static struct platform_driver android_platform_driver = {
+ .driver = { .name = "android_usb", },
+ .probe = android_probe,
+};
+#endif
+
+static int __init init(void)
+{
+ struct android_dev *dev;
+ int err;
+
+ printk(KERN_DEBUG "%s\n", __func__);
+ android_class = class_create(THIS_MODULE, "android_usb");
+ if (IS_ERR(android_class))
+ return PTR_ERR(android_class);
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ dev->functions = supported_functions;
+ INIT_LIST_HEAD(&dev->enabled_functions);
+ INIT_WORK(&dev->work, android_work);
+ mutex_init(&dev->mutex);
+
+ err = android_create_device(dev);
+ if (err) {
+ class_destroy(android_class);
+ kfree(dev);
+ return err;
+ }
+
+ _android_dev = dev;
+
+ /* Override composite driver functions */
+ composite_driver.setup = android_setup;
+ composite_driver.disconnect = android_disconnect;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* Create below sysfs
+ * /sys/class/android_usb/android0/terminal_version
+ */
+ err = create_terminal_attribute(&dev->dev);
+ if (err) {
+ printk(KERN_ERR "usb: %s To create terminal_atttrr is failed\n",
+ __func__);
+ return err;
+ }
+ err = platform_driver_register(&android_platform_driver);
+ if (err) {
+ printk(KERN_ERR "usb: %s platform register is failed\n",
+ __func__);
+ return err;
+ }
+#endif
+
+#ifdef CONFIG_USB_DUN_SUPPORT
+ err = modem_misc_register();
+ if (err) {
+ printk(KERN_ERR "usb: %s modem misc register is failed\n",
+ __func__);
+ return err;
+ }
+#endif
+
+ return usb_composite_probe(&android_usb_driver, android_bind);
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+ usb_composite_unregister(&android_usb_driver);
+ class_destroy(android_class);
+ kfree(_android_dev);
+ _android_dev = NULL;
+}
+module_exit(cleanup);
diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c
index 5cbb1a4..274facf 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -27,7 +27,13 @@
#include <linux/utsname.h>
#include <linux/usb/composite.h>
+#include <asm/unaligned.h>
+#include "multi_config.h"
+/*
+#undef DBG
+#define DBG(dev, fmt, args...) printk(KERN_DEBUG "usb: "fmt, ##args)
+*/
/*
* The code in this file is utility code, used to build a gadget driver
@@ -74,7 +80,6 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string");
static char composite_manufacturer[50];
/*-------------------------------------------------------------------------*/
-
/**
* usb_add_function() - add a function to a configuration
* @config: the configuration
@@ -123,6 +128,8 @@ int usb_add_function(struct usb_configuration *config,
config->fullspeed = true;
if (!config->highspeed && function->hs_descriptors)
config->highspeed = true;
+ if (!config->superspeed && function->ss_descriptors)
+ config->superspeed = true;
done:
if (value)
@@ -247,7 +254,11 @@ static int config_buf(struct usb_configuration *config,
c->bDescriptorType = type;
/* wTotalLength is written later */
c->bNumInterfaces = config->next_interface_id;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ c->bConfigurationValue = get_config_number() + 1;
+#else
c->bConfigurationValue = config->bConfigurationValue;
+#endif
c->iConfiguration = config->iConfiguration;
c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes;
c->bMaxPower = config->bMaxPower ? : (CONFIG_USB_GADGET_VBUS_DRAW / 2);
@@ -266,20 +277,44 @@ static int config_buf(struct usb_configuration *config,
list_for_each_entry(f, &config->functions, list) {
struct usb_descriptor_header **descriptors;
- if (speed == USB_SPEED_HIGH)
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (!is_available_function(f->name)) {
+ USB_DBG("skip f->%s\n", f->name);
+ continue;
+ } else {
+ USB_DBG("f->%s\n", f->name);
+ }
+#endif
+ switch (speed) {
+ case USB_SPEED_SUPER:
+ descriptors = f->ss_descriptors;
+ break;
+ case USB_SPEED_HIGH:
descriptors = f->hs_descriptors;
- else
+ break;
+ default:
descriptors = f->descriptors;
+ }
+
if (!descriptors)
continue;
status = usb_descriptor_fillbuf(next, len,
(const struct usb_descriptor_header **) descriptors);
if (status < 0)
return status;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (change_conf(f, next, len, config, speed) < 0) {
+ USB_DBG_ESS("failed to change configuration\n");
+ return -EINVAL;
+ }
+#endif
len -= status;
next += status;
}
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ set_interface_count(config, c);
+#endif
len = next - buf;
c->wTotalLength = cpu_to_le16(len);
return len;
@@ -292,9 +327,10 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
u8 type = w_value >> 8;
enum usb_device_speed speed = USB_SPEED_UNKNOWN;
- if (gadget_is_dualspeed(gadget)) {
- int hs = 0;
-
+ if (gadget->speed == USB_SPEED_SUPER)
+ speed = gadget->speed;
+ else if (gadget_is_dualspeed(gadget)) {
+ int hs = 0;
if (gadget->speed == USB_SPEED_HIGH)
hs = 1;
if (type == USB_DT_OTHER_SPEED_CONFIG)
@@ -306,15 +342,34 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
/* This is a lookup by config *INDEX* */
w_value &= 0xff;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ w_value = set_config_number(w_value);
+#endif
list_for_each_entry(c, &cdev->configs, list) {
/* ignore configs that won't work at this speed */
- if (speed == USB_SPEED_HIGH) {
+ switch (speed) {
+ case USB_SPEED_SUPER:
+ if (!c->superspeed)
+ continue;
+ /* USB 3.0 Max power < 900 mA */
+ if (c->bMaxPower > 0x70) /* 896mA = 0x70 * 8mA */
+ c->bMaxPower = 0x70; /* 896 mA */
+ break;
+ case USB_SPEED_HIGH:
if (!c->highspeed)
continue;
- } else {
+ /* USB 2.0 Max power < 500 mA */
+ if (c->bMaxPower > 0xFA) /* 500mA = 0xFA * 2mA */
+ c->bMaxPower = 0xFA; /* 500 mA */
+ break;
+ default:
if (!c->fullspeed)
continue;
+ /* USB 2.0 Max power < 500 mA */
+ if (c->bMaxPower > 0xFA) /* 500mA = 0xFA * 2mA */
+ c->bMaxPower = 0xFA; /* 500 mA */
}
+
if (w_value == 0)
return config_buf(c, speed, cdev->req->buf, type);
w_value--;
@@ -328,16 +383,22 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type)
struct usb_configuration *c;
unsigned count = 0;
int hs = 0;
+ int ss = 0;
if (gadget_is_dualspeed(gadget)) {
if (gadget->speed == USB_SPEED_HIGH)
hs = 1;
+ if (gadget->speed == USB_SPEED_SUPER)
+ ss = 1;
if (type == USB_DT_DEVICE_QUALIFIER)
hs = !hs;
}
list_for_each_entry(c, &cdev->configs, list) {
/* ignore configs that won't work at this speed */
- if (hs) {
+ if (ss) {
+ if (!c->superspeed)
+ continue;
+ } else if (hs) {
if (!c->highspeed)
continue;
} else {
@@ -345,10 +406,78 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type)
continue;
}
count++;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ count = count_multi_config(c, count);
+#endif
}
return count;
}
+/**
+ * bos_desc() - prepares the BOS descriptor.
+ * @cdev: pointer to usb_composite device to generate the bos
+ * descriptor for
+ *
+ * This function generates the BOS (Binary Device Object)
+ * descriptor and its device capabilities descriptors. The BOS
+ * descriptor should be supported by a SuperSpeed device.
+ */
+static int bos_desc(struct usb_composite_dev *cdev)
+{
+ struct usb_ext_cap_descriptor *usb_ext;
+ struct usb_ss_cap_descriptor *ss_cap;
+ struct usb_dcd_config_params dcd_config_params;
+ struct usb_bos_descriptor *bos = cdev->req->buf;
+
+ bos->bLength = USB_DT_BOS_SIZE;
+ bos->bDescriptorType = USB_DT_BOS;
+
+ bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE);
+ bos->bNumDeviceCaps = 0;
+
+ /*
+ * A SuperSpeed device shall include the USB2.0 extension descriptor
+ * and shall support LPM when operating in USB2.0 HS mode.
+ */
+ usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
+ usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
+ usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
+ usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT);
+
+ /*
+ * The Superspeed USB Capability descriptor shall be implemented by all
+ * SuperSpeed devices.
+ */
+ ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
+ bos->bNumDeviceCaps++;
+ le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE);
+ ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
+ ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
+ ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
+ ss_cap->bmAttributes = 0; /* LTM is not supported yet */
+ ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION |
+ USB_FULL_SPEED_OPERATION |
+ USB_HIGH_SPEED_OPERATION |
+ USB_5GBPS_OPERATION);
+ ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
+
+ /* Get Controller configuration */
+ if (cdev->gadget->ops->get_config_params)
+ cdev->gadget->ops->get_config_params(&dcd_config_params);
+ else {
+ dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT;
+ dcd_config_params.bU2DevExitLat =
+ cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT);
+ }
+ ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat;
+ ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat;
+
+ return le16_to_cpu(bos->wTotalLength);
+}
+
static void device_qual(struct usb_composite_dev *cdev)
{
struct usb_qualifier_descriptor *qual = cdev->req->buf;
@@ -361,7 +490,7 @@ static void device_qual(struct usb_composite_dev *cdev)
qual->bDeviceSubClass = cdev->desc.bDeviceSubClass;
qual->bDeviceProtocol = cdev->desc.bDeviceProtocol;
/* ASSUME same EP0 fifo size at both speeds */
- qual->bMaxPacketSize0 = cdev->desc.bMaxPacketSize0;
+ qual->bMaxPacketSize0 = cdev->gadget->ep0->maxpacket;
qual->bNumConfigurations = count_configs(cdev, USB_DT_DEVICE_QUALIFIER);
qual->bRESERVED = 0;
}
@@ -392,20 +521,32 @@ static int set_config(struct usb_composite_dev *cdev,
unsigned power = gadget_is_otg(gadget) ? 8 : 100;
int tmp;
- if (cdev->config)
- reset_config(cdev);
-
if (number) {
list_for_each_entry(c, &cdev->configs, list) {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (c->bConfigurationValue == number ||
+ check_config(number)) {
+#else
if (c->bConfigurationValue == number) {
+#endif
+ /*
+ * We disable the FDs of the previous
+ * configuration only if the new configuration
+ * is a valid one
+ */
+ if (cdev->config)
+ reset_config(cdev);
result = 0;
break;
}
}
if (result < 0)
goto done;
- } else
+ } else { /* Zero configuration value - need to reset the config */
+ if (cdev->config)
+ reset_config(cdev);
result = 0;
+ }
INFO(cdev, "%s speed config #%d: %s\n",
({ char *speed;
@@ -413,6 +554,7 @@ static int set_config(struct usb_composite_dev *cdev,
case USB_SPEED_LOW: speed = "low"; break;
case USB_SPEED_FULL: speed = "full"; break;
case USB_SPEED_HIGH: speed = "high"; break;
+ case USB_SPEED_SUPER: speed = "super"; break;
default: speed = "?"; break;
} ; speed; }), number, c ? c->label : "unconfigured");
@@ -428,17 +570,25 @@ static int set_config(struct usb_composite_dev *cdev,
if (!f)
break;
-
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ USB_DBG_ESS("e %s[%d]\n", f->name, tmp);
+#endif
/*
* Record which endpoints are used by the function. This is used
* to dispatch control requests targeted at that endpoint to the
* function's setup callback instead of the current
* configuration's setup callback.
*/
- if (gadget->speed == USB_SPEED_HIGH)
+ switch (gadget->speed) {
+ case USB_SPEED_SUPER:
+ descriptors = f->ss_descriptors;
+ break;
+ case USB_SPEED_HIGH:
descriptors = f->hs_descriptors;
- else
+ break;
+ default:
descriptors = f->descriptors;
+ }
for (; *descriptors; ++descriptors) {
struct usb_endpoint_descriptor *ep;
@@ -476,6 +626,7 @@ static int set_config(struct usb_composite_dev *cdev,
power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW;
done:
usb_gadget_vbus_draw(gadget, power);
+
if (result >= 0 && cdev->delayed_status)
result = USB_GADGET_DELAYED_STATUS;
return result;
@@ -523,6 +674,7 @@ int usb_add_config(struct usb_composite_dev *cdev,
INIT_LIST_HEAD(&config->functions);
config->next_interface_id = 0;
+ memset(config->interface, '\0', sizeof(config->interface));
status = bind(config);
if (status < 0) {
@@ -531,8 +683,9 @@ int usb_add_config(struct usb_composite_dev *cdev,
} else {
unsigned i;
- DBG(cdev, "cfg %d/%p speeds:%s%s\n",
+ DBG(cdev, "cfg %d/%p speeds:%s%s%s\n",
config->bConfigurationValue, config,
+ config->superspeed ? " super" : "",
config->highspeed ? " high" : "",
config->fullspeed
? (gadget_is_dualspeed(cdev->gadget)
@@ -562,6 +715,47 @@ done:
return status;
}
+static int remove_config(struct usb_composite_dev *cdev,
+ struct usb_configuration *config)
+{
+ while (!list_empty(&config->functions)) {
+ struct usb_function *f;
+
+ f = list_first_entry(&config->functions,
+ struct usb_function, list);
+ list_del(&f->list);
+ if (f->unbind) {
+ DBG(cdev, "unbind function '%s'/%p\n", f->name, f);
+ f->unbind(config, f);
+ /* may free memory for "f" */
+ }
+ }
+ list_del(&config->list);
+ if (config->unbind) {
+ DBG(cdev, "unbind config '%s'/%p\n", config->label, config);
+ config->unbind(config);
+ /* may free memory for "c" */
+ }
+ return 0;
+}
+
+int usb_remove_config(struct usb_composite_dev *cdev,
+ struct usb_configuration *config)
+{
+ unsigned long flags;
+
+ printk(KERN_DEBUG "usb: %s cdev->config=%p, config=%p\n",
+ __func__, cdev->config, config);
+ spin_lock_irqsave(&cdev->lock, flags);
+
+ if (cdev->config == config)
+ reset_config(cdev);
+
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ return remove_config(cdev, config);
+}
+
/*-------------------------------------------------------------------------*/
/* We support strings in multiple languages ... string descriptor zero
@@ -641,6 +835,14 @@ static int get_string(struct usb_composite_dev *cdev,
collect_langs(sp, s->wData);
list_for_each_entry(f, &c->functions, list) {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (!is_available_function(f->name)) {
+ USB_DBG("skip f->%s\n", f->name);
+ continue;
+ } else {
+ USB_DBG("f->%s\n", f->name);
+ }
+#endif
sp = f->strings;
if (sp)
collect_langs(sp, s->wData);
@@ -723,8 +925,12 @@ int usb_string_id(struct usb_composite_dev *cdev)
* supported languages */
/* 255 reserved as well? -- mina86 */
cdev->next_string_id++;
+ printk(KERN_DEBUG "usb: %s cdev(0x%p)->next_string_id=%d\n",
+ __func__, cdev, cdev->next_string_id);
return cdev->next_string_id;
}
+ printk(KERN_DEBUG "usb: %s error cdev(0x%p)->next_string_id=%d\n",
+ __func__, cdev, cdev->next_string_id);
return -ENODEV;
}
@@ -748,6 +954,8 @@ int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str)
{
int next = cdev->next_string_id;
+ printk(KERN_DEBUG "usb: %s --cdev(0x%p)->next_string_id=%d\n",
+ __func__, cdev, cdev->next_string_id);
for (; str->s; ++str) {
if (unlikely(next >= 254))
return -ENODEV;
@@ -781,6 +989,8 @@ int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str)
int usb_string_ids_n(struct usb_composite_dev *c, unsigned n)
{
unsigned next = c->next_string_id;
+ printk(KERN_DEBUG "usb: %s --cdev(0x%p)->next_string_id=%d\n",
+ __func__, c, c->next_string_id);
if (unlikely(n > 254 || (unsigned)next + n > 254))
return -ENODEV;
c->next_string_id += n;
@@ -811,6 +1021,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
struct usb_composite_dev *cdev = get_gadget_data(gadget);
struct usb_request *req = cdev->req;
int value = -EOPNOTSUPP;
+ int status = 0;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u8 intf = w_index & 0xFF;
u16 w_value = le16_to_cpu(ctrl->wValue);
@@ -838,18 +1049,32 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
case USB_DT_DEVICE:
cdev->desc.bNumConfigurations =
count_configs(cdev, USB_DT_DEVICE);
+ cdev->desc.bMaxPacketSize0 =
+ cdev->gadget->ep0->maxpacket;
+ if (gadget_is_superspeed(gadget)) {
+ if (gadget->speed >= USB_SPEED_SUPER) {
+ cdev->desc.bcdUSB = cpu_to_le16(0x0300);
+ cdev->desc.bMaxPacketSize0 = 9;
+ } else {
+ cdev->desc.bcdUSB = cpu_to_le16(0x0210);
+ }
+ }
+
value = min(w_length, (u16) sizeof cdev->desc);
memcpy(req->buf, &cdev->desc, value);
+ printk(KERN_DEBUG "usb: GET_DES\n");
break;
case USB_DT_DEVICE_QUALIFIER:
- if (!gadget_is_dualspeed(gadget))
+ if (!gadget_is_dualspeed(gadget) ||
+ gadget->speed >= USB_SPEED_SUPER)
break;
device_qual(cdev);
value = min_t(int, w_length,
sizeof(struct usb_qualifier_descriptor));
break;
case USB_DT_OTHER_SPEED_CONFIG:
- if (!gadget_is_dualspeed(gadget))
+ if (!gadget_is_dualspeed(gadget) ||
+ gadget->speed >= USB_SPEED_SUPER)
break;
/* FALLTHROUGH */
case USB_DT_CONFIG:
@@ -858,11 +1083,20 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
value = min(w_length, (u16) value);
break;
case USB_DT_STRING:
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ set_string_mode(w_length);
+#endif
value = get_string(cdev, req->buf,
w_index, w_value & 0xff);
if (value >= 0)
value = min(w_length, (u16) value);
break;
+ case USB_DT_BOS:
+ if (gadget_is_superspeed(gadget)) {
+ value = bos_desc(cdev);
+ value = min(w_length, (u16) value);
+ }
+ break;
}
break;
@@ -881,12 +1115,17 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
spin_lock(&cdev->lock);
value = set_config(cdev, ctrl, w_value);
spin_unlock(&cdev->lock);
+ printk(KERN_DEBUG "usb: SET_CON\n");
break;
case USB_REQ_GET_CONFIGURATION:
if (ctrl->bRequestType != USB_DIR_IN)
goto unknown;
if (cdev->config)
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ *(u8 *)req->buf = get_config_number() + 1;
+#else
*(u8 *)req->buf = cdev->config->bConfigurationValue;
+#endif
else
*(u8 *)req->buf = 0;
value = min(w_length, (u16) 1);
@@ -930,6 +1169,61 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
*((u8 *)req->buf) = value;
value = min(w_length, (u16) 1);
break;
+
+ /*
+ * USB 3.0 additions:
+ * Function driver should handle get_status request. If such cb
+ * wasn't supplied we respond with default value = 0
+ * Note: function driver should supply such cb only for the first
+ * interface of the function
+ */
+ case USB_REQ_GET_STATUS:
+ if (!gadget_is_superspeed(gadget))
+ goto unknown;
+ if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE))
+ goto unknown;
+ value = 2; /* This is the length of the get_status reply */
+ put_unaligned_le16(0, req->buf);
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+ break;
+ f = cdev->config->interface[intf];
+ if (!f)
+ break;
+ status = f->get_status ? f->get_status(f) : 0;
+ if (status < 0)
+ break;
+ put_unaligned_le16(status & 0x0000ffff, req->buf);
+ break;
+ /*
+ * Function drivers should handle SetFeature/ClearFeature
+ * (FUNCTION_SUSPEND) request. function_suspend cb should be supplied
+ * only for the first interface of the function
+ */
+ case USB_REQ_CLEAR_FEATURE:
+ case USB_REQ_SET_FEATURE:
+ if (!gadget_is_superspeed(gadget))
+ goto unknown;
+ if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE))
+ goto unknown;
+ switch (w_value) {
+ case USB_INTRF_FUNC_SUSPEND:
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+ break;
+ f = cdev->config->interface[intf];
+ if (!f)
+ break;
+ value = 0;
+ if (f->func_suspend)
+ value = f->func_suspend(f, w_index >> 8);
+ if (value < 0) {
+ ERROR(cdev,
+ "func_suspend() returned error %d\n",
+ value);
+ value = 0;
+ }
+ break;
+ }
+ break;
default:
unknown:
VDBG(cdev,
@@ -1002,6 +1296,9 @@ static void composite_disconnect(struct usb_gadget *gadget)
struct usb_composite_dev *cdev = get_gadget_data(gadget);
unsigned long flags;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ set_string_mode(0);
+#endif
/* REVISIT: should we have config and device level
* disconnect callbacks?
*/
@@ -1041,28 +1338,9 @@ composite_unbind(struct usb_gadget *gadget)
while (!list_empty(&cdev->configs)) {
struct usb_configuration *c;
-
c = list_first_entry(&cdev->configs,
struct usb_configuration, list);
- while (!list_empty(&c->functions)) {
- struct usb_function *f;
-
- f = list_first_entry(&c->functions,
- struct usb_function, list);
- list_del(&f->list);
- if (f->unbind) {
- DBG(cdev, "unbind function '%s'/%p\n",
- f->name, f);
- f->unbind(c, f);
- /* may free memory for "f" */
- }
- }
- list_del(&c->list);
- if (c->unbind) {
- DBG(cdev, "unbind config '%s'/%p\n", c->label, c);
- c->unbind(c);
- /* may free memory for "c" */
- }
+ remove_config(cdev, c);
}
if (composite->unbind)
composite->unbind(cdev);
@@ -1140,7 +1418,6 @@ static int composite_bind(struct usb_gadget *gadget)
goto fail;
cdev->desc = *composite->dev;
- cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
/* standardized runtime overrides for device ID data */
if (idVendor)
@@ -1150,6 +1427,10 @@ static int composite_bind(struct usb_gadget *gadget)
if (bcdDevice)
cdev->desc.bcdDevice = cpu_to_le16(bcdDevice);
+ printk(KERN_DEBUG "usb: %s idVendor=0x%x, idProduct=0x%x\n",
+ __func__, idVendor, idProduct);
+ printk(KERN_DEBUG "usb: %s bcdDevice=0x%x\n", __func__, bcdDevice);
+
/* string overrides */
if (iManufacturer || !cdev->desc.iManufacturer) {
if (!iManufacturer && !composite->iManufacturer &&
@@ -1165,6 +1446,9 @@ static int composite_bind(struct usb_gadget *gadget)
override_id(cdev, &cdev->desc.iManufacturer);
}
+ printk(KERN_DEBUG "usb: %s composite_manufacturer=%s\n",
+ __func__, composite_manufacturer);
+
if (iProduct || (!cdev->desc.iProduct && composite->iProduct))
cdev->product_override =
override_id(cdev, &cdev->desc.iProduct);
@@ -1247,7 +1531,11 @@ composite_resume(struct usb_gadget *gadget)
/*-------------------------------------------------------------------------*/
static struct usb_gadget_driver composite_driver = {
+#ifdef CONFIG_USB_GADGET_SUPERSPEED
+ .speed = USB_SPEED_SUPER,
+#else
.speed = USB_SPEED_HIGH,
+#endif
.unbind = composite_unbind,
diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c
index 9b7360f..212f8fc 100644
--- a/drivers/usb/gadget/epautoconf.c
+++ b/drivers/usb/gadget/epautoconf.c
@@ -142,13 +142,13 @@ ep_matches (
max = 0x7ff & le16_to_cpu(desc->wMaxPacketSize);
switch (type) {
case USB_ENDPOINT_XFER_INT:
- /* INT: limit 64 bytes full speed, 1024 high speed */
+ /* INT: limit 64 bytes full speed, 1024 high/super speed */
if (!gadget->is_dualspeed && max > 64)
return 0;
/* FALLTHROUGH */
case USB_ENDPOINT_XFER_ISOC:
- /* ISO: limit 1023 bytes full speed, 1024 high speed */
+ /* ISO: limit 1023 bytes full speed, 1024 high/super speed */
if (ep->maxpacket < max)
return 0;
if (!gadget->is_dualspeed && max > 1023)
@@ -290,6 +290,62 @@ struct usb_ep *usb_ep_autoconfig (
if (ep && ep_matches (gadget, ep, desc))
return ep;
#endif
+ } else if (gadget_is_s3c(gadget)) {
+ if (USB_ENDPOINT_XFER_INT == type) {
+ /* single buffering is enough */
+ ep = find_ep (gadget, "ep3-int");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep6-int");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep9-int");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ ep = find_ep(gadget, "ep12-int");
+ if (ep && ep_matches(gadget, ep, desc))
+ return ep;
+#endif
+ } else if (USB_ENDPOINT_XFER_BULK == type
+ && (USB_DIR_IN & desc->bEndpointAddress)) {
+ ep = find_ep (gadget, "ep2-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep5-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep8-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ ep = find_ep(gadget, "ep11-bulk");
+ if (ep && ep_matches(gadget, ep, desc))
+ return ep;
+ ep = find_ep(gadget, "ep14-bulk");
+ if (ep && ep_matches(gadget, ep, desc))
+ return ep;
+#endif
+ } else if (USB_ENDPOINT_XFER_BULK == type
+ && !(USB_DIR_IN & desc->bEndpointAddress)) {
+ ep = find_ep (gadget, "ep1-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep4-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep7-bulk");
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (ep && ep_matches(gadget, ep, desc))
+ return ep;
+ ep = find_ep(gadget, "ep10-bulk");
+ if (ep && ep_matches(gadget, ep, desc))
+ return ep;
+ ep = find_ep(gadget, "ep13-bulk");
+#endif
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ }
}
/* Second, look at endpoints until an unclaimed one looks usable */
@@ -323,4 +379,3 @@ void usb_ep_autoconfig_reset (struct usb_gadget *gadget)
#endif
epnum = 0;
}
-
diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c
index 1690c9d..dcb17d2 100644
--- a/drivers/usb/gadget/ether.c
+++ b/drivers/usb/gadget/ether.c
@@ -246,7 +246,9 @@ static int __init rndis_do_config(struct usb_configuration *c)
c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
}
- return rndis_bind_config(c, hostaddr);
+ return rndis_bind_config(c, hostaddr,
+ cpu_to_le16 (CDC_VENDOR_NUM),
+ manufacturer);
}
static struct usb_configuration rndis_config_driver = {
diff --git a/drivers/usb/gadget/exynos_ss_udc.c b/drivers/usb/gadget/exynos_ss_udc.c
new file mode 100644
index 0000000..c8001e8
--- /dev/null
+++ b/drivers/usb/gadget/exynos_ss_udc.c
@@ -0,0 +1,3047 @@
+/* linux/drivers/usb/gadget/exynos_ss_udc.c
+ *
+ * Copyright 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS SuperSpeed USB 3.0 Device Controlle driver
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/exynos_usb3_drd.h>
+
+#include <linux/platform_data/exynos_usb3_drd.h>
+
+#include <asm/byteorder.h>
+
+#include "exynos_ss_udc.h"
+
+#define SAMSUNG_MUIC_OR_BATTERY_DRIVER_NOT_READY
+#define USE_WAKE_LOCK
+
+static void exynos_ss_udc_kill_all_requests(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ int result);
+static void exynos_ss_udc_ep0_restart(struct exynos_ss_udc *udc);
+static void exynos_ss_udc_complete_setup(struct usb_ep *ep,
+ struct usb_request *req);
+static void exynos_ss_udc_complete_request(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *udc_req,
+ int result);
+static void exynos_ss_udc_start_req(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *udc_req,
+ bool continuing);
+static void exynos_ss_udc_ep_activate(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep);
+static void exynos_ss_udc_ep_deactivate(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep);
+
+
+static struct exynos_ss_udc *our_udc;
+
+static int poll_bit_set(void __iomem *ptr, u32 val, int timeout)
+{
+ u32 reg;
+
+ do {
+ reg = readl(ptr);
+ if (reg & val)
+ return 0;
+
+ udelay(1);
+ } while (timeout-- > 0);
+
+ return -ETIME;
+}
+
+static int poll_bit_clear(void __iomem *ptr, u32 val, int timeout)
+{
+ u32 reg;
+
+ do {
+ reg = readl(ptr);
+ if (!(reg & val))
+ return 0;
+
+ udelay(1);
+ } while (timeout-- > 0);
+
+ return -ETIME;
+}
+
+/**
+ * ep_from_windex - convert control wIndex value to endpoint
+ * @udc: The device state.
+ * @windex: The control request wIndex field (in host order).
+ *
+ * Convert the given wIndex into a pointer to an driver endpoint
+ * structure, or return NULL if it is not a valid endpoint.
+ */
+static struct exynos_ss_udc_ep *ep_from_windex(struct exynos_ss_udc *udc,
+ u32 windex)
+{
+ struct exynos_ss_udc_ep *ep = &udc->eps[windex & 0x7F];
+ int dir = (windex & USB_DIR_IN) ? 1 : 0;
+ int idx = windex & 0x7F;
+
+ if (windex >= 0x100)
+ return NULL;
+
+ if (idx > EXYNOS_USB3_EPS)
+ return NULL;
+
+ if (idx && ep->dir_in != dir)
+ return NULL;
+
+ return ep;
+}
+
+/**
+ * get_ep_head - return the first request on the endpoint
+ * @udc_ep: The endpoint to get request from.
+ *
+ * Get the first request on the endpoint.
+ */
+static struct exynos_ss_udc_req *get_ep_head(struct exynos_ss_udc_ep *udc_ep)
+{
+ if (list_empty(&udc_ep->queue))
+ return NULL;
+
+ return list_first_entry(&udc_ep->queue,
+ struct exynos_ss_udc_req,
+ queue);
+}
+
+/**
+ * on_list - check request is on the given endpoint
+ * @ep: The endpoint to check.
+ * @test: The request to test if it is on the endpoint.
+ */
+static bool on_list(struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *test)
+{
+ struct exynos_ss_udc_req *udc_req, *treq;
+
+ list_for_each_entry_safe(udc_req, treq, &udc_ep->queue, queue) {
+ if (udc_req == test)
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * exynos_ss_udc_map_dma - map the DMA memory being used for the request
+ * @udc: The device state.
+ * @udc_ep: The endpoint the request is on.
+ * @req: The request being processed.
+ *
+ * We've been asked to queue a request, so ensure that the memory buffer
+ * is correctly setup for DMA. If we've been passed an extant DMA address
+ * then ensure the buffer has been synced to memory. If our buffer has no
+ * DMA memory, then we map the memory and mark our request to allow us to
+ * cleanup on completion.
+ */
+static int exynos_ss_udc_map_dma(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct usb_request *req)
+{
+ enum dma_data_direction dir;
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+
+ dir = udc_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+
+ /* if the length is zero, ignore the DMA data */
+ if (udc_req->req.length == 0)
+ return 0;
+
+ if (req->dma == DMA_ADDR_INVALID) {
+ dma_addr_t dma;
+
+ dma = dma_map_single(udc->dev,
+ req->buf, req->length, dir);
+
+ if (unlikely(dma_mapping_error(udc->dev, dma)))
+ goto dma_error;
+
+ udc_req->mapped = 1;
+ req->dma = dma;
+ } else
+ dma_sync_single_for_device(udc->dev,
+ req->dma, req->length, dir);
+
+ return 0;
+
+dma_error:
+ dev_err(udc->dev, "%s: failed to map buffer %p, %d bytes\n",
+ __func__, req->buf, req->length);
+
+ return -EIO;
+}
+
+/**
+ * exynos_ss_udc_unmap_dma - unmap the DMA memory being used for the request
+ * @udc: The device state.
+ * @udc_ep: The endpoint for the request.
+ * @udc_req: The request being processed.
+ *
+ * This is the reverse of exynos_ss_udc_map_dma(), called for the completion
+ * of a request to ensure the buffer is ready for access by the caller.
+ */
+static void exynos_ss_udc_unmap_dma(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *udc_req)
+{
+ struct usb_request *req = &udc_req->req;
+ enum dma_data_direction dir;
+
+ dir = udc_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+
+ /* ignore this if we're not moving any data */
+ if (udc_req->req.length == 0)
+ return;
+
+ if (udc_req->mapped) {
+ /* we mapped this, so unmap and remove the dma */
+
+ dma_unmap_single(udc->dev, req->dma, req->length, dir);
+
+ req->dma = DMA_ADDR_INVALID;
+ udc_req->mapped = 0;
+ }
+}
+
+/**
+ * exynos_ss_udc_issue_epcmd - issue physical endpoint-specific command
+ * @udc: The device state.
+ * @epcmd: The command to issue.
+ */
+static int exynos_ss_udc_issue_epcmd(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep_command *epcmd)
+{
+ int res;
+ u32 depcmd;
+
+ /* If some of parameters are not in use, we will write it anyway
+ for simplification */
+ writel(epcmd->param0, udc->regs + EXYNOS_USB3_DEPCMDPAR0(epcmd->ep));
+ writel(epcmd->param1, udc->regs + EXYNOS_USB3_DEPCMDPAR1(epcmd->ep));
+ writel(epcmd->param2, udc->regs + EXYNOS_USB3_DEPCMDPAR2(epcmd->ep));
+
+ depcmd = epcmd->cmdtyp | epcmd->cmdflags;
+ writel(depcmd, udc->regs + EXYNOS_USB3_DEPCMD(epcmd->ep));
+
+ res = poll_bit_clear(udc->regs + EXYNOS_USB3_DEPCMD(epcmd->ep),
+ EXYNOS_USB3_DEPCMDx_CmdAct,
+ 1000);
+ return res;
+}
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+void exynos_ss_udc_cable_connect(struct exynos_ss_udc *udc, bool connect)
+{
+ static int last_connect;
+
+ if (last_connect != connect) {
+ samsung_cable_check_status(connect);
+ last_connect = connect;
+ }
+}
+#define EXYNOS_SS_UDC_CABLE_CONNECT(udc, connect) \
+ exynos_ss_udc_cable_connect(udc, connect)
+#else
+#define EXYNOS_SS_UDC_CABLE_CONNECT(udc, conect)
+#endif
+
+/**
+ * exynos_ss_udc_run_stop - start/stop the device controller operation
+ * @udc: The device state.
+ * @is_on: The action to take (1 - start, 0 - stop).
+ */
+static void exynos_ss_udc_run_stop(struct exynos_ss_udc *udc, int is_on)
+{
+ int res;
+
+ if (is_on) {
+ __orr32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_Run_Stop);
+ res = poll_bit_clear(udc->regs + EXYNOS_USB3_DSTS,
+ EXYNOS_USB3_DSTS_DevCtrlHlt,
+ 1000);
+ } else {
+ __bic32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_Run_Stop);
+ res = poll_bit_set(udc->regs + EXYNOS_USB3_DSTS,
+ EXYNOS_USB3_DSTS_DevCtrlHlt,
+ 1000);
+ }
+
+ if (res < 0)
+ dev_dbg(udc->dev, "Failed to %sconnect by software\n",
+ is_on ? "" : "dis");
+}
+
+/**
+ * exynos_ss_udc_ep_enable - configure endpoint, making it usable
+ * @ep: The endpoint being configured.
+ * @desc: The descriptor for desired behavior.
+ */
+static int exynos_ss_udc_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ unsigned long flags;
+ int dir_in;
+ int epnum;
+
+ dev_dbg(udc->dev,
+ "%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n",
+ __func__, ep->name, desc->bEndpointAddress, desc->bmAttributes,
+ desc->wMaxPacketSize, desc->bInterval);
+
+ /* not to be called for EP0 */
+ WARN_ON(udc_ep->epnum == 0);
+
+ if (udc_ep->enabled)
+ return -EINVAL;
+
+ epnum = (desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ if (epnum != udc_ep->epnum) {
+ dev_err(udc->dev, "%s: EP number mismatch!\n", __func__);
+ return -EINVAL;
+ }
+
+ dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0;
+ if (dir_in != udc_ep->dir_in) {
+ dev_err(udc->dev, "%s: EP direction mismatch!\n", __func__);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&udc_ep->lock, flags);
+
+ /* update the endpoint state */
+ udc_ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+ udc_ep->type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+
+ switch (udc_ep->type) {
+ case USB_ENDPOINT_XFER_ISOC:
+ dev_err(udc->dev, "no current ISOC support\n");
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+ return -EINVAL;
+
+ case USB_ENDPOINT_XFER_BULK:
+ dev_dbg(udc->dev, "Bulk endpoint\n");
+ break;
+
+ case USB_ENDPOINT_XFER_INT:
+ dev_dbg(udc->dev, "Interrupt endpoint\n");
+ break;
+
+ case USB_ENDPOINT_XFER_CONTROL:
+ dev_dbg(udc->dev, "Control endpoint\n");
+ break;
+ }
+
+ exynos_ss_udc_ep_activate(udc, udc_ep);
+ udc_ep->enabled = 1;
+ udc_ep->halted = udc_ep->wedged = 0;
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_ep_disable - endpoint is no longer usable
+ * @ep: The endpoint being unconfigured.
+ */
+static int exynos_ss_udc_ep_disable(struct usb_ep *ep)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ unsigned long flags;
+
+ dev_dbg(udc->dev, "%s: ep%d%s\n", __func__,
+ udc_ep->epnum, udc_ep->dir_in ? "in" : "out");
+
+ spin_lock_irqsave(&udc_ep->lock, flags);
+ exynos_ss_udc_ep_deactivate(udc, udc_ep);
+ udc_ep->enabled = 0;
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+
+ /* terminate all requests with shutdown */
+ exynos_ss_udc_kill_all_requests(udc, udc_ep, -ESHUTDOWN);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_ep_alloc_request - allocate a request object
+ * @ep: The endpoint to be used with the request.
+ * @flags: Allocation flags.
+ *
+ * Allocate a new USB request structure appropriate for the specified endpoint.
+ */
+static struct usb_request *exynos_ss_udc_ep_alloc_request(struct usb_ep *ep,
+ gfp_t flags)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ struct exynos_ss_udc_req *udc_req;
+
+ dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum);
+
+ udc_req = kzalloc(sizeof(struct exynos_ss_udc_req), flags);
+ if (!udc_req)
+ return NULL;
+
+ INIT_LIST_HEAD(&udc_req->queue);
+
+ udc_req->req.dma = DMA_ADDR_INVALID;
+ return &udc_req->req;
+}
+
+/**
+ * exynos_ss_udc_ep_free_request - free a request object
+ * @ep: The endpoint associated with the request.
+ * @req: The request being freed.
+ *
+ * Reverse the effect of exynos_ss_udc_ep_alloc_request().
+ */
+static void exynos_ss_udc_ep_free_request(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+
+ dev_dbg(udc->dev, "%s: ep%d, req %p\n", __func__, udc_ep->epnum, req);
+
+ kfree(udc_req);
+}
+
+/**
+ * exynos_ss_udc_ep_queue - queue (submit) an I/O request to an endpoint
+ * @ep: The endpoint associated with the request.
+ * @req: The request being submitted.
+ * @gfp_flags: Not used.
+ *
+ * Queue a request and start it if the first.
+ */
+static int exynos_ss_udc_ep_queue(struct usb_ep *ep,
+ struct usb_request *req,
+ gfp_t gfp_flags)
+{
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ unsigned long irqflags;
+ bool first;
+ int ret;
+
+ dev_dbg(udc->dev, "%s: ep%d%s (%p): %d@%p, noi=%d, zero=%d, snok=%d\n",
+ __func__, udc_ep->epnum,
+ udc_ep->dir_in ? "in" : "out", req,
+ req->length, req->buf, req->no_interrupt,
+ req->zero, req->short_not_ok);
+
+ /* initialise status of the request */
+ INIT_LIST_HEAD(&udc_req->queue);
+
+ req->actual = 0;
+ req->status = -EINPROGRESS;
+
+ /* Sync the buffers as necessary */
+ if (req->buf == udc->ctrl_buff)
+ req->dma = udc->ctrl_buff_dma;
+ else if (req->buf == udc->ep0_buff)
+ req->dma = udc->ep0_buff_dma;
+ else {
+ ret = exynos_ss_udc_map_dma(udc, udc_ep, req);
+ if (ret)
+ return ret;
+ }
+
+ spin_lock_irqsave(&udc_ep->lock, irqflags);
+
+ first = list_empty(&udc_ep->queue);
+ list_add_tail(&udc_req->queue, &udc_ep->queue);
+
+ if (first && !udc_ep->not_ready)
+ exynos_ss_udc_start_req(udc, udc_ep, udc_req, false);
+
+ spin_unlock_irqrestore(&udc_ep->lock, irqflags);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_ep_dequeue - dequeue an I/O request from an endpoint
+ * @ep: The endpoint associated with the request.
+ * @req: The request being canceled.
+ *
+ * Dequeue a request and call its completion routine.
+ */
+static int exynos_ss_udc_ep_dequeue(struct usb_ep *ep, struct usb_request *req)
+{
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ unsigned long flags;
+
+ dev_dbg(udc->dev, "%s: ep%d%s (%p)\n", __func__,
+ udc_ep->epnum, udc_ep->dir_in ? "in" : "out", req);
+
+ spin_lock_irqsave(&udc_ep->lock, flags);
+
+ if (!on_list(udc_ep, udc_req)) {
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+ return -EINVAL;
+ }
+
+ exynos_ss_udc_complete_request(udc, udc_ep, udc_req, -ECONNRESET);
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_ep_sethalt - set/clear the endpoint halt feature
+ * @ep: The endpoint being stalled/reset.
+ * @value: The action to take (1 - set stall, 0 - clear stall).
+ */
+static int exynos_ss_udc_ep_sethalt(struct usb_ep *ep, int value)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ struct exynos_ss_udc_ep_command epcmd = {{0}, };
+ int epnum = udc_ep->epnum;
+ int index = get_phys_epnum(udc_ep);
+ unsigned long irqflags;
+ int res;
+
+ dev_dbg(udc->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value);
+
+ spin_lock_irqsave(&udc_ep->lock, irqflags);
+
+ if (value && epnum != 0 && udc_ep->dir_in && udc_ep->req) {
+ dev_dbg(udc->dev, "%s: transfer in progress!\n", __func__);
+ spin_unlock_irqrestore(&udc_ep->lock, irqflags);
+ return -EAGAIN;
+ }
+
+ if (epnum == 0)
+ /* Only OUT direction can be stalled */
+ epcmd.ep = 0;
+ else
+ epcmd.ep = index;
+
+ if (value)
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL;
+ else
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCSTALL;
+
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0) {
+ dev_err(udc->dev, "Failed to set/clear stall\n");
+ spin_unlock_irqrestore(&udc_ep->lock, irqflags);
+ return res;
+ }
+
+ if (epnum == 0) {
+ if (value)
+ udc->ep0_state = EP0_STALL;
+ } else {
+ udc_ep->halted = !!value;
+
+ if (!value)
+ udc_ep->wedged = 0;
+ }
+
+ spin_unlock_irqrestore(&udc_ep->lock, irqflags);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_ep_setwedge - set the halt feature and ignore clear requests
+ * @ep: The endpoint being wedged.
+ */
+static int exynos_ss_udc_ep_setwedge(struct usb_ep *ep)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&udc_ep->lock, irqflags);
+ udc_ep->wedged = 1;
+ spin_unlock_irqrestore(&udc_ep->lock, irqflags);
+
+ return exynos_ss_udc_ep_sethalt(ep, 1);
+}
+
+static struct usb_ep_ops exynos_ss_udc_ep_ops = {
+ .enable = exynos_ss_udc_ep_enable,
+ .disable = exynos_ss_udc_ep_disable,
+ .alloc_request = exynos_ss_udc_ep_alloc_request,
+ .free_request = exynos_ss_udc_ep_free_request,
+ .queue = exynos_ss_udc_ep_queue,
+ .dequeue = exynos_ss_udc_ep_dequeue,
+ .set_halt = exynos_ss_udc_ep_sethalt,
+ .set_wedge = exynos_ss_udc_ep_setwedge,
+};
+
+/**
+ * exynos_ss_udc_start_req - start a USB request from an endpoint's queue
+ * @udc: The device state.
+ * @udc_ep: The endpoint to process a request for.
+ * @udc_req: The request being started.
+ * @continuing: True if we are doing more for the current request.
+ *
+ * Start the given request running by setting the TRB appropriately,
+ * and issuing Start Transfer endpoint command.
+ */
+static void exynos_ss_udc_start_req(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *udc_req,
+ bool continuing)
+{
+ struct exynos_ss_udc_ep_command epcmd = {{0}, };
+ struct usb_request *ureq = &udc_req->req;
+ enum trb_control trb_type = NORMAL;
+ int epnum = udc_ep->epnum;
+ int xfer_length;
+ int res;
+
+ dev_dbg(udc->dev, "%s: ep%d%s, req %p\n", __func__, epnum,
+ udc_ep->dir_in ? "in" : "out", ureq);
+
+ /* If endpoint is stalled, we will restart request later */
+ if (udc_ep->halted) {
+ dev_dbg(udc->dev, "%s: ep%d is stalled\n", __func__, epnum);
+ return;
+ }
+
+ udc_ep->req = udc_req;
+
+ /* Get type of TRB */
+ if (epnum == 0 && !continuing)
+ switch (udc->ep0_state) {
+ case EP0_SETUP_PHASE:
+ trb_type = CONTROL_SETUP;
+ break;
+
+ case EP0_DATA_PHASE:
+ trb_type = CONTROL_DATA;
+ break;
+
+ case EP0_STATUS_PHASE:
+ if (udc->ep0_three_stage)
+ trb_type = CONTROL_STATUS_3;
+ else
+ trb_type = CONTROL_STATUS_2;
+ break;
+ default:
+ dev_warn(udc->dev, "%s: Erroneous EP0 state (%d)",
+ __func__, udc->ep0_state);
+ return;
+ break;
+ }
+ else
+ trb_type = NORMAL;
+
+ /* Get transfer length */
+ if (udc_ep->dir_in)
+ xfer_length = ureq->length;
+ else
+ xfer_length = (ureq->length + udc_ep->ep.maxpacket - 1) &
+ ~(udc_ep->ep.maxpacket - 1);
+
+ /* Fill TRB */
+ udc_ep->trb->buff_ptr_low = (u32) ureq->dma;
+ udc_ep->trb->buff_ptr_high = 0;
+ udc_ep->trb->param1 = EXYNOS_USB3_TRB_BUFSIZ(xfer_length);
+ udc_ep->trb->param2 = EXYNOS_USB3_TRB_IOC |
+ EXYNOS_USB3_TRB_LST |
+ EXYNOS_USB3_TRB_HWO |
+ EXYNOS_USB3_TRB_TRBCTL(trb_type);
+
+ /* Start Transfer */
+ epcmd.ep = get_phys_epnum(udc_ep);
+ epcmd.param0 = 0;
+ epcmd.param1 = udc_ep->trb_dma;
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTRTXFER;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to start transfer\n");
+
+ udc_ep->tri = (readl(udc->regs + EXYNOS_USB3_DEPCMD(epcmd.ep)) >>
+ EXYNOS_USB3_DEPCMDx_EventParam_SHIFT) &
+ EXYNOS_USB3_DEPCMDx_XferRscIdx_LIMIT;
+}
+
+/**
+ * exynos_ss_udc_enqueue_status - start a request for EP0 status stage
+ * @udc: The device state.
+ */
+static void exynos_ss_udc_enqueue_status(struct exynos_ss_udc *udc)
+{
+ struct usb_request *req = udc->ctrl_req;
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+ int ret;
+
+ dev_dbg(udc->dev, "%s: queueing status request\n", __func__);
+
+ req->zero = 0;
+ req->length = 0;
+ req->buf = udc->ctrl_buff;
+ req->complete = NULL;
+
+ if (!list_empty(&udc_req->queue)) {
+ dev_info(udc->dev, "%s already queued???\n", __func__);
+ return;
+ }
+
+ ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(udc->dev, "%s: failed queue (%d)\n", __func__, ret);
+}
+
+/**
+ * exynos_ss_udc_enqueue_data - start a request for EP0 data stage
+ * @udc: The device state.
+ * @buff: The buffer used for data.
+ * @length: The length of data.
+ * @complete: The function called when request completes.
+ */
+static int exynos_ss_udc_enqueue_data(struct exynos_ss_udc *udc,
+ void *buff, int length,
+ void (*complete) (struct usb_ep *ep,
+ struct usb_request *req))
+{
+ struct usb_request *req = udc->ctrl_req;
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+ int ret;
+
+ dev_dbg(udc->dev, "%s: queueing data request\n", __func__);
+
+ req->zero = 0;
+ req->length = length;
+
+ if (buff == NULL)
+ req->buf = udc->ep0_buff;
+ else
+ req->buf = buff;
+
+ req->complete = complete;
+
+ if (!list_empty(&udc_req->queue)) {
+ dev_info(udc->dev, "%s: already queued???\n", __func__);
+ return -EAGAIN;
+ }
+
+ ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC);
+ if (ret < 0) {
+ dev_err(udc->dev, "%s: failed to enqueue data request (%d)\n",
+ __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_enqueue_setup - start a request for EP0 setup stage
+ * @udc: The device state.
+ *
+ * Enqueue a request on EP0 if necessary to receive any SETUP packets
+ * from the host.
+ */
+static void exynos_ss_udc_enqueue_setup(struct exynos_ss_udc *udc)
+{
+ struct usb_request *req = udc->ctrl_req;
+ struct exynos_ss_udc_req *udc_req = our_req(req);
+ int ret;
+
+ dev_dbg(udc->dev, "%s: queueing setup request\n", __func__);
+
+ req->zero = 0;
+ req->length = EXYNOS_USB3_CTRL_BUFF_SIZE;
+ req->buf = udc->ctrl_buff;
+ req->complete = exynos_ss_udc_complete_setup;
+
+ if (!list_empty(&udc_req->queue)) {
+ dev_dbg(udc->dev, "%s already queued???\n", __func__);
+ return;
+ }
+
+ udc->eps[0].dir_in = 0;
+
+ ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(udc->dev, "%s: failed queue (%d)\n", __func__, ret);
+}
+
+/**
+ * exynos_ss_udc_process_set_config - process request SET_CONFIGURATION
+ * @udc: The device state.
+ * @ctrl: The USB control request.
+ */
+static int exynos_ss_udc_process_set_config(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ int ret;
+ u16 config = le16_to_cpu(ctrl->wValue);
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+
+ udc->ep0_state = EP0_STATUS_PHASE;
+
+ switch (udc->state) {
+ case USB_STATE_ADDRESS:
+ ret = udc->driver->setup(&udc->gadget, ctrl);
+ if (!ret || ret == USB_GADGET_DELAYED_STATUS) {
+ ret = 1;
+ if (config)
+ udc->state = USB_STATE_CONFIGURED;
+ }
+ break;
+
+ case USB_STATE_CONFIGURED:
+ ret = udc->driver->setup(&udc->gadget, ctrl);
+ if (!ret || ret == USB_GADGET_DELAYED_STATUS) {
+ ret = 1;
+ if (!config)
+ udc->state = USB_STATE_ADDRESS;
+ }
+ break;
+
+ case USB_STATE_DEFAULT:
+ /* FALLTHROUGH */
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/**
+ * exynos_ss_udc_complete_set_sel - completion of SET_SEL request data stage
+ * @ep: The endpoint the request was on.
+ * @req: The request completed.
+ */
+static void exynos_ss_udc_complete_set_sel(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+ u8 *sel = req->buf;
+ u32 param;
+ u32 dgcmd;
+ int res;
+
+ /* Our device is U1/U2 enabled, so we will use U2PEL */
+ param = sel[5] << 8 | sel[4];
+ /* Documentation says "If the value is greater than 125us, then
+ * software must program a value of zero into this register */
+ if (param > 125)
+ param = 0;
+
+ dev_dbg(udc->dev, "%s: dgcmd_param = 0x%08x\n", __func__, param);
+
+ dgcmd = EXYNOS_USB3_DGCMD_CmdAct |
+ EXYNOS_USB3_DGCMD_CmdTyp_SetPerParams;
+
+ writel(param, udc->regs + EXYNOS_USB3_DGCMDPAR);
+ writel(dgcmd, udc->regs + EXYNOS_USB3_DGCMD);
+ res = poll_bit_clear(udc->regs + EXYNOS_USB3_DGCMD,
+ EXYNOS_USB3_DGCMD_CmdAct,
+ 1000);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to set periodic parameters\n");
+}
+
+/**
+ * exynos_ss_udc_process_set_sel - process request SET_SEL
+ * @udc: The device state.
+ */
+static int exynos_ss_udc_process_set_sel(struct exynos_ss_udc *udc)
+{
+ int ret;
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+
+ if (udc->state != USB_STATE_ADDRESS &&
+ udc->state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+
+ ret = exynos_ss_udc_enqueue_data(udc, udc->ep0_buff,
+ EXYNOS_USB3_EP0_BUFF_SIZE,
+ exynos_ss_udc_complete_set_sel);
+ if (ret < 0) {
+ dev_err(udc->dev, "%s: failed to become ready for SEL data\n",
+ __func__);
+ return ret;
+ }
+
+ return 1;
+}
+
+/**
+ * exynos_ss_udc_process_set_isoch_delay - process request SET_ISOCH_DELAY
+ * @udc: The device state.
+ * @ctrl: The USB control request.
+ */
+static int exynos_ss_udc_process_set_isoch_delay(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ u16 isoch_delay;
+ int ret = 1;
+
+ if (ctrl->wIndex || ctrl->wLength) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ switch (udc->state) {
+ case USB_STATE_DEFAULT:
+ /* FALLTHROUGH */
+ case USB_STATE_ADDRESS:
+ /* FALLTHROUGH */
+ case USB_STATE_CONFIGURED:
+ isoch_delay = le16_to_cpu(ctrl->wValue);
+ /* REVISIT don't know what to do with this value */
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+err:
+ return ret;
+}
+
+/**
+ * exynos_ss_udc_set_test_mode - set TEST_MODE feature
+ * @udc: The device state.
+ * @wIndex: The request wIndex field.
+ */
+static int exynos_ss_udc_set_test_mode(struct exynos_ss_udc *udc,
+ u16 wIndex)
+{
+ u8 selector = wIndex >> 8;
+ char *mode;
+ u32 reg;
+ int ret = 0;
+
+ switch (selector) {
+ case TEST_J:
+ mode = "TEST J";
+ break;
+ case TEST_K:
+ mode = "TEST K";
+ break;
+ case TEST_SE0_NAK:
+ mode = "TEST SE0 NAK";
+ break;
+ case TEST_PACKET:
+ mode = "TEST PACKET";
+ break;
+ case TEST_FORCE_EN:
+ mode = "TEST FORCE EN";
+ break;
+ default:
+ mode = "unknown";
+ ret = -EINVAL;
+ break;
+ }
+
+ dev_info(udc->dev, "Test mode selector is %s\n", mode);
+
+ if (ret == 0) {
+ reg = readl(udc->regs + EXYNOS_USB3_DCTL) &
+ ~EXYNOS_USB3_DCTL_TstCtl_MASK;
+
+ reg |= EXYNOS_USB3_DCTL_TstCtl(selector);
+
+ writel(reg, udc->regs + EXYNOS_USB3_DCTL);
+ }
+
+ return ret;
+}
+
+/**
+ * exynos_ss_udc_process_clr_feature - process request CLEAR_FEATURE
+ * @udc: The device state.
+ * @ctrl: The USB control request.
+ */
+static int exynos_ss_udc_process_clr_feature(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct exynos_ss_udc_ep *udc_ep;
+ struct exynos_ss_udc_req *udc_req;
+ int ret;
+ bool restart;
+ u16 wValue;
+ u16 wIndex;
+ u8 recip;
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+
+ if (udc->state != USB_STATE_ADDRESS &&
+ udc->state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+
+ wValue = le16_to_cpu(ctrl->wValue);
+ wIndex = le16_to_cpu(ctrl->wIndex);
+ recip = ctrl->bRequestType & USB_RECIP_MASK;
+
+ switch (recip) {
+ case USB_RECIP_DEVICE:
+ switch (wValue) {
+ case USB_DEVICE_U1_ENABLE:
+ if (udc->gadget.speed != USB_SPEED_SUPER ||
+ udc->state == USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ __bic32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_InitU1Ena);
+ break;
+
+ case USB_DEVICE_U2_ENABLE:
+ if (udc->gadget.speed != USB_SPEED_SUPER ||
+ udc->state == USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ __bic32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_InitU2Ena);
+ break;
+
+ default:
+ return -ENOENT;
+ }
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ udc_ep = ep_from_windex(udc, wIndex);
+ if (!udc_ep) {
+ dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n",
+ __func__, wIndex);
+ return -ENOENT;
+ }
+
+ if (udc->state == USB_STATE_ADDRESS &&
+ udc_ep->epnum != 0)
+ return -EINVAL;
+
+ switch (wValue) {
+ case USB_ENDPOINT_HALT:
+ if (!udc_ep->wedged) {
+ ret = exynos_ss_udc_ep_sethalt(&udc_ep->ep, 0);
+ if (ret < 0)
+ return ret;
+
+ /* If we have pending request, then start it */
+ restart = !list_empty(&udc_ep->queue);
+ if (restart) {
+ udc_req = get_ep_head(udc_ep);
+ exynos_ss_udc_start_req(udc, udc_ep,
+ udc_req, false);
+ }
+ }
+ break;
+
+ default:
+ return -ENOENT;
+ }
+
+ break;
+
+ default:
+ return -ENOENT;
+ }
+
+ return 1;
+}
+
+/**
+ * exynos_ss_udc_process_set_feature - process request SET_FEATURE
+ * @udc: The device state.
+ * @ctrl: The USB control request.
+ */
+static int exynos_ss_udc_process_set_feature(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct exynos_ss_udc_ep *udc_ep;
+ int ret;
+ u16 wValue;
+ u16 wIndex;
+ u8 recip;
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+
+ if (udc->state != USB_STATE_ADDRESS &&
+ udc->state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+
+ wValue = le16_to_cpu(ctrl->wValue);
+ wIndex = le16_to_cpu(ctrl->wIndex);
+ recip = ctrl->bRequestType & USB_RECIP_MASK;
+
+ switch (recip) {
+ case USB_RECIP_DEVICE:
+ switch (wValue) {
+ case USB_DEVICE_TEST_MODE:
+ if (wIndex & 0xff)
+ return -EINVAL;
+
+ ret = exynos_ss_udc_set_test_mode(udc, wIndex);
+ if (ret < 0)
+ return ret;
+ break;
+ case USB_DEVICE_U1_ENABLE:
+ if (udc->gadget.speed != USB_SPEED_SUPER ||
+ udc->state == USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ /*
+ * Enable U1 entry only for DWC3 revisions > 1.85a,
+ * since earlier revisions have a bug
+ */
+ if (udc->release > 0x185a)
+ __orr32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_InitU1Ena);
+ break;
+
+ case USB_DEVICE_U2_ENABLE:
+ if (udc->gadget.speed != USB_SPEED_SUPER ||
+ udc->state == USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ /*
+ * Enable U2 entry only for DWC3 revisions > 1.85a,
+ * since earlier revisions have a bug
+ */
+ if (udc->release > 0x185a)
+ __orr32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_InitU2Ena);
+ break;
+
+ default:
+ return -ENOENT;
+ }
+ break;
+
+ case USB_RECIP_INTERFACE:
+ switch (wValue) {
+ case USB_INTRF_FUNC_SUSPEND:
+ if (udc->gadget.speed != USB_SPEED_SUPER ||
+ udc->state == USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ /*
+ * Currently, there is no functions supporting
+ * FUNCTION_SUSPEND feature. Moreover, if a function
+ * doesn't support the feature (true for all), composite
+ * driver returns error on 'setup' call. This causes
+ * Command Verifier test to fail. To fix it, will use
+ * dummy handler instead.
+ */
+
+ break;
+
+ default:
+ return -ENOENT;
+ }
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ udc_ep = ep_from_windex(udc, wIndex);
+ if (!udc_ep) {
+ dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n",
+ __func__, wIndex);
+ return -ENOENT;
+ }
+
+ if (udc->state == USB_STATE_ADDRESS &&
+ udc_ep->epnum != 0)
+ return -EINVAL;
+
+ switch (wValue) {
+ case USB_ENDPOINT_HALT:
+ ret = exynos_ss_udc_ep_sethalt(&udc_ep->ep, 1);
+ if (ret < 0)
+ return ret;
+ break;
+
+ default:
+ return -ENOENT;
+ }
+
+ break;
+
+ default:
+ return -ENOENT;
+ }
+
+ return 1;
+}
+
+/**
+ * exynos_ss_udc_process_get_status - process request GET_STATUS
+ * @udc: The device state.
+ * @ctrl: The USB control request.
+ */
+static int exynos_ss_udc_process_get_status(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct exynos_ss_udc_ep *udc_ep0 = &udc->eps[0];
+ struct exynos_ss_udc_ep *udc_ep;
+ u8 *reply = udc->ep0_buff;
+ u32 reg;
+ int ret;
+
+ dev_dbg(udc->dev, "%s: USB_REQ_GET_STATUS\n", __func__);
+
+ if (udc->state != USB_STATE_ADDRESS &&
+ udc->state != USB_STATE_CONFIGURED)
+ return -EINVAL;
+
+ if (!udc_ep0->dir_in) {
+ dev_warn(udc->dev, "%s: direction out?\n", __func__);
+ return -EINVAL;
+ }
+
+ if (le16_to_cpu(ctrl->wLength) != 2)
+ return -EINVAL;
+
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ *reply = 1;
+ if (udc->gadget.speed == USB_SPEED_SUPER) {
+ reg = readl(udc->regs + EXYNOS_USB3_DCTL);
+
+ if (reg & EXYNOS_USB3_DCTL_InitU1Ena)
+ *reply |= 1 << 2;
+
+ if (reg & EXYNOS_USB3_DCTL_InitU2Ena)
+ *reply |= 1 << 3;
+ }
+ *(reply + 1) = 0;
+ break;
+
+ case USB_RECIP_INTERFACE:
+ if (udc->state == USB_STATE_ADDRESS)
+ return -EINVAL;
+
+ /* currently, the data result should be zero */
+ *reply = 0;
+ *(reply + 1) = 0;
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ udc_ep = ep_from_windex(udc, le16_to_cpu(ctrl->wIndex));
+ if (!udc_ep)
+ return -ENOENT;
+
+ if (udc->state == USB_STATE_ADDRESS &&
+ udc_ep->epnum != 0)
+ return -EINVAL;
+
+ *reply = udc_ep->halted ? 1 : 0;
+ *(reply + 1) = 0;
+ break;
+
+ default:
+ return 0;
+ }
+
+ ret = exynos_ss_udc_enqueue_data(udc, reply, 2, NULL);
+ if (ret) {
+ dev_err(udc->dev, "%s: failed to send reply\n", __func__);
+ return ret;
+ }
+
+ return 1;
+}
+
+/**
+ * exynos_ss_udc_process_set_address - process request SET_CONFIGURATION
+ * @udc: The device state.
+ * @ctrl: The USB control request.
+ */
+static int exynos_ss_udc_process_set_address(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ int ret = 1;
+ u16 address = le16_to_cpu(ctrl->wValue);
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+
+ switch (udc->state) {
+ case USB_STATE_DEFAULT:
+ if (address)
+ udc->state = USB_STATE_ADDRESS;
+ break;
+
+ case USB_STATE_ADDRESS:
+ if (!address)
+ udc->state = USB_STATE_DEFAULT;
+ break;
+
+ case USB_STATE_CONFIGURED:
+ /* FALLTHROUGH */
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ret == 1) {
+ __bic32(udc->regs + EXYNOS_USB3_DCFG,
+ EXYNOS_USB3_DCFG_DevAddr_MASK);
+ __orr32(udc->regs + EXYNOS_USB3_DCFG,
+ EXYNOS_USB3_DCFG_DevAddr(address));
+ dev_dbg(udc->dev, "new address %d\n", address);
+ }
+
+ return ret;
+}
+
+/**
+ * exynos_ss_udc_process_control - process a control request
+ * @udc: The device state.
+ * @ctrl: The control request received.
+ *
+ * The controller has received the SETUP phase of a control request, and
+ * needs to work out what to do next (and whether to pass it on to the
+ * gadget driver).
+ */
+static void exynos_ss_udc_process_control(struct exynos_ss_udc *udc,
+ struct usb_ctrlrequest *ctrl)
+{
+ struct exynos_ss_udc_ep *ep0 = &udc->eps[0];
+ int ret = 0;
+
+ dev_dbg(udc->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n",
+ ctrl->bRequest, ctrl->bRequestType,
+ ctrl->wValue, ctrl->wLength);
+
+ /* record the direction of the request, for later use when enquing
+ * packets onto EP0. */
+
+ ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0;
+ dev_dbg(udc->dev, "ctrl: dir_in=%d\n", ep0->dir_in);
+
+ /* if we've no data with this request, then the last part of the
+ * transaction is going to implicitly be IN. */
+ if (ctrl->wLength == 0) {
+ ep0->dir_in = 1;
+ udc->ep0_three_stage = 0;
+ udc->ep0_state = EP0_WAIT_NRDY;
+ } else
+ udc->ep0_three_stage = 1;
+
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) {
+ switch (ctrl->bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ ret = exynos_ss_udc_process_set_address(udc, ctrl);
+ break;
+
+ case USB_REQ_GET_STATUS:
+ ret = exynos_ss_udc_process_get_status(udc, ctrl);
+ break;
+
+ case USB_REQ_CLEAR_FEATURE:
+ ret = exynos_ss_udc_process_clr_feature(udc, ctrl);
+ break;
+
+ case USB_REQ_SET_FEATURE:
+ ret = exynos_ss_udc_process_set_feature(udc, ctrl);
+ break;
+
+ case USB_REQ_SET_SEL:
+ ret = exynos_ss_udc_process_set_sel(udc);
+ break;
+ case USB_REQ_SET_ISOCH_DELAY:
+ ret = exynos_ss_udc_process_set_isoch_delay(udc, ctrl);
+ break;
+ case USB_REQ_SET_CONFIGURATION:
+ ret = exynos_ss_udc_process_set_config(udc, ctrl);
+ break;
+ }
+ }
+
+ /* as a fallback, try delivering it to the driver to deal with */
+
+ if (ret == 0 && udc->driver) {
+ if (udc->ep0_three_stage == 0)
+ udc->ep0_state = EP0_STATUS_PHASE;
+
+ ret = udc->driver->setup(&udc->gadget, ctrl);
+ if (ret < 0)
+ dev_dbg(udc->dev, "driver->setup() ret %d\n", ret);
+ }
+
+ /* the request is either unhandlable, or is not formatted correctly
+ * so respond with a STALL for the status stage to indicate failure.
+ */
+
+ if (ret < 0) {
+ dev_dbg(udc->dev, "ep0 stall (dir=%d)\n", ep0->dir_in);
+ exynos_ss_udc_ep0_restart(udc);
+ }
+}
+
+/**
+ * exynos_ss_udc_complete_setup - completion of a setup transfer
+ * @ep: The endpoint the request was on.
+ * @req: The request completed.
+ *
+ * Called on completion of any requests the driver itself submitted for
+ * EP0 setup packets.
+ */
+static void exynos_ss_udc_complete_setup(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct exynos_ss_udc_ep *udc_ep = our_ep(ep);
+ struct exynos_ss_udc *udc = udc_ep->parent;
+
+ if (req->status < 0) {
+ dev_dbg(udc->dev, "%s: failed %d\n", __func__, req->status);
+ return;
+ }
+
+ exynos_ss_udc_process_control(udc, req->buf);
+}
+
+/**
+ * exynos_ss_udc_kill_all_requests - remove all requests from the endpoint's queue
+ * @udc: The device state.
+ * @ep: The endpoint the requests may be on.
+ * @result: The result code to use.
+ *
+ * Go through the requests on the given endpoint and mark them
+ * completed with the given result code.
+ */
+static void exynos_ss_udc_kill_all_requests(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ int result)
+{
+ struct exynos_ss_udc_req *udc_req, *treq;
+ unsigned long flags;
+
+ dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum);
+
+ spin_lock_irqsave(&udc_ep->lock, flags);
+
+ list_for_each_entry_safe(udc_req, treq, &udc_ep->queue, queue) {
+
+ exynos_ss_udc_complete_request(udc, udc_ep, udc_req, result);
+ }
+
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+}
+
+/**
+ * exynos_ss_udc_complete_request - complete a request given to us
+ * @udc: The device state.
+ * @udc_ep: The endpoint the request was on.
+ * @udc_req: The request being completed.
+ * @result: The result code (0 => Ok, otherwise errno)
+ *
+ * The given request has finished, so call the necessary completion
+ * if it has one and then look to see if we can start a new request
+ * on the endpoint.
+ *
+ * Note, expects the ep to already be locked as appropriate.
+ */
+static void exynos_ss_udc_complete_request(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *udc_req,
+ int result)
+{
+ bool restart;
+
+ if (!udc_req) {
+ dev_dbg(udc->dev, "%s: nothing to complete\n", __func__);
+ return;
+ }
+
+ dev_dbg(udc->dev, "complete: ep %p %s, req %p, %d => %p\n",
+ udc_ep, udc_ep->ep.name, udc_req,
+ result, udc_req->req.complete);
+
+ /* only replace the status if we've not already set an error
+ * from a previous transaction */
+
+ if (udc_req->req.status == -EINPROGRESS)
+ udc_req->req.status = result;
+
+ udc_ep->req = NULL;
+ udc_ep->tri = 0;
+ list_del_init(&udc_req->queue);
+
+ if (udc_req->req.buf != udc->ctrl_buff &&
+ udc_req->req.buf != udc->ep0_buff)
+ exynos_ss_udc_unmap_dma(udc, udc_ep, udc_req);
+
+ /* call the complete request with the locks off, just in case the
+ * request tries to queue more work for this endpoint. */
+
+ if (udc_req->req.complete) {
+ spin_unlock(&udc_ep->lock);
+ udc_req->req.complete(&udc_ep->ep, &udc_req->req);
+ spin_lock(&udc_ep->lock);
+ }
+
+ /* Look to see if there is anything else to do. Note, the completion
+ * of the previous request may have caused a new request to be started
+ * so be careful when doing this. */
+
+ if (!udc_ep->req && result >= 0) {
+ restart = !list_empty(&udc_ep->queue);
+ if (restart) {
+ udc_req = get_ep_head(udc_ep);
+ exynos_ss_udc_start_req(udc, udc_ep, udc_req, false);
+ }
+ }
+}
+
+/**
+ * exynos_ss_udc_complete_request_lock - complete a request given to us (locked)
+ * @udc: The device state.
+ * @udc_ep: The endpoint the request was on.
+ * @udc_req: The request being completed.
+ * @result: The result code (0 => Ok, otherwise errno).
+ *
+ * See exynos_ss_udc_complete_request(), but called with the endpoint's
+ * lock held.
+ */
+static void exynos_ss_udc_complete_request_lock(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ struct exynos_ss_udc_req *udc_req,
+ int result)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&udc_ep->lock, flags);
+ exynos_ss_udc_complete_request(udc, udc_ep, udc_req, result);
+ spin_unlock_irqrestore(&udc_ep->lock, flags);
+}
+
+/**
+ * exynos_ss_udc_ep0_restart - stall and restart EP0
+ * @udc: The device state.
+ *
+ * Stall EP0 and restart control transfer state machine.
+ */
+static void exynos_ss_udc_ep0_restart(struct exynos_ss_udc *udc)
+{
+ struct exynos_ss_udc_ep *ep0 = &udc->eps[0];
+
+ exynos_ss_udc_ep_sethalt(&ep0->ep, 1);
+ exynos_ss_udc_kill_all_requests(udc, ep0, -ECONNRESET);
+ udc->ep0_state = EP0_SETUP_PHASE;
+ exynos_ss_udc_enqueue_setup(udc);
+}
+
+/**
+ * exynos_ss_udc_ep_cmd_complete - process event EP Command Complete
+ * @udc: The device state.
+ * @udc_ep: The endpoint this event is for.
+ * @event: The event being handled.
+ */
+static void exynos_ss_udc_ep_cmd_complete(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ u32 event)
+{
+ struct exynos_ss_udc_ep_command *epcmd, *tepcmd;
+ struct exynos_ss_udc_req *udc_req;
+ int epnum;
+ int res;
+ bool restart;
+
+ dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, udc_ep->epnum,
+ udc_ep->dir_in ? "in" : "out");
+
+ /* We use IOC _only_ for End Transfer command currently */
+
+ udc_ep->not_ready = 0;
+
+ /* Issue all pending commands for endpoint */
+ list_for_each_entry_safe(epcmd, tepcmd,
+ &udc_ep->cmd_queue, queue) {
+
+ dev_dbg(udc->dev, "Pending command %02xh for ep%d%s\n",
+ epcmd->cmdtyp, epnum,
+ udc_ep->dir_in ? "in" : "out");
+
+ res = exynos_ss_udc_issue_epcmd(udc, epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to issue command\n");
+
+ list_del_init(&epcmd->queue);
+ kfree(epcmd);
+ }
+
+ /* If we have pending request, then start it */
+ restart = !list_empty(&udc_ep->queue);
+ if (restart) {
+ udc_req = get_ep_head(udc_ep);
+ exynos_ss_udc_start_req(udc, udc_ep,
+ udc_req, false);
+ }
+}
+
+/**
+ * exynos_ss_udc_xfer_complete - complete transfer
+ * @udc: The device state.
+ * @udc_ep: The endpoint that has just completed.
+ * @event: The event being handled.
+ *
+ * Transfer has been completed, update the transfer's state and then
+ * call the relevant completion routines.
+ */
+static void exynos_ss_udc_xfer_complete(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ u32 event)
+{
+ struct exynos_ss_udc_req *udc_req = udc_ep->req;
+ struct usb_request *req = &udc_req->req;
+ int size_left;
+ int result = 0;
+
+ dev_dbg(udc->dev, "%s: ep%d%s, req %p\n",
+ __func__, udc_ep->epnum,
+ udc_ep->dir_in ? "in" : "out", req);
+
+ if (!udc_req) {
+ dev_dbg(udc->dev, "XferCompl but no req\n");
+ return;
+ }
+
+ if (event & EXYNOS_USB3_DEPEVT_EventStatus_BUSERR) {
+ dev_err(udc->dev, "%s: Bus Error occured\n", __func__);
+ result = -ECONNRESET;
+ }
+
+ if (udc_ep->trb->param2 & EXYNOS_USB3_TRB_HWO)
+ dev_err(udc->dev, "%s: HWO bit set\n", __func__);
+
+ size_left = udc_ep->trb->param1 & EXYNOS_USB3_TRB_BUFSIZ_MASK;
+
+ if (udc_ep->dir_in) {
+ /* Incomplete IN transfer */
+ if (size_left) {
+ dev_err(udc->dev, "%s: BUFSIZ is not zero (%d)",
+ __func__, size_left);
+ /* REVISIT shall we -ECONNRESET here? */
+ }
+
+ udc_req->req.actual = udc_req->req.length - size_left;
+ } else {
+ int len;
+
+ len = (req->length + udc_ep->ep.maxpacket - 1) &
+ ~(udc_ep->ep.maxpacket - 1);
+ udc_req->req.actual = len - size_left;
+ }
+
+ if (udc_ep->epnum == 0) {
+ switch (udc->ep0_state) {
+ case EP0_SETUP_PHASE:
+ udc->ep0_state = EP0_DATA_PHASE;
+ break;
+ case EP0_DATA_PHASE:
+ udc->ep0_state = EP0_WAIT_NRDY;
+ break;
+ case EP0_STATUS_PHASE:
+ udc->ep0_state = EP0_SETUP_PHASE;
+ break;
+ default:
+ dev_err(udc->dev, "%s: Erroneous EP0 state (%d)",
+ __func__, udc->ep0_state);
+ /* Will try to repair from it */
+ udc->ep0_state = EP0_SETUP_PHASE;
+ return;
+ break;
+ }
+ }
+
+ exynos_ss_udc_complete_request_lock(udc, udc_ep, udc_req, result);
+}
+
+/**
+ * exynos_ss_udc_xfer_notready - process event Transfer Not Ready
+ * @udc: The device state.
+ * @udc_ep: The endpoint this event is for.
+ * @event: The event being handled.
+ */
+static void exynos_ss_udc_xfer_notready(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ u32 event)
+{
+ int index = (event & EXYNOS_USB3_DEPEVT_EPNUM_MASK) >> 1;
+ int direction = index & 1;
+ u32 status = event & EXYNOS_USB3_DEPEVT_EventStatus_CTL_MASK;
+
+ dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, udc_ep->epnum,
+ direction ? "in" : "out");
+
+ if (udc_ep->epnum == 0) {
+ switch (udc->ep0_state) {
+ case EP0_SETUP_PHASE:
+ /*
+ * Check if host is attempting to move data or start
+ * the status stage for a previous control transfer.
+ */
+ if (status !=
+ EXYNOS_USB3_DEPEVT_EventStatus_CTL_SETUP) {
+
+ dev_dbg(udc->dev, "Unexpected XferNotReady "
+ "during EP0 Setup phase\n");
+
+ exynos_ss_udc_ep0_restart(udc);
+ return;
+ }
+
+ break;
+
+ case EP0_DATA_PHASE:
+ /*
+ * Check if host is attempting to move data in the
+ * wrong direction.
+ */
+ if (udc_ep->dir_in != direction &&
+ status == EXYNOS_USB3_DEPEVT_EventStatus_CTL_DATA) {
+
+ dev_dbg(udc->dev, "Unexpected XferNotReady "
+ "during EP0 Data phase\n");
+
+ exynos_ss_udc_ep0_restart(udc);
+ return;
+ }
+
+ break;
+
+ case EP0_WAIT_NRDY:
+ /*
+ * Check if host is attempting to start the data stage
+ * when data stage is not present or move more data
+ * than specified in the wLength field.
+ */
+ if (status == EXYNOS_USB3_DEPEVT_EventStatus_CTL_DATA) {
+
+ dev_dbg(udc->dev, "Unexpected XferNotReady "
+ "during EP0 Wait NotReady\n");
+
+ exynos_ss_udc_ep0_restart(udc);
+ return;
+ }
+
+ udc_ep->dir_in = direction;
+ udc->ep0_state = EP0_STATUS_PHASE;
+ exynos_ss_udc_enqueue_status(udc);
+ break;
+
+ case EP0_STATUS_PHASE:
+ /* FALLTHROUGH */
+ default:
+ dev_dbg(udc->dev, "Unexpected XferNotReady\n");
+ break;
+ }
+ }
+}
+
+/**
+ * exynos_ss_udc_irq_connectdone - process event Connection Done
+ * @udc: The device state.
+ *
+ * Get the speed of connection and configure physical endpoints 0 & 1.
+ */
+static void exynos_ss_udc_irq_connectdone(struct exynos_ss_udc *udc)
+{
+ struct exynos_ss_udc_ep_command epcmd = {{0}, };
+ u32 reg, speed;
+ int mps0, mps;
+ int i;
+ int res;
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+
+ reg = readl(udc->regs + EXYNOS_USB3_DSTS);
+ speed = reg & EXYNOS_USB3_DSTS_ConnectSpd_MASK;
+
+ switch (speed) {
+ /* High-speed */
+ case 0:
+ udc->gadget.speed = USB_SPEED_HIGH;
+ mps0 = EP0_HS_MPS;
+ mps = EP_HS_MPS;
+ break;
+ /* Full-speed */
+ case 1:
+ case 3:
+ udc->gadget.speed = USB_SPEED_FULL;
+ mps0 = EP0_FS_MPS;
+ mps = EP_FS_MPS;
+ break;
+ /* Low-speed */
+ case 2:
+ udc->gadget.speed = USB_SPEED_LOW;
+ mps0 = EP0_LS_MPS;
+ mps = EP_LS_MPS;
+ break;
+ /* SuperSpeed */
+ case 4:
+ udc->gadget.speed = USB_SPEED_SUPER;
+ mps0 = EP0_SS_MPS;
+ mps = EP_SS_MPS;
+ break;
+
+ default:
+ dev_err(udc->dev, "Connection speed is unknown (%d)\n", speed);
+ return;
+ }
+
+ /* Suspend the inactive Phy */
+ if (udc->gadget.speed == USB_SPEED_SUPER) {
+ __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY);
+
+ /* Accept U1&U2 transition */
+ __orr32(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_AcceptU2Ena |
+ EXYNOS_USB3_DCTL_AcceptU1Ena);
+ } else {
+ __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+ }
+
+ udc->eps[0].ep.maxpacket = mps0;
+ for (i = 1; i < EXYNOS_USB3_EPS; i++)
+ udc->eps[i].ep.maxpacket = mps;
+
+ epcmd.ep = 0;
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(mps0);
+ epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn |
+ EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn;
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to configure physical EP0\n");
+
+ epcmd.ep = 1;
+ epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_EpDir |
+ EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn |
+ EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to configure physical EP1\n");
+}
+
+/**
+ * exynos_ss_udc_irq_usbrst - process event USB Reset
+ * @udc: The device state.
+ */
+static void exynos_ss_udc_irq_usbrst(struct exynos_ss_udc *udc)
+{
+ struct exynos_ss_udc_ep_command epcmd = {{0}, };
+ struct exynos_ss_udc_ep *ep;
+ int res;
+ int epnum;
+
+ dev_dbg(udc->dev, "%s\n", __func__);
+#ifdef CONFIG_USB_G_ANDROID
+ /*
+ * Android MTP should be configuration after disconnect uevet
+ * A reset USB device has the following characteristics:
+ * - Responds to the default USB address
+ * - Is not configured
+ * - Is not initially suspended
+ */
+ if (udc->state == USB_STATE_CONFIGURED)
+ call_gadget(udc, disconnect);
+#endif
+ /* Disable test mode */
+ __bic32(udc->regs + EXYNOS_USB3_DCTL, EXYNOS_USB3_DCTL_TstCtl_MASK);
+
+ /* Enable PHYs */
+ __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY);
+ __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER;
+
+ /* End transfer, kill all requests and clear STALL on the
+ non-EP0 endpoints */
+ for (epnum = 1; epnum < EXYNOS_USB3_EPS; epnum++) {
+
+ ep = &udc->eps[epnum];
+
+ epcmd.ep = get_phys_epnum(ep);
+
+ if (ep->tri) {
+ epcmd.cmdflags = (ep->tri <<
+ EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) |
+ EXYNOS_USB3_DEPCMDx_HiPri_ForceRM |
+ EXYNOS_USB3_DEPCMDx_CmdIOC |
+ EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0) {
+ dev_err(udc->dev, "Failed to end transfer\n");
+ ep->not_ready = 1;
+ }
+
+ ep->tri = 0;
+ }
+
+ exynos_ss_udc_kill_all_requests(udc, ep, -ECONNRESET);
+
+ if (ep->halted)
+ exynos_ss_udc_ep_sethalt(&ep->ep, 0);
+ }
+
+ /* Set device address to 0 */
+ __bic32(udc->regs + EXYNOS_USB3_DCFG, EXYNOS_USB3_DCFG_DevAddr_MASK);
+
+ udc->state = USB_STATE_DEFAULT;
+}
+
+/**
+ * exynos_ss_udc_irq_ulstchng - process event USB Link State Change
+ * @udc: The device state.
+ * @event: The event being handled.
+ */
+static void exynos_ss_udc_irq_ulstchng(struct exynos_ss_udc *udc, u32 event)
+{
+ u32 link_state;
+
+ link_state = event & EXYNOS_USB3_DEVT_EvtInfo_MASK;
+
+ if (event & EXYNOS_USB3_DEVT_EvtInfo_SS) {
+ if (link_state == EXYNOS_USB3_DEVT_EvtInfo_U3)
+ __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+ else
+ __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+ } else {
+ if (link_state == EXYNOS_USB3_DEVT_EvtInfo_Suspend)
+ __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY);
+ else
+ __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY);
+ }
+
+ /* Disconnect event detection for SMDK5250 EVT0 */
+#if defined(CONFIG_MACH_SMDK5250)
+ if (udc->release == 0x185a) {
+ if (link_state == EXYNOS_USB3_DEVT_EvtInfo_Suspend ||
+ link_state == EXYNOS_USB3_DEVT_EvtInfo_SS_DIS) {
+ call_gadget(udc, disconnect);
+ EXYNOS_SS_UDC_CABLE_CONNECT(udc, false);
+ dev_dbg(udc->dev, "Disconnection (0x%x, %s)\n",
+ link_state >> EXYNOS_USB3_DEVT_EvtInfo_SHIFT,
+ event & EXYNOS_USB3_DEVT_EvtInfo_SS ?
+ "SS" : "non-SS");
+ }
+ }
+#endif
+}
+
+/**
+ * exynos_ss_udc_handle_depevt - handle endpoint-specific event
+ * @udc: The device state.
+ * @event: The event being handled.
+ */
+static void exynos_ss_udc_handle_depevt(struct exynos_ss_udc *udc, u32 event)
+{
+ int index = (event & EXYNOS_USB3_DEPEVT_EPNUM_MASK) >> 1;
+ int epnum = get_usb_epnum(index);
+ struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum];
+
+ switch (event & EXYNOS_USB3_DEPEVT_EVENT_MASK) {
+ case EXYNOS_USB3_DEPEVT_EVENT_XferNotReady:
+ dev_dbg(udc->dev, "Xfer Not Ready\n");
+
+ exynos_ss_udc_xfer_notready(udc, udc_ep, event);
+ break;
+
+ case EXYNOS_USB3_DEPEVT_EVENT_XferComplete:
+ dev_dbg(udc->dev, "Xfer Complete\n");
+
+ exynos_ss_udc_xfer_complete(udc, udc_ep, event);
+
+ if (epnum == 0 && udc->ep0_state == EP0_SETUP_PHASE)
+ exynos_ss_udc_enqueue_setup(udc);
+
+ break;
+
+ case EXYNOS_USB3_DEPEVT_EVENT_EPCmdCmplt:
+ dev_dbg(udc->dev, "EP Cmd Complete\n");
+
+ exynos_ss_udc_ep_cmd_complete(udc, udc_ep, event);
+ break;
+ }
+}
+
+/**
+ * exynos_ss_udc_handle_devt - handle device-specific event
+ * @udc: The driver state.
+ * @event: The event being handled.
+ */
+static void exynos_ss_udc_handle_devt(struct exynos_ss_udc *udc, u32 event)
+{
+ switch (event & EXYNOS_USB3_DEVT_EVENT_MASK) {
+ case EXYNOS_USB3_DEVT_EVENT_ULStChng:
+ dev_dbg(udc->dev, "USB-Link State Change");
+ exynos_ss_udc_irq_ulstchng(udc, event);
+ break;
+
+ case EXYNOS_USB3_DEVT_EVENT_ConnectDone:
+#if defined(USE_WAKE_LOCK)
+ wake_lock(&udc->usbd_wake_lock);
+#endif
+ dev_dbg(udc->dev, "Connection Done");
+ EXYNOS_SS_UDC_CABLE_CONNECT(udc, true);
+ exynos_ss_udc_irq_connectdone(udc);
+ break;
+
+ case EXYNOS_USB3_DEVT_EVENT_USBRst:
+ dev_dbg(udc->dev, "USB Reset");
+ exynos_ss_udc_irq_usbrst(udc);
+ break;
+
+ case EXYNOS_USB3_DEVT_EVENT_DisconnEvt:
+ dev_info(udc->dev, "Disconnection Detected");
+ call_gadget(udc, disconnect);
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+ udc->state = USB_STATE_NOTATTACHED;
+ EXYNOS_SS_UDC_CABLE_CONNECT(udc, false);
+
+#if defined(USE_WAKE_LOCK)
+ wake_lock_timeout(&udc->usbd_wake_lock, HZ * 5);
+#endif
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void exynos_ss_udc_handle_otgevt(struct exynos_ss_udc *udc, u32 event)
+{}
+
+static void exynos_ss_udc_handle_gevt(struct exynos_ss_udc *udc, u32 event)
+{}
+
+/**
+ * exynos_ss_udc_irq - handle device interrupt
+ * @irq: The IRQ number triggered.
+ * @pw: The pw value when registered the handler.
+ */
+static irqreturn_t exynos_ss_udc_irq(int irq, void *pw)
+{
+ struct exynos_ss_udc *udc = pw;
+ int indx = udc->event_indx;
+ int gevntcount;
+ u32 event;
+ u32 ecode1, ecode2;
+
+ gevntcount = readl(udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)) &
+ EXYNOS_USB3_GEVNTCOUNTx_EVNTCount_MASK;
+ /* TODO: what if number of events more then buffer size? */
+
+ dev_dbg(udc->dev, "INTERRUPT (%d)\n", gevntcount >> 2);
+
+ while (gevntcount) {
+ event = udc->event_buff[indx++];
+
+ dev_dbg(udc->dev, "event[%x] = 0x%08x\n",
+ (unsigned int) &udc->event_buff[indx - 1], event);
+
+ ecode1 = event & 0x01;
+
+ if (ecode1 == 0)
+ /* Device Endpoint-Specific Event */
+ exynos_ss_udc_handle_depevt(udc, event);
+ else {
+ ecode2 = (event & 0xfe) >> 1;
+
+ switch (ecode2) {
+ /* Device-Specific Event */
+ case 0x00:
+ exynos_ss_udc_handle_devt(udc, event);
+ break;
+
+ /* OTG Event */
+ case 0x01:
+ exynos_ss_udc_handle_otgevt(udc, event);
+ break;
+
+ /* Other Core Event */
+ case 0x03:
+ case 0x04:
+ exynos_ss_udc_handle_gevt(udc, event);
+ break;
+
+ /* Unknown Event Type */
+ default:
+ dev_info(udc->dev, "Unknown event type\n");
+ break;
+ }
+ }
+ /* We processed 1 event (4 bytes) */
+ writel(4, udc->regs + EXYNOS_USB3_GEVNTCOUNT(0));
+
+ if (indx > (EXYNOS_USB3_EVENT_BUFF_WSIZE - 1))
+ indx = 0;
+
+ gevntcount -= 4;
+ }
+
+ udc->event_indx = indx;
+ /* Do we need to read GEVENTCOUNT here and retry? */
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * exynos_ss_udc_free_all_trb - free all allocated TRBs
+ * @udc: The device state.
+ */
+static void exynos_ss_udc_free_all_trb(struct exynos_ss_udc *udc)
+{
+ int epnum;
+
+ for (epnum = 0; epnum < EXYNOS_USB3_EPS; epnum++) {
+ struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum];
+
+ if (udc_ep->trb_dma)
+ dma_free_coherent(NULL,
+ sizeof(struct exynos_ss_udc_trb),
+ udc_ep->trb,
+ udc_ep->trb_dma);
+ }
+}
+
+/**
+ * exynos_ss_udc_initep - initialize a single endpoint
+ * @udc: The device state.
+ * @udc_ep: The endpoint being initialised.
+ * @epnum: The endpoint number.
+ *
+ * Initialise the given endpoint (as part of the probe and device state
+ * creation) to give to the gadget driver. Setup the endpoint name, any
+ * direction information and other state that may be required.
+ */
+static int __devinit exynos_ss_udc_initep(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep,
+ int epnum)
+{
+ char *dir;
+
+ if (epnum == 0)
+ dir = "";
+ else if ((epnum % 2) == 0) {
+ dir = "out";
+ } else {
+ dir = "in";
+ udc_ep->dir_in = 1;
+ }
+
+ udc_ep->epnum = epnum;
+
+ snprintf(udc_ep->name, sizeof(udc_ep->name), "ep%d%s", epnum, dir);
+
+ INIT_LIST_HEAD(&udc_ep->queue);
+ INIT_LIST_HEAD(&udc_ep->cmd_queue);
+ INIT_LIST_HEAD(&udc_ep->ep.ep_list);
+
+ spin_lock_init(&udc_ep->lock);
+
+ /* add to the list of endpoints known by the gadget driver */
+ if (epnum)
+ list_add_tail(&udc_ep->ep.ep_list, &udc->gadget.ep_list);
+
+ udc_ep->parent = udc;
+ udc_ep->ep.name = udc_ep->name;
+#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE)
+ udc_ep->ep.maxpacket = epnum ? EP_SS_MPS : EP0_SS_MPS;
+#else
+ udc_ep->ep.maxpacket = epnum ? EP_HS_MPS : EP0_HS_MPS;
+#endif
+ udc_ep->ep.ops = &exynos_ss_udc_ep_ops;
+ udc_ep->trb = dma_alloc_coherent(NULL,
+ sizeof(struct exynos_ss_udc_trb),
+ &udc_ep->trb_dma,
+ GFP_KERNEL);
+ if (!udc_ep->trb)
+ return -ENOMEM;
+
+ memset(udc_ep->trb, 0, sizeof(struct exynos_ss_udc_trb));
+
+ if (epnum == 0) {
+ /* allocate EP0 control request */
+ udc->ctrl_req = exynos_ss_udc_ep_alloc_request(&udc->eps[0].ep,
+ GFP_KERNEL);
+ if (!udc->ctrl_req)
+ return -ENOMEM;
+
+ udc->ep0_state = EP0_UNCONNECTED;
+ }
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_phy_set - intitialize the controller PHY interface
+ * @pdev: The platform-level device instance.
+ */
+static void exynos_ss_udc_phy_set(struct platform_device *pdev)
+{
+ struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data;
+ struct exynos_ss_udc *udc = platform_get_drvdata(pdev);
+ /* The reset values:
+ * GUSB2PHYCFG(0) = 0x00002400
+ * GUSB3PIPECTL(0) = 0x00260002
+ */
+
+ __orr32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset);
+ __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst);
+ __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst);
+
+ /* PHY initialization */
+ if (pdata && pdata->phy_init)
+ pdata->phy_init(pdev, pdata->phy_type);
+
+ __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst);
+ __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst);
+ __bic32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset);
+
+
+ __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY |
+ EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM |
+ EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim_MASK);
+ __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim(9));
+
+ __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+
+ dev_dbg(udc->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x",
+ readl(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0)),
+ readl(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0)));
+}
+
+/**
+ * exynos_ss_udc_phy_unset - disable the controller PHY interface
+ * @pdev: The platform-level device instance.
+ */
+static void exynos_ss_udc_phy_unset(struct platform_device *pdev)
+{
+ struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data;
+ struct exynos_ss_udc *udc = platform_get_drvdata(pdev);
+
+ __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY |
+ EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM);
+ __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, pdata->phy_type);
+
+ dev_dbg(udc->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x",
+ readl(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0)),
+ readl(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0)));
+}
+
+/**
+ * exynos_ss_udc_corereset - issue softreset to the core
+ * @udc: The device state.
+ *
+ * Issue a soft reset to the core, and await the core finishing it.
+ */
+static int exynos_ss_udc_corereset(struct exynos_ss_udc *udc)
+{
+ int res;
+
+ /* issue soft reset */
+ __orr32(udc->regs + EXYNOS_USB3_DCTL, EXYNOS_USB3_DCTL_CSftRst);
+
+ res = poll_bit_clear(udc->regs + EXYNOS_USB3_DCTL,
+ EXYNOS_USB3_DCTL_CSftRst,
+ 1000);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to get CSftRst asserted\n");
+
+ return res;
+}
+
+/**
+ * exynos_ss_udc_ep0_activate - activate USB endpoint 0
+ * @udc: The device state.
+ *
+ * Configure physical endpoints 0 & 1.
+ */
+static void exynos_ss_udc_ep0_activate(struct exynos_ss_udc *udc)
+{
+ struct exynos_ss_udc_ep_command epcmd = {{0}, };
+ int res;
+
+ /* Start New Configuration */
+ epcmd.ep = 0;
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to start new configuration\n");
+
+ /* Configure Physical Endpoint 0 */
+ epcmd.ep = 0;
+#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE)
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS);
+#else
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_HS_MPS);
+#endif
+ epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn |
+ EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn;
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to configure physical EP0\n");
+
+ /* Configure Physical Endpoint 1 */
+ epcmd.ep = 1;
+#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE)
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS);
+#else
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_HS_MPS);
+#endif
+ epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_EpDir |
+ EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn |
+ EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn;
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to configure physical EP1\n");
+
+ /* Configure Pysical Endpoint 0 Transfer Resource */
+ epcmd.ep = 0;
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1);
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev,
+ "Failed to configure physical EP0 transfer resource\n");
+
+ /* Configure Pysical Endpoint 1 Transfer Resource */
+ epcmd.ep = 1;
+ epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1);
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG;
+ epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0)
+ dev_err(udc->dev,
+ "Failed to configure physical EP1 transfer resource\n");
+
+ /* Enable Physical Endpoints 0 and 1 */
+ writel(3, udc->regs + EXYNOS_USB3_DALEPENA);
+}
+
+/**
+ * exynos_ss_udc_ep_activate - activate USB endpoint
+ * @udc: The device state.
+ * @udc_ep: The endpoint being activated.
+ *
+ * Configure physical endpoints > 1.
+ */
+static void exynos_ss_udc_ep_activate(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep)
+{
+ struct exynos_ss_udc_ep_command ep_command;
+ struct exynos_ss_udc_ep_command *epcmd = &ep_command;
+ int epnum = udc_ep->epnum;
+ int maxburst = udc_ep->ep.maxburst;
+ int res;
+
+ if (!udc->eps_enabled) {
+ udc->eps_enabled = true;
+
+ /* Start New Configuration */
+ epcmd->ep = 0;
+ epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG;
+ epcmd->cmdflags =
+ (2 << EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) |
+ EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to start new configuration\n");
+ }
+
+ if (udc_ep->not_ready) {
+ epcmd = kzalloc(sizeof(struct exynos_ss_udc_ep_command),
+ GFP_ATOMIC);
+ if (!epcmd) {
+ /* Will try to issue command immediately */
+ epcmd = &ep_command;
+ udc_ep->not_ready = 0;
+ }
+ }
+
+ epcmd->ep = get_phys_epnum(udc_ep);
+ epcmd->param0 = EXYNOS_USB3_DEPCMDPAR0x_EPType(udc_ep->type) |
+ EXYNOS_USB3_DEPCMDPAR0x_MPS(udc_ep->ep.maxpacket) |
+ EXYNOS_USB3_DEPCMDPAR0x_BrstSiz(maxburst);
+ if (udc_ep->dir_in)
+ epcmd->param0 |= EXYNOS_USB3_DEPCMDPAR0x_FIFONum(epnum);
+ epcmd->param1 = EXYNOS_USB3_DEPCMDPAR1x_EpNum(epnum) |
+ (udc_ep->dir_in ? EXYNOS_USB3_DEPCMDPAR1x_EpDir : 0) |
+ EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn |
+ EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn;
+ epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG;
+ epcmd->cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ if (udc_ep->not_ready)
+ list_add_tail(&epcmd->queue, &udc_ep->cmd_queue);
+ else {
+ res = exynos_ss_udc_issue_epcmd(udc, epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to configure physical EP\n");
+ }
+
+ /* Configure Pysical Endpoint Transfer Resource */
+ if (udc_ep->not_ready) {
+ epcmd = kzalloc(sizeof(struct exynos_ss_udc_ep_command),
+ GFP_ATOMIC);
+ if (!epcmd) {
+ epcmd = &ep_command;
+ udc_ep->not_ready = 0;
+ }
+ }
+
+ epcmd->ep = get_phys_epnum(udc_ep);
+ epcmd->param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1);
+ epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG;
+ epcmd->cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ if (udc_ep->not_ready)
+ list_add_tail(&epcmd->queue, &udc_ep->cmd_queue);
+ else {
+ res = exynos_ss_udc_issue_epcmd(udc, epcmd);
+ if (res < 0)
+ dev_err(udc->dev, "Failed to configure physical EP "
+ "transfer resource\n");
+ }
+
+ /* Enable Physical Endpoint */
+ __orr32(udc->regs + EXYNOS_USB3_DALEPENA, 1 << epcmd->ep);
+}
+
+/**
+ * exynos_ss_udc_ep_deactivate - deactivate USB endpoint
+ * @udc: The device state.
+ * @udc_ep: The endpoint being deactivated.
+ *
+ * End any active transfer and disable endpoint.
+ */
+static void exynos_ss_udc_ep_deactivate(struct exynos_ss_udc *udc,
+ struct exynos_ss_udc_ep *udc_ep)
+{
+ struct exynos_ss_udc_ep_command epcmd = {{0}, };
+ int index = get_phys_epnum(udc_ep);
+
+ udc->eps_enabled = false;
+
+ if (udc_ep->tri && !udc_ep->not_ready) {
+ int res;
+
+ epcmd.ep = get_phys_epnum(udc_ep);
+ epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER;
+ epcmd.cmdflags = (udc_ep->tri <<
+ EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) |
+ EXYNOS_USB3_DEPCMDx_HiPri_ForceRM |
+ EXYNOS_USB3_DEPCMDx_CmdIOC |
+ EXYNOS_USB3_DEPCMDx_CmdAct;
+
+ res = exynos_ss_udc_issue_epcmd(udc, &epcmd);
+ if (res < 0) {
+ dev_err(udc->dev, "Failed to end transfer\n");
+ udc_ep->not_ready = 1;
+ }
+
+ udc_ep->tri = 0;
+ }
+
+ __bic32(udc->regs + EXYNOS_USB3_DALEPENA, 1 << index);
+}
+
+/**
+ * exynos_ss_udc_init - initialize the controller
+ * @udc: The device state.
+ *
+ * Initialize the event buffer, enable events, activate USB EP0,
+ * and start the controller operation.
+ */
+static void exynos_ss_udc_init(struct exynos_ss_udc *udc)
+{
+ u32 reg;
+
+ reg = readl(udc->regs + EXYNOS_USB3_GSNPSID);
+ udc->release = reg & 0xffff;
+ /*
+ * WORKAROUND: core revision 1.80a has a wrong release number
+ * in GSNPSID register
+ */
+ if (udc->release == 0x131a)
+ udc->release = 0x180a;
+ dev_info(udc->dev, "Core ID Number: 0x%04x\n", reg >> 16);
+ dev_info(udc->dev, "Release Number: 0x%04x\n", udc->release);
+
+ /*
+ * WORKAROUND: DWC3 revisions <1.90a have a bug
+ * when The device fails to connect at SuperSpeed
+ * and falls back to high-speed mode which causes
+ * the device to enter in a Connect/Disconnect loop
+ */
+ if (udc->release < 0x190a)
+ __orr32(udc->regs + EXYNOS_USB3_GCTL,
+ EXYNOS_USB3_GCTL_U2RSTECN);
+
+ writel(EXYNOS_USB3_GSBUSCFG0_INCR16BrstEna,
+ udc->regs + EXYNOS_USB3_GSBUSCFG0);
+ writel(EXYNOS_USB3_GSBUSCFG1_BREQLIMIT(3),
+ udc->regs + EXYNOS_USB3_GSBUSCFG1);
+
+ /* Event buffer */
+ udc->event_indx = 0;
+ writel(0, udc->regs + EXYNOS_USB3_GEVNTADR_63_32(0));
+ writel(udc->event_buff_dma,
+ udc->regs + EXYNOS_USB3_GEVNTADR_31_0(0));
+ /* Set Event Buffer size */
+ writel(EXYNOS_USB3_EVENT_BUFF_BSIZE,
+ udc->regs + EXYNOS_USB3_GEVNTSIZ(0));
+
+ writel(EXYNOS_USB3_DCFG_NumP(1) | EXYNOS_USB3_DCFG_PerFrInt(2) |
+#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE)
+ EXYNOS_USB3_DCFG_DevSpd(4),
+#else
+ EXYNOS_USB3_DCFG_DevSpd(0),
+#endif
+ udc->regs + EXYNOS_USB3_DCFG);
+
+ /* Flush any pending events */
+ __orr32(udc->regs + EXYNOS_USB3_GEVNTSIZ(0),
+ EXYNOS_USB3_GEVNTSIZx_EvntIntMask);
+
+ reg = readl(udc->regs + EXYNOS_USB3_GEVNTCOUNT(0));
+ writel(reg, udc->regs + EXYNOS_USB3_GEVNTCOUNT(0));
+
+ __bic32(udc->regs + EXYNOS_USB3_GEVNTSIZ(0),
+ EXYNOS_USB3_GEVNTSIZx_EvntIntMask);
+
+ /* Enable events */
+ writel(EXYNOS_USB3_DEVTEN_ULStCngEn | EXYNOS_USB3_DEVTEN_ConnectDoneEn |
+ EXYNOS_USB3_DEVTEN_USBRstEn | EXYNOS_USB3_DEVTEN_DisconnEvtEn,
+ udc->regs + EXYNOS_USB3_DEVTEN);
+
+ exynos_ss_udc_ep0_activate(udc);
+}
+
+static int exynos_ss_udc_enable(struct exynos_ss_udc *udc)
+{
+ struct platform_device *pdev = to_platform_device(udc->dev);
+
+ enable_irq(udc->irq);
+ clk_enable(udc->clk);
+
+ exynos_ss_udc_phy_set(pdev);
+ exynos_ss_udc_corereset(udc);
+ exynos_ss_udc_init(udc);
+
+ udc->eps[0].enabled = 1;
+ udc->ep0_state = EP0_SETUP_PHASE;
+ exynos_ss_udc_enqueue_setup(udc);
+
+ /* Start the device controller operation */
+ exynos_ss_udc_run_stop(udc, 1);
+
+ return 0;
+}
+
+#ifdef CONFIG_USB_EXYNOS_SWITCH
+#define EXYNOS_SS_UDC_ENABLE(udc)
+#else
+#define EXYNOS_SS_UDC_ENABLE(udc) exynos_ss_udc_enable(udc)
+#endif
+
+static int exynos_ss_udc_disable(struct exynos_ss_udc *udc)
+{
+ struct platform_device *pdev = to_platform_device(udc->dev);
+ int ep;
+
+ exynos_ss_udc_run_stop(udc, 0);
+ /* all endpoints should be shutdown */
+ for (ep = 0; ep < EXYNOS_USB3_EPS; ep++)
+ exynos_ss_udc_ep_disable(&udc->eps[ep].ep);
+
+ call_gadget(udc, disconnect);
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+
+ exynos_ss_udc_phy_unset(pdev);
+
+ clk_disable(udc->clk);
+
+ disable_irq(udc->irq);
+
+ EXYNOS_SS_UDC_CABLE_CONNECT(udc, false);
+
+ return 0;
+}
+
+#ifdef CONFIG_USB_EXYNOS_SWITCH
+#define EXYNOS_SS_UDC_DISABLE(udc)
+#else
+#define EXYNOS_SS_UDC_DISABLE(udc) exynos_ss_udc_disable(udc)
+#endif
+
+/**
+ * exynos_ss_udc_vbus_session - software-controlled vbus active/in-active
+ * @gadget: The peripheral being vbus active/in-active.
+ * @is_active: The action to take (1 - vbus enable, 0 - vbus disable).
+ */
+static int exynos_ss_udc_vbus_session(struct usb_gadget *gadget, int is_active)
+{
+ struct exynos_ss_udc *udc = container_of(gadget,
+ struct exynos_ss_udc, gadget);
+
+ if (!is_active)
+ exynos_ss_udc_disable(udc);
+ else
+ exynos_ss_udc_enable(udc);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_pullup - software-controlled connect/disconnect to USB host
+ * @gadget: The peripheral being connected/disconnected.
+ * @is_on: The action to take (1 - connect, 0 - disconnect).
+ */
+static int exynos_ss_udc_pullup(struct usb_gadget *gadget, int is_on)
+{
+ struct exynos_ss_udc *udc = container_of(gadget,
+ struct exynos_ss_udc, gadget);
+
+ exynos_ss_udc_run_stop(udc, is_on);
+
+ return 0;
+}
+
+/**
+ * exynos_ss_udc_get_config_params - get UDC configuration
+ * @params: The controller parameters being returned to the caller.
+ */
+void exynos_ss_udc_get_config_params(struct usb_dcd_config_params *params)
+{
+ params->bU1devExitLat = EXYNOS_USB3_U1_DEV_EXIT_LAT;
+ params->bU2DevExitLat = cpu_to_le16(EXYNOS_USB3_U2_DEV_EXIT_LAT);
+}
+
+static struct usb_gadget_ops exynos_ss_udc_gadget_ops = {
+ .vbus_session = exynos_ss_udc_vbus_session,
+ .pullup = exynos_ss_udc_pullup,
+ .get_config_params = exynos_ss_udc_get_config_params,
+};
+
+int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
+ int (*bind)(struct usb_gadget *))
+{
+ struct exynos_ss_udc *udc = our_udc;
+ int ret;
+
+ if (!udc) {
+ printk(KERN_ERR "%s: called with no device\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!driver) {
+ dev_err(udc->dev, "%s: no driver\n", __func__);
+ return -EINVAL;
+ }
+
+ if (driver->speed < USB_SPEED_FULL) {
+ dev_err(udc->dev, "%s: bad speed\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!bind || !driver->setup) {
+ dev_err(udc->dev, "%s: missing entry points\n", __func__);
+ return -EINVAL;
+ }
+
+ WARN_ON(udc->driver);
+
+ driver->driver.bus = NULL;
+ udc->driver = driver;
+ udc->gadget.dev.driver = &driver->driver;
+ udc->gadget.dev.dma_mask = udc->dev->dma_mask;
+ udc->gadget.speed = USB_SPEED_UNKNOWN;
+
+ ret = device_add(&udc->gadget.dev);
+ if (ret) {
+ dev_err(udc->dev, "failed to register gadget device\n");
+ goto err;
+ }
+
+ ret = bind(&udc->gadget);
+ if (ret) {
+ dev_err(udc->dev, "failed bind %s\n", driver->driver.name);
+
+ udc->gadget.dev.driver = NULL;
+ udc->driver = NULL;
+ goto err;
+ }
+ /* we must now enable ep0 ready for host detection and then
+ * set configuration. */
+ EXYNOS_SS_UDC_ENABLE(udc);
+ /* report to the user, and return */
+ dev_info(udc->dev, "bound driver %s\n", driver->driver.name);
+ return 0;
+
+err:
+ udc->driver = NULL;
+ udc->gadget.dev.driver = NULL;
+ return ret;
+}
+EXPORT_SYMBOL(usb_gadget_probe_driver);
+
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+ struct exynos_ss_udc *udc = our_udc;
+
+ if (!udc)
+ return -ENODEV;
+
+ if (!driver || driver != udc->driver || !driver->unbind)
+ return -EINVAL;
+
+ EXYNOS_SS_UDC_DISABLE(udc);
+
+ udc->driver = NULL;
+ driver->unbind(&udc->gadget);
+ device_del(&udc->gadget.dev);
+
+ dev_info(udc->dev, "unregistered gadget driver '%s'\n",
+ driver->driver.name);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+static int __devinit exynos_ss_udc_probe(struct platform_device *pdev)
+{
+ struct exynos_ss_udc_plat *plat = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct exynos_ss_udc *udc;
+ struct resource *res;
+ int epnum;
+ int ret;
+
+ if (!plat) {
+ dev_err(dev, "cannot get platform data\n");
+ return -ENODEV;
+ }
+
+ udc = kzalloc(sizeof(struct exynos_ss_udc), GFP_KERNEL);
+ if (!udc) {
+ dev_err(dev, "cannot get memory\n");
+ return -ENOMEM;
+ }
+
+ udc->dev = dev;
+ udc->plat = plat;
+
+ udc->event_buff = dma_alloc_coherent(NULL,
+ EXYNOS_USB3_EVENT_BUFF_BSIZE,
+ &udc->event_buff_dma,
+ GFP_KERNEL);
+ if (!udc->event_buff) {
+ dev_err(dev, "cannot get memory for event buffer\n");
+ ret = -ENOMEM;
+ goto err_mem;
+ }
+ memset(udc->event_buff, 0, EXYNOS_USB3_EVENT_BUFF_BSIZE);
+
+ udc->ctrl_buff = dma_alloc_coherent(NULL,
+ EXYNOS_USB3_CTRL_BUFF_SIZE,
+ &udc->ctrl_buff_dma,
+ GFP_KERNEL);
+ if (!udc->ctrl_buff) {
+ dev_err(dev, "cannot get memory for control buffer\n");
+ ret = -ENOMEM;
+ goto err_mem;
+ }
+ memset(udc->ctrl_buff, 0, EXYNOS_USB3_CTRL_BUFF_SIZE);
+
+ udc->ep0_buff = dma_alloc_coherent(NULL,
+ EXYNOS_USB3_EP0_BUFF_SIZE,
+ &udc->ep0_buff_dma,
+ GFP_KERNEL);
+ if (!udc->ep0_buff) {
+ dev_err(dev, "cannot get memory for EP0 buffer\n");
+ ret = -ENOMEM;
+ goto err_mem;
+ }
+ memset(udc->ep0_buff, 0, EXYNOS_USB3_EP0_BUFF_SIZE);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev, "cannot find register resource 0\n");
+ ret = -EINVAL;
+ goto err_mem;
+ }
+
+ if (!request_mem_region(res->start, resource_size(res),
+ dev_name(dev))) {
+ dev_err(dev, "cannot reserve registers\n");
+ ret = -ENOENT;
+ goto err_mem;
+ }
+
+ udc->regs = ioremap(res->start, resource_size(res));
+ if (!udc->regs) {
+ dev_err(dev, "cannot map registers\n");
+ ret = -ENXIO;
+ goto err_remap;
+ }
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0) {
+ dev_err(dev, "cannot find irq\n");
+ goto err_irq;
+ }
+
+ udc->irq = ret;
+
+#if defined(USE_WAKE_LOCK)
+ wake_lock_init(&udc->usbd_wake_lock, WAKE_LOCK_SUSPEND,
+ "usb device wake lock");
+#endif
+ ret = request_irq(udc->irq,
+ exynos_ss_udc_irq,
+ IRQF_SHARED,
+ dev_name(dev),
+ udc);
+ if (ret < 0) {
+ dev_err(dev, "cannot claim IRQ\n");
+ goto err_irq;
+ }
+
+#ifndef CONFIG_USB_XHCI_EXYNOS
+ disable_irq(udc->irq);
+#endif
+ dev_info(dev, "regs %p, irq %d\n", udc->regs, udc->irq);
+
+ udc->clk = clk_get(&pdev->dev, "usbdrd30");
+ if (IS_ERR(udc->clk)) {
+ dev_err(dev, "cannot get UDC clock\n");
+ ret = -EINVAL;
+ goto err_clk;
+ }
+
+ device_initialize(&udc->gadget.dev);
+
+ dev_set_name(&udc->gadget.dev, "gadget");
+
+ udc->gadget.is_dualspeed = 1;
+ udc->gadget.ops = &exynos_ss_udc_gadget_ops;
+ udc->gadget.name = dev_name(dev);
+
+ udc->gadget.dev.parent = dev;
+ udc->gadget.dev.dma_mask = dev->dma_mask;
+
+ /* setup endpoint information */
+
+ INIT_LIST_HEAD(&udc->gadget.ep_list);
+ udc->gadget.ep0 = &udc->eps[0].ep;
+
+ /* initialise the endpoints now the core has been initialised */
+ for (epnum = 0; epnum < EXYNOS_USB3_EPS; epnum++) {
+ ret = exynos_ss_udc_initep(udc, &udc->eps[epnum], epnum);
+ if (ret < 0) {
+ dev_err(dev, "cannot get memory for TRB\n");
+ goto err_ep;
+ }
+ }
+
+ platform_set_drvdata(pdev, udc);
+
+ our_udc = udc;
+ return 0;
+
+err_ep:
+ exynos_ss_udc_ep_free_request(&udc->eps[0].ep, udc->ctrl_req);
+ exynos_ss_udc_free_all_trb(udc);
+ clk_put(udc->clk);
+err_clk:
+ free_irq(udc->irq, udc);
+err_irq:
+ iounmap(udc->regs);
+err_remap:
+ release_mem_region(res->start, resource_size(res));
+err_mem:
+ if (udc->ep0_buff)
+ dma_free_coherent(NULL, EXYNOS_USB3_EP0_BUFF_SIZE,
+ udc->ep0_buff, udc->ep0_buff_dma);
+ if (udc->ctrl_buff)
+ dma_free_coherent(NULL, EXYNOS_USB3_CTRL_BUFF_SIZE,
+ udc->ctrl_buff, udc->ctrl_buff_dma);
+ if (udc->event_buff)
+ dma_free_coherent(NULL, EXYNOS_USB3_EVENT_BUFF_BSIZE,
+ udc->event_buff, udc->event_buff_dma);
+ kfree(udc);
+
+ return ret;
+}
+
+static int __devexit exynos_ss_udc_remove(struct platform_device *pdev)
+{
+ struct exynos_ss_udc *udc = platform_get_drvdata(pdev);
+ struct resource *res;
+
+ usb_gadget_unregister_driver(udc->driver);
+
+ exynos_ss_udc_phy_unset(pdev);
+
+ clk_disable(udc->clk);
+ clk_put(udc->clk);
+
+ free_irq(udc->irq, udc);
+
+ iounmap(udc->regs);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res)
+ release_mem_region(res->start, resource_size(res));
+
+ device_unregister(&udc->gadget.dev);
+
+ platform_set_drvdata(pdev, NULL);
+
+ exynos_ss_udc_ep_free_request(&udc->eps[0].ep, udc->ctrl_req);
+ exynos_ss_udc_free_all_trb(udc);
+
+ dma_free_coherent(NULL, EXYNOS_USB3_EP0_BUFF_SIZE,
+ udc->ep0_buff, udc->ep0_buff_dma);
+ dma_free_coherent(NULL, EXYNOS_USB3_CTRL_BUFF_SIZE,
+ udc->ctrl_buff, udc->ctrl_buff_dma);
+ dma_free_coherent(NULL, EXYNOS_USB3_EVENT_BUFF_BSIZE,
+ udc->event_buff, udc->event_buff_dma);
+ kfree(udc);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int exynos_ss_udc_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct exynos_ss_udc *udc = platform_get_drvdata(pdev);
+
+ if (udc->driver) {
+ call_gadget(udc, suspend);
+ EXYNOS_SS_UDC_DISABLE(udc);
+ }
+
+ return 0;
+}
+
+static int exynos_ss_udc_resume(struct platform_device *pdev)
+{
+ struct exynos_ss_udc *udc = platform_get_drvdata(pdev);
+
+ if (udc->driver) {
+ EXYNOS_SS_UDC_ENABLE(udc);
+ call_gadget(udc, resume);
+ }
+
+ return 0;
+}
+#else
+#define exynos_ss_udc_suspend NULL
+#define exynos_ss_udc_resume NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver exynos_ss_udc_driver = {
+ .driver = {
+ .name = "exynos-ss-udc",
+ .owner = THIS_MODULE,
+ },
+ .probe = exynos_ss_udc_probe,
+ .remove = __devexit_p(exynos_ss_udc_remove),
+ .suspend = exynos_ss_udc_suspend,
+ .resume = exynos_ss_udc_resume,
+};
+
+static int __init exynos_ss_udc_modinit(void)
+{
+ return platform_driver_register(&exynos_ss_udc_driver);
+}
+
+static void __exit exynos_ss_udc_modexit(void)
+{
+ platform_driver_unregister(&exynos_ss_udc_driver);
+}
+
+module_init(exynos_ss_udc_modinit);
+module_exit(exynos_ss_udc_modexit);
+
+MODULE_DESCRIPTION("EXYNOS SuperSpeed USB 3.0 Device Controller");
+MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/exynos_ss_udc.h b/drivers/usb/gadget/exynos_ss_udc.h
new file mode 100644
index 0000000..a413a44
--- /dev/null
+++ b/drivers/usb/gadget/exynos_ss_udc.h
@@ -0,0 +1,462 @@
+/* linux/drivers/usb/gadget/exynos_ss_udc.h
+ *
+ * Copyright 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * EXYNOS SuperSpeed USB 3.0 Device Controlle driver
+ *
+ * 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_SS_UDC_H__
+#define __EXYNOS_SS_UDC_H__
+
+#define DMA_ADDR_INVALID (~((dma_addr_t)0))
+
+/* Maximum packet size for different speeds */
+#define EP0_LS_MPS 8
+#define EP_LS_MPS 8
+
+#define EP0_FS_MPS 64
+#define EP_FS_MPS 64
+
+#define EP0_HS_MPS 64
+#define EP_HS_MPS 512
+
+#define EP0_SS_MPS 512
+#define EP_SS_MPS 1024
+
+#define EXYNOS_USB3_EPS 9
+
+/* Has to be multiple of four */
+#define EXYNOS_USB3_EVENT_BUFF_WSIZE 256
+#define EXYNOS_USB3_EVENT_BUFF_BSIZE (EXYNOS_USB3_EVENT_BUFF_WSIZE << 2)
+
+#define EXYNOS_USB3_CTRL_BUFF_SIZE 8
+#define EXYNOS_USB3_EP0_BUFF_SIZE 512
+
+#define EXYNOS_USB3_U1_DEV_EXIT_LAT 0
+#define EXYNOS_USB3_U2_DEV_EXIT_LAT 0x20
+
+/* Device registers */
+#define EXYNOS_USB3_DCFG 0xC700
+#define EXYNOS_USB3_DCFG_IgnoreStreamPP (1 << 23)
+#define EXYNOS_USB3_DCFG_LPMCap (1 << 22)
+#define EXYNOS_USB3_DCFG_NumP_MASK (0x1f << 17)
+#define EXYNOS_USB3_DCFG_NumP_SHIFT 17
+#define EXYNOS_USB3_DCFG_NumP(_x) ((_x) << 17)
+#define EXYNOS_USB3_DCFG_IntrNum_MASK (0x1f << 12)
+#define EXYNOS_USB3_DCFG_IntrNum_SHIFT 12
+#define EXYNOS_USB3_DCFG_IntrNum(_x) (0x1f << 12)
+#define EXYNOS_USB3_DCFG_PerFrInt_MASK (0x3 << 10)
+#define EXYNOS_USB3_DCFG_PerFrInt_SHIFT 10
+#define EXYNOS_USB3_DCFG_PerFrInt(_x) ((_x) << 10)
+#define EXYNOS_USB3_DCFG_DevAddr_MASK (0x7f << 3)
+#define EXYNOS_USB3_DCFG_DevAddr_SHIFT 3
+#define EXYNOS_USB3_DCFG_DevAddr(_x) ((_x) << 3)
+#define EXYNOS_USB3_DCFG_DevSpd_MASK (0x7 << 0)
+#define EXYNOS_USB3_DCFG_DevSpd_SHIFT 0
+#define EXYNOS_USB3_DCFG_DevSpd(_x) ((_x) << 0)
+
+#define EXYNOS_USB3_DCTL 0xC704
+#define EXYNOS_USB3_DCTL_Run_Stop (1 << 31)
+#define EXYNOS_USB3_DCTL_CSftRst (1 << 30)
+#define EXYNOS_USB3_DCTL_LSftRst (1 << 29)
+#define EXYNOS_USB3_DCTL_HIRD_Thres_MASK (0x1f << 24)
+#define EXYNOS_USB3_DCTL_HIRD_Thres_SHIFT 24
+#define EXYNOS_USB3_DCTL_HIRD_Thres(_x) ((_x) << 24)
+#define EXYNOS_USB3_DCTL_AppL1Res (1 << 23)
+#define EXYNOS_USB3_DCTL_TrgtULSt_MASK (0xf << 17)
+#define EXYNOS_USB3_DCTL_TrgtULSt_SHIFT 17
+#define EXYNOS_USB3_DCTL_TrgtULSt(_x) ((_x) << 17)
+#define EXYNOS_USB3_DCTL_InitU2Ena (1 << 12)
+#define EXYNOS_USB3_DCTL_AcceptU2Ena (1 << 11)
+#define EXYNOS_USB3_DCTL_InitU1Ena (1 << 10)
+#define EXYNOS_USB3_DCTL_AcceptU1Ena (1 << 9)
+#define EXYNOS_USB3_DCTL_ULStChngReq_MASK (0xf << 5)
+#define EXYNOS_USB3_DCTL_ULStChngReq_SHIFT 5
+#define EXYNOS_USB3_DCTL_ULStChngReq(_x) ((_x) << 5)
+#define EXYNOS_USB3_DCTL_TstCtl_MASK (0xf << 1)
+#define EXYNOS_USB3_DCTL_TstCtl_SHIFT 1
+#define EXYNOS_USB3_DCTL_TstCtl(_x) ((_x) << 1)
+
+#define EXYNOS_USB3_DEVTEN 0xC708
+#define EXYNOS_USB3_DEVTEN_VndrDevTstRcvedEn (1 << 12)
+#define EXYNOS_USB3_DEVTEN_EvntOverflowEn (1 << 11)
+#define EXYNOS_USB3_DEVTEN_CmdCmpltEn (1 << 10)
+#define EXYNOS_USB3_DEVTEN_ErrticErrEn (1 << 9)
+#define EXYNOS_USB3_DEVTEN_SofEn (1 << 7)
+#define EXYNOS_USB3_DEVTEN_EOPFEn (1 << 6)
+#define EXYNOS_USB3_DEVTEN_WkUpEvtEn (1 << 4)
+#define EXYNOS_USB3_DEVTEN_ULStCngEn (1 << 3)
+#define EXYNOS_USB3_DEVTEN_ConnectDoneEn (1 << 2)
+#define EXYNOS_USB3_DEVTEN_USBRstEn (1 << 1)
+#define EXYNOS_USB3_DEVTEN_DisconnEvtEn (1 << 0)
+
+#define EXYNOS_USB3_DSTS 0xC70C
+#define EXYNOS_USB3_DSTS_PwrUpReq (1 << 24)
+#define EXYNOS_USB3_DSTS_CoreIdle (1 << 23)
+#define EXYNOS_USB3_DSTS_DevCtrlHlt (1 << 22)
+#define EXYNOS_USB3_DSTS_USBLnkSt_MASK (0xf << 18)
+#define EXYNOS_USB3_DSTS_USBLnkSt_SHIFT 18
+#define EXYNOS_USB3_DSTS_USBLnkSt(_x) ((_x) << 18)
+#define EXYNOS_USB3_LnkSt_LPBK 0xb
+#define EXYNOS_USB3_LnkSt_CMPLY 0xa
+#define EXYNOS_USB3_LnkSt_HRESET 0x9
+#define EXYNOS_USB3_LnkSt_RECOV 0x8
+#define EXYNOS_USB3_LnkSt_POLL 0x7
+#define EXYNOS_USB3_LnkSt_SS_INACT 0x6
+#define EXYNOS_USB3_LnkSt_RX_DET 0x5
+#define EXYNOS_USB3_LnkSt_SS_DIS 0x4
+#define EXYNOS_USB3_LnkSt_U3 0x3
+#define EXYNOS_USB3_LnkSt_U2 0x2
+#define EXYNOS_USB3_LnkSt_U1 0x1
+#define EXYNOS_USB3_LnkSt_U0 0x0
+#define EXYNOS_USB3_DSTS_RxFIFOEmpty (1 << 17)
+#define EXYNOS_USB3_DSTS_SOFFN_MASK (0x3fff << 3)
+#define EXYNOS_USB3_DSTS_SOFFN_SHIFT 3
+#define EXYNOS_USB3_DSTS_SOFFN(_x) ((_x) << 3)
+#define EXYNOS_USB3_DSTS_ConnectSpd_MASK (0x7 << 0)
+#define EXYNOS_USB3_DSTS_ConnectSpd_SHIFT 0
+#define EXYNOS_USB3_DSTS_ConnectSpd(_x) ((_x) << 0)
+
+#define EXYNOS_USB3_DGCMDPAR 0xC710
+
+#define EXYNOS_USB3_DGCMD 0xC714
+#define EXYNOS_USB3_DGCMD_CmdStatus (1 << 15)
+#define EXYNOS_USB3_DGCMD_CmdAct (1 << 10)
+#define EXYNOS_USB3_DGCMD_CmdIOC (1 << 8)
+#define EXYNOS_USB3_DGCMD_CmdTyp_MASK (0xff << 0)
+#define EXYNOS_USB3_DGCMD_CmdTyp_SHIFT 0
+#define EXYNOS_USB3_DGCMD_CmdTyp(_x) ((_x) << 0)
+/* Device generic commands */
+#define EXYNOS_USB3_DGCMD_CmdTyp_SetPerParams 0x2
+
+#define EXYNOS_USB3_DALEPENA 0xC720
+
+#define EXYNOS_USB3_DEPCMDPAR2(_a) (0xC800 + ((_a) * 0x10))
+
+#define EXYNOS_USB3_DEPCMDPAR1(_a) (0xC804 + ((_a) * 0x10))
+/* DEPCFG command parameter 1 */
+#define EXYNOS_USB3_DEPCMDPAR1x_FIFO_based (1 << 31)
+#define EXYNOS_USB3_DEPCMDPAR1x_BULK_based (1 << 30)
+#define EXYNOS_USB3_DEPCMDPAR1x_EpNum_MASK (0xf << 26)
+#define EXYNOS_USB3_DEPCMDPAR1x_EpNum_SHIFT 26
+#define EXYNOS_USB3_DEPCMDPAR1x_EpNum(_x) ((_x) << 26)
+#define EXYNOS_USB3_DEPCMDPAR1x_EpDir (1 << 25)
+#define EXYNOS_USB3_DEPCMDPAR1x_StrmCap (1 << 24)
+#define EXYNOS_USB3_DEPCMDPAR1x_bInterval_m1_MASK (0xff << 16)
+#define EXYNOS_USB3_DEPCMDPAR1x_bInterval_m1_SHIFT 16
+#define EXYNOS_USB3_DEPCMDPAR1x_bInterval_m1(_x) ((_x) << 16)
+#define EXYNOS_USB3_DEPCMDPAR1x_StreamEvtEn (1 << 13)
+#define EXYNOS_USB3_DEPCMDPAR1x_RxTxfifoEvtEn (1 << 11)
+#define EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn (1 << 10)
+#define EXYNOS_USB3_DEPCMDPAR1x_XferInProgEn (1 << 9)
+#define EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn (1 << 8)
+#define EXYNOS_USB3_DEPCMDPAR1x_IntrNum_MASK (0x1f << 0)
+#define EXYNOS_USB3_DEPCMDPAR1x_IntrNum_SHIFT 0
+#define EXYNOS_USB3_DEPCMDPAR1x_IntrNum(_x) ((_x) << 0)
+
+#define EXYNOS_USB3_DEPCMDPAR0(_a) (0xC808 + ((_a) * 0x10))
+/* DEPCFG command parameter 0 */
+#define EXYNOS_USB3_DEPCMDPAR0x_IgnrSeqNum (1 << 31)
+#define EXYNOS_USB3_DEPCMDPAR0x_DataSeqNum_MASK (0x1f << 26)
+#define EXYNOS_USB3_DEPCMDPAR0x_DataSeqNum_SHIFT 26
+#define EXYNOS_USB3_DEPCMDPAR0x_DataSeqNum(_x) ((_x) << 26)
+#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz_MASK (0xf << 22)
+#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz_SHIFT 22
+#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz(_x) ((_x) << 22)
+#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum_MASK (0x1f << 17)
+#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum_SHIFT 17
+#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum(_x) ((_x) << 17)
+#define EXYNOS_USB3_DEPCMDPAR0x_MPS_MASK (0x7ff << 3)
+#define EXYNOS_USB3_DEPCMDPAR0x_MPS_SHIFT 3
+#define EXYNOS_USB3_DEPCMDPAR0x_MPS(_x) ((_x) << 3)
+#define EXYNOS_USB3_DEPCMDPAR0x_EPType_MASK (0x3 << 1)
+#define EXYNOS_USB3_DEPCMDPAR0x_EPType_SHIFT 1
+#define EXYNOS_USB3_DEPCMDPAR0x_EPType(_x) ((_x) << 1)
+/* DEPXFERCFG command parameter 0 */
+#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes_MASK (0xff << 0)
+#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes_SHIFT 0
+#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(_x) ((_x) << 0)
+
+#define EXYNOS_USB3_DEPCMD(_a) (0xC80C + ((_a) * 0x10))
+#define EXYNOS_USB3_DEPCMDx_CommandParam_MASK (0xffff << 16)
+#define EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT 16
+#define EXYNOS_USB3_DEPCMDx_CommandParam(_x) ((_x) << 16)
+#define EXYNOS_USB3_DEPCMDx_EventParam_MASK (0xffff << 16)
+#define EXYNOS_USB3_DEPCMDx_EventParam_SHIFT 16
+#define EXYNOS_USB3_DEPCMDx_EventParam(_x) ((_x) << 16)
+#define EXYNOS_USB3_DEPCMDx_XferRscIdx_LIMIT 0x7f
+#define EXYNOS_USB3_DEPCMDx_CmdStatus_MASK (0xf << 12)
+#define EXYNOS_USB3_DEPCMDx_CmdStatus_SHIFT 12
+#define EXYNOS_USB3_DEPCMDx_CmdStatus(_x) ((_x) << 12)
+#define EXYNOS_USB3_DEPCMDx_HiPri_ForceRM (1 << 11)
+#define EXYNOS_USB3_DEPCMDx_CmdAct (1 << 10)
+#define EXYNOS_USB3_DEPCMDx_CmdIOC (1 << 8)
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_MASK (0xf << 0)
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_SHIFT 0
+#define EXYNOS_USB3_DEPCMDx_CmdTyp(_x) ((_x) << 0)
+/* Physical Endpoint commands */
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG 0x9
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER 0x8
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPUPDXFER 0x7
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTRTXFER 0x6
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCSTALL 0x5
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL 0x4
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPGETDSEQ 0x3
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG 0x2
+#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG 0x1
+
+/* Transfer Request Block */
+#define EXYNOS_USB3_TRB_TRBSTS_MASK (0xf << 28)
+#define EXYNOS_USB3_TRB_TRBSTS_SHIFT 28
+#define EXYNOS_USB3_TRB_TRBSTS(_x) ((_x) << 28)
+#define EXYNOS_USB3_TRB_PCM1_MASK (0x3 << 24)
+#define EXYNOS_USB3_TRB_PCM1_SHIFT 24
+#define EXYNOS_USB3_TRB_PCM1(_x) ((_x) << 24)
+#define EXYNOS_USB3_TRB_BUFSIZ_MASK (0xffffff << 0)
+#define EXYNOS_USB3_TRB_BUFSIZ_SHIFT 0
+#define EXYNOS_USB3_TRB_BUFSIZ(_x) ((_x) << 0)
+#define EXYNOS_USB3_TRB_StreamID_SOFNumber_MASK (0xffff << 14)
+#define EXYNOS_USB3_TRB_StreamID_SOFNumber_SHIFT 14
+#define EXYNOS_USB3_TRB_StreamID_SOFNumber(_x) ((_x) << 14)
+#define EXYNOS_USB3_TRB_IOC (1 << 11)
+#define EXYNOS_USB3_TRB_ISP_IMI (1 << 10)
+#define EXYNOS_USB3_TRB_TRBCTL_MASK (0x3f << 4)
+#define EXYNOS_USB3_TRB_TRBCTL_SHIFT 4
+#define EXYNOS_USB3_TRB_TRBCTL(_x) ((_x) << 4)
+#define EXYNOS_USB3_TRB_CSP (1 << 3)
+#define EXYNOS_USB3_TRB_CHN (1 << 2)
+#define EXYNOS_USB3_TRB_LST (1 << 1)
+#define EXYNOS_USB3_TRB_HWO (1 << 0)
+/*****************************************************************************/
+
+#define call_gadget(_udc, _entry) do { \
+ if ((_udc)->gadget.speed != USB_SPEED_UNKNOWN && \
+ (_udc)->driver && (_udc)->driver->_entry) \
+ (_udc)->driver->_entry(&(_udc)->gadget); \
+} while (0)
+
+#include <linux/wakelock.h>
+
+/**
+ * States of EP0
+ */
+enum ctrl_ep_state {
+ EP0_UNCONNECTED,
+ EP0_SETUP_PHASE,
+ EP0_DATA_PHASE,
+ EP0_WAIT_NRDY,
+ EP0_STATUS_PHASE,
+ EP0_STALL,
+};
+
+/**
+ * Types of TRB
+ */
+enum trb_control {
+ NORMAL = 1,
+ CONTROL_SETUP,
+ CONTROL_STATUS_2,
+ CONTROL_STATUS_3,
+ CONTROL_DATA,
+ ISOCHRONOUS_FIRST,
+ ISOCHRONOUS,
+ LINK_TRB,
+};
+
+/**
+ * struct exynos_ss_udc_trb - transfer request block (TRB)
+ * @buff_ptr_low: Buffer pointer low.
+ * @buff_ptr_high: Buffer pointer high.
+ * @param1: TRB parameter 1.
+ * @param2: TRB parameter 2.
+ */
+struct exynos_ss_udc_trb {
+ u32 buff_ptr_low;
+ u32 buff_ptr_high;
+ u32 param1;
+ u32 param2;
+};
+
+/**
+ * struct exynos_ss_udc_ep_command - endpoint command.
+ * @queue: The list of commands for the endpoint.
+ * @ep: physical endpoint number.
+ * @param0: Command parameter 0.
+ * @param1: Command parameter 1.
+ * @param2: Command parameter 2.
+ * @cmdtype: Command to issue.
+ * @cmdflags: Command flags.
+ */
+struct exynos_ss_udc_ep_command {
+ struct list_head queue;
+
+ int ep;
+ u32 param0;
+ u32 param1;
+ u32 param2;
+ u32 cmdtyp;
+ u32 cmdflags;
+};
+
+/**
+ * struct exynos_ss_udc_req - data transfer request
+ * @req: The USB gadget request.
+ * @queue: The list of requests for the endpoint this is queued for.
+ * @mapped: DMA buffer for this request has been mapped via dma_map_single().
+ */
+struct exynos_ss_udc_req {
+ struct usb_request req;
+ struct list_head queue;
+ unsigned char mapped;
+};
+
+/**
+ * struct exynos_ss_udc_ep - driver endpoint definition.
+ * @ep: The gadget layer representation of the endpoint.
+ * @queue: Queue of requests for this endpoint.
+ * @cmd_queue: Queue of commands for this endpoint.
+ * @parent: Reference back to the parent device structure.
+ * @req: The current request that the endpoint is processing. This is
+ * used to indicate an request has been loaded onto the endpoint
+ * and has yet to be completed (maybe due to data move, or simply
+ * awaiting an ack from the core all the data has been completed).
+ * @lock: State lock to protect contents of endpoint.
+ * @trb: Transfer Request Block.
+ * @trb_dma: Transfer Request Block DMA address.
+ * @tri: Transfer resource index.
+ * @epnum: The USB endpoint number.
+ * @type: The endpoint type.
+ * @dir_in: Set to true if this endpoint is of the IN direction, which
+ * means that it is sending data to the Host.
+ * @halted: Set if the endpoint has been halted.
+ * @enabled: Set to true if endpoint is enabled.
+ * @wedged: Set if the endpoint has been wedged.
+ * @not_ready: Set to true if a command for the endpoint hasn't completed
+ * during timeout interval.
+ * @name: The driver generated name for the endpoint.
+ *
+ * This is the driver's state for each registered enpoint, allowing it
+ * to keep track of transactions that need doing. Each endpoint has a
+ * lock to protect the state, to try and avoid using an overall lock
+ * for the host controller as much as possible.
+ */
+struct exynos_ss_udc_ep {
+ struct usb_ep ep;
+ struct list_head queue;
+ struct list_head cmd_queue;
+ struct exynos_ss_udc *parent;
+ struct exynos_ss_udc_req *req;
+
+ spinlock_t lock;
+
+ struct exynos_ss_udc_trb *trb;
+ dma_addr_t trb_dma;
+ u8 tri;
+
+ unsigned char epnum;
+ unsigned int type;
+ unsigned int dir_in:1;
+ unsigned int halted:1;
+ unsigned int enabled:1;
+ unsigned int wedged:1;
+ unsigned int not_ready:1;
+
+ char name[10];
+};
+
+/**
+ * struct exynos_ss_udc - driver state.
+ * @dev: The parent device supplied to the probe function
+ * @driver: USB gadget driver
+ * @plat: The platform specific configuration data.
+ * @state: The device USB state.
+ * @regs: The memory area mapped for accessing registers.
+ * @irq: The IRQ number we are using.
+ * @clk: The clock we are using.
+ * @release: The core release number.
+ * @event_buff: Event buffer.
+ * @event_buff_dma: Event buffer DMA address.
+ * @event_indx: Event buffer index.
+ * @eps_enabled: Set if new configuration for physical endpoints > 1 started.
+ * @ep0_state: State of EP0.
+ * @ep0_three_stage: Set if control transfer has three stages.
+ * @ep0_buff: Buffer for EP0 data.
+ * @ep0_buff_dma: EP0 data buffer DMA address.
+ * @ctrl_buff: Buffer for EP0 control requests.
+ * @ctrl_buff_dma: EP0 control request buffer DMA address.
+ * @ctrl_req: Request for EP0 control packets.
+ * @gadget: Represents USB slave device.
+ * @eps: The endpoints being supplied to the gadget framework
+ */
+struct exynos_ss_udc {
+ struct device *dev;
+ struct usb_gadget_driver *driver;
+ struct exynos_ss_udc_plat *plat;
+
+ enum usb_device_state state;
+
+ void __iomem *regs;
+ int irq;
+ struct clk *clk;
+
+ u16 release;
+
+ u32 *event_buff;
+ dma_addr_t event_buff_dma;
+ u32 event_indx;
+
+ bool eps_enabled;
+ enum ctrl_ep_state ep0_state;
+ int ep0_three_stage;
+
+ u8 *ep0_buff;
+ dma_addr_t ep0_buff_dma;
+ u8 *ctrl_buff;
+ dma_addr_t ctrl_buff_dma;
+ struct usb_request *ctrl_req;
+
+ struct usb_gadget gadget;
+ struct exynos_ss_udc_ep eps[EXYNOS_USB3_EPS];
+ struct wake_lock usbd_wake_lock;
+};
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+extern void samsung_cable_check_status(int flag);
+#endif
+
+/* conversion functions */
+static inline struct exynos_ss_udc_req *our_req(struct usb_request *req)
+{
+ return container_of(req, struct exynos_ss_udc_req, req);
+}
+
+static inline struct exynos_ss_udc_ep *our_ep(struct usb_ep *ep)
+{
+ return container_of(ep, struct exynos_ss_udc_ep, ep);
+}
+
+static inline void __orr32(void __iomem *ptr, u32 val)
+{
+ writel(readl(ptr) | val, ptr);
+}
+
+static inline void __bic32(void __iomem *ptr, u32 val)
+{
+ writel(readl(ptr) & ~val, ptr);
+}
+
+static inline int get_phys_epnum(struct exynos_ss_udc_ep *udc_ep)
+{
+ return udc_ep->epnum * 2 + udc_ep->dir_in;
+}
+
+static inline int get_usb_epnum(int index)
+{
+ return index >> 1;
+}
+
+#endif /* __EXYNOS_SS_UDC_H__ */
diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c
new file mode 100644
index 0000000..dfe3e51
--- /dev/null
+++ b/drivers/usb/gadget/f_accessory.c
@@ -0,0 +1,792 @@
+/*
+ * Gadget Function Driver for Android USB accessories
+ *
+ * Copyright (C) 2011 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/f_accessory.h>
+
+#define BULK_BUFFER_SIZE 16384
+#define ACC_STRING_SIZE 256
+
+#define PROTOCOL_VERSION 1
+
+/* String IDs */
+#define INTERFACE_STRING_INDEX 0
+
+/* number of tx and rx requests to allocate */
+#define TX_REQ_MAX 4
+#define RX_REQ_MAX 2
+
+struct acc_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+
+ /* set to 1 when we connect */
+ int online:1;
+ /* Set to 1 when we disconnect.
+ * Not cleared until our file is closed.
+ */
+ int disconnected:1;
+
+ /* strings sent by the host */
+ char manufacturer[ACC_STRING_SIZE];
+ char model[ACC_STRING_SIZE];
+ char description[ACC_STRING_SIZE];
+ char version[ACC_STRING_SIZE];
+ char uri[ACC_STRING_SIZE];
+ char serial[ACC_STRING_SIZE];
+
+ /* for acc_complete_set_string */
+ int string_index;
+
+ /* set to 1 if we have a pending start request */
+ int start_requested;
+
+ /* synchronize access to our device file */
+ atomic_t open_excl;
+
+ struct list_head tx_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ struct usb_request *rx_req[RX_REQ_MAX];
+ int rx_done;
+ struct delayed_work work;
+};
+
+static struct usb_interface_descriptor acc_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = 0,
+};
+
+static struct usb_endpoint_descriptor acc_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor acc_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor acc_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor acc_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_acc_descs[] = {
+ (struct usb_descriptor_header *) &acc_interface_desc,
+ (struct usb_descriptor_header *) &acc_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &acc_fullspeed_out_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_acc_descs[] = {
+ (struct usb_descriptor_header *) &acc_interface_desc,
+ (struct usb_descriptor_header *) &acc_highspeed_in_desc,
+ (struct usb_descriptor_header *) &acc_highspeed_out_desc,
+ NULL,
+};
+
+static struct usb_string acc_string_defs[] = {
+ [INTERFACE_STRING_INDEX].s = "Android Accessory Interface",
+ { }, /* end of list */
+};
+
+static struct usb_gadget_strings acc_string_table = {
+ .language = 0x0409, /* en-US */
+ .strings = acc_string_defs,
+};
+
+static struct usb_gadget_strings *acc_strings[] = {
+ &acc_string_table,
+ NULL,
+};
+
+/* temporary variable used between acc_open() and acc_gadget_bind() */
+static struct acc_dev *_acc_dev;
+
+static inline struct acc_dev *func_to_dev(struct usb_function *f)
+{
+ return container_of(f, struct acc_dev, function);
+}
+
+static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void acc_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+/* add a request to the tail of a list */
+static void req_put(struct acc_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void acc_set_disconnected(struct acc_dev *dev)
+{
+ dev->online = 0;
+ dev->disconnected = 1;
+}
+
+static void acc_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct acc_dev *dev = _acc_dev;
+
+ if (req->status != 0)
+ acc_set_disconnected(dev);
+
+ req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void acc_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct acc_dev *dev = _acc_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ acc_set_disconnected(dev);
+
+ wake_up(&dev->read_wq);
+}
+
+static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req)
+{
+ struct acc_dev *dev = ep->driver_data;
+ char *string_dest = NULL;
+ int length = req->actual;
+
+ if (req->status != 0) {
+ pr_err("acc_complete_set_string, err %d\n", req->status);
+ return;
+ }
+
+ switch (dev->string_index) {
+ case ACCESSORY_STRING_MANUFACTURER:
+ string_dest = dev->manufacturer;
+ break;
+ case ACCESSORY_STRING_MODEL:
+ string_dest = dev->model;
+ break;
+ case ACCESSORY_STRING_DESCRIPTION:
+ string_dest = dev->description;
+ break;
+ case ACCESSORY_STRING_VERSION:
+ string_dest = dev->version;
+ break;
+ case ACCESSORY_STRING_URI:
+ string_dest = dev->uri;
+ break;
+ case ACCESSORY_STRING_SERIAL:
+ string_dest = dev->serial;
+ break;
+ }
+ if (string_dest) {
+ unsigned long flags;
+
+ if (length >= ACC_STRING_SIZE)
+ length = ACC_STRING_SIZE - 1;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ memcpy(string_dest, req->buf, length);
+ /* ensure zero termination */
+ string_dest[length] = 0;
+ spin_unlock_irqrestore(&dev->lock, flags);
+ } else {
+ pr_err("unknown accessory string index %d\n",
+ dev->string_index);
+ }
+}
+
+static int create_bulk_endpoints(struct acc_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ /* now allocate requests for our endpoints */
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = acc_complete_in;
+ req_put(dev, &dev->tx_idle, req);
+ }
+ for (i = 0; i < RX_REQ_MAX; i++) {
+ req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = acc_complete_out;
+ dev->rx_req[i] = req;
+ }
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "acc_bind() could not allocate requests\n");
+ while ((req = req_get(dev, &dev->tx_idle)))
+ acc_request_free(req, dev->ep_in);
+ for (i = 0; i < RX_REQ_MAX; i++)
+ acc_request_free(dev->rx_req[i], dev->ep_out);
+ return -1;
+}
+
+static ssize_t acc_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct acc_dev *dev = fp->private_data;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret = 0;
+
+ pr_debug("acc_read(%d)\n", count);
+
+ if (dev->disconnected)
+ return -ENODEV;
+
+ if (count > BULK_BUFFER_SIZE)
+ count = BULK_BUFFER_SIZE;
+
+ /* we will block until we're online */
+ pr_debug("acc_read: waiting for online\n");
+ ret = wait_event_interruptible(dev->read_wq, dev->online);
+ if (ret < 0) {
+ r = ret;
+ goto done;
+ }
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req[0];
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ goto done;
+ } else {
+ pr_debug("rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ r = ret;
+ usb_ep_dequeue(dev->ep_out, req);
+ goto done;
+ }
+ if (dev->online) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ pr_debug("rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ r = xfer;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+ } else
+ r = -EIO;
+
+done:
+ pr_debug("acc_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t acc_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct acc_dev *dev = fp->private_data;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+ pr_debug("acc_write(%d)\n", count);
+
+ if (!dev->online || dev->disconnected)
+ return -ENODEV;
+
+ while (count > 0) {
+ if (!dev->online) {
+ pr_debug("acc_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = req_get(dev, &dev->tx_idle)) || !dev->online));
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > BULK_BUFFER_SIZE)
+ xfer = BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ if (copy_from_user(req->buf, buf, xfer)) {
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ pr_debug("acc_write: xfer error %d\n", ret);
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ req_put(dev, &dev->tx_idle, req);
+
+ pr_debug("acc_write returning %d\n", r);
+ return r;
+}
+
+static long acc_ioctl(struct file *fp, unsigned code, unsigned long value)
+{
+ struct acc_dev *dev = fp->private_data;
+ char *src = NULL;
+ int ret;
+
+ switch (code) {
+ case ACCESSORY_GET_STRING_MANUFACTURER:
+ src = dev->manufacturer;
+ break;
+ case ACCESSORY_GET_STRING_MODEL:
+ src = dev->model;
+ break;
+ case ACCESSORY_GET_STRING_DESCRIPTION:
+ src = dev->description;
+ break;
+ case ACCESSORY_GET_STRING_VERSION:
+ src = dev->version;
+ break;
+ case ACCESSORY_GET_STRING_URI:
+ src = dev->uri;
+ break;
+ case ACCESSORY_GET_STRING_SERIAL:
+ src = dev->serial;
+ break;
+ case ACCESSORY_IS_START_REQUESTED:
+ return dev->start_requested;
+ }
+ if (!src)
+ return -EINVAL;
+
+ ret = strlen(src) + 1;
+ if (copy_to_user((void __user *)value, src, ret))
+ ret = -EFAULT;
+ return ret;
+}
+
+static int acc_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "acc_open\n");
+ if (atomic_xchg(&_acc_dev->open_excl, 1))
+ return -EBUSY;
+
+ _acc_dev->disconnected = 0;
+ fp->private_data = _acc_dev;
+ return 0;
+}
+
+static int acc_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "acc_release\n");
+
+ WARN_ON(!atomic_xchg(&_acc_dev->open_excl, 0));
+ _acc_dev->disconnected = 0;
+ return 0;
+}
+
+/* file operations for /dev/acc_usb */
+static const struct file_operations acc_fops = {
+ .owner = THIS_MODULE,
+ .read = acc_read,
+ .write = acc_write,
+ .unlocked_ioctl = acc_ioctl,
+ .open = acc_open,
+ .release = acc_release,
+};
+
+static struct miscdevice acc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "usb_accessory",
+ .fops = &acc_fops,
+};
+
+
+static int acc_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct acc_dev *dev = _acc_dev;
+ int value = -EOPNOTSUPP;
+ u8 b_requestType = ctrl->bRequestType;
+ u8 b_request = ctrl->bRequest;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+/*
+ printk(KERN_INFO "acc_ctrlrequest "
+ "%02x.%02x v%04x i%04x l%u\n",
+ b_requestType, b_request,
+ w_value, w_index, w_length);
+*/
+
+ if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) {
+ if (b_request == ACCESSORY_START) {
+ dev->start_requested = 1;
+ schedule_delayed_work(
+ &dev->work, msecs_to_jiffies(5));
+ value = 0;
+ } else if (b_request == ACCESSORY_SEND_STRING) {
+ dev->string_index = w_index;
+ cdev->gadget->ep0->driver_data = dev;
+ cdev->req->complete = acc_complete_set_string;
+ value = w_length;
+ }
+ } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) {
+ if (b_request == ACCESSORY_GET_PROTOCOL) {
+ *((u16 *)cdev->req->buf) = PROTOCOL_VERSION;
+ value = sizeof(u16);
+
+ /* clear any strings left over from a previous session */
+ memset(dev->manufacturer, 0, sizeof(dev->manufacturer));
+ memset(dev->model, 0, sizeof(dev->model));
+ memset(dev->description, 0, sizeof(dev->description));
+ memset(dev->version, 0, sizeof(dev->version));
+ memset(dev->uri, 0, sizeof(dev->uri));
+ memset(dev->serial, 0, sizeof(dev->serial));
+ dev->start_requested = 0;
+ }
+ }
+
+ if (value >= 0) {
+ cdev->req->zero = 0;
+ cdev->req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (value < 0)
+ ERROR(cdev, "%s setup response queue error\n",
+ __func__);
+ }
+
+ if (value == -EOPNOTSUPP)
+ VDBG(cdev,
+ "unknown class-specific control req "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ return value;
+}
+
+static int
+acc_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct acc_dev *dev = func_to_dev(f);
+ int id;
+ int ret;
+
+ DBG(cdev, "acc_function_bind dev: %p\n", dev);
+
+ dev->start_requested = 0;
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ acc_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc,
+ &acc_fullspeed_out_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ acc_highspeed_in_desc.bEndpointAddress =
+ acc_fullspeed_in_desc.bEndpointAddress;
+ acc_highspeed_out_desc.bEndpointAddress =
+ acc_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+acc_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct acc_dev *dev = func_to_dev(f);
+ struct usb_request *req;
+ int i;
+
+ while ((req = req_get(dev, &dev->tx_idle)))
+ acc_request_free(req, dev->ep_in);
+ for (i = 0; i < RX_REQ_MAX; i++)
+ acc_request_free(dev->rx_req[i], dev->ep_out);
+}
+
+static void acc_work(struct work_struct *data)
+{
+ char *envp[2] = { "ACCESSORY=START", NULL };
+ kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp);
+}
+
+static int acc_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct acc_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt);
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &acc_highspeed_in_desc,
+ &acc_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &acc_highspeed_out_desc,
+ &acc_fullspeed_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+
+ dev->online = 1;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void acc_function_disable(struct usb_function *f)
+{
+ struct acc_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "acc_function_disable\n");
+ acc_set_disconnected(dev);
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int acc_bind_config(struct usb_configuration *c)
+{
+ struct acc_dev *dev = _acc_dev;
+ int ret;
+
+ printk(KERN_INFO "acc_bind_config\n");
+
+ /* allocate a string ID for our interface */
+ if (acc_string_defs[INTERFACE_STRING_INDEX].id == 0) {
+ ret = usb_string_id(c->cdev);
+ if (ret < 0)
+ return ret;
+ acc_string_defs[INTERFACE_STRING_INDEX].id = ret;
+ acc_interface_desc.iInterface = ret;
+ }
+
+ dev->cdev = c->cdev;
+ dev->function.name = "accessory";
+ dev->function.strings = acc_strings,
+ dev->function.descriptors = fs_acc_descs;
+ dev->function.hs_descriptors = hs_acc_descs;
+ dev->function.bind = acc_function_bind;
+ dev->function.unbind = acc_function_unbind;
+ dev->function.set_alt = acc_function_set_alt;
+ dev->function.disable = acc_function_disable;
+
+ return usb_add_function(c, &dev->function);
+}
+
+static int acc_setup(void)
+{
+ struct acc_dev *dev;
+ int ret;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->lock);
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+ atomic_set(&dev->open_excl, 0);
+ INIT_LIST_HEAD(&dev->tx_idle);
+ INIT_DELAYED_WORK(&dev->work, acc_work);
+
+ /* _acc_dev must be set before calling usb_gadget_register_driver */
+ _acc_dev = dev;
+
+ ret = misc_register(&acc_device);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ kfree(dev);
+ printk(KERN_ERR "USB accessory gadget driver failed to initialize\n");
+ return ret;
+}
+
+static void acc_cleanup(void)
+{
+ misc_deregister(&acc_device);
+ kfree(_acc_dev);
+ _acc_dev = NULL;
+}
diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c
index bd6226c..0c3c3ea 100644
--- a/drivers/usb/gadget/f_acm.c
+++ b/drivers/usb/gadget/f_acm.c
@@ -366,6 +366,9 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
* that bit, we should return to that no-flow state.
*/
acm->port_handshake_bits = w_value;
+#ifdef CONFIG_USB_DUN_SUPPORT
+ notify_control_line_state((unsigned long)w_value);
+#endif
break;
default:
@@ -405,10 +408,10 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
usb_ep_disable(acm->notify);
} else {
VDBG(cdev, "init acm ctrl interface %d\n", intf);
- acm->notify_desc = ep_choose(cdev->gadget,
- acm->hs.notify,
- acm->fs.notify);
}
+ acm->notify_desc = ep_choose(cdev->gadget,
+ acm->hs.notify,
+ acm->fs.notify);
usb_ep_enable(acm->notify, acm->notify_desc);
acm->notify->driver_data = acm;
@@ -418,11 +421,11 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
gserial_disconnect(&acm->port);
} else {
DBG(cdev, "activate acm ttyGS%d\n", acm->port_num);
- acm->port.in_desc = ep_choose(cdev->gadget,
- acm->hs.in, acm->fs.in);
- acm->port.out_desc = ep_choose(cdev->gadget,
- acm->hs.out, acm->fs.out);
}
+ acm->port.in_desc = ep_choose(cdev->gadget,
+ acm->hs.in, acm->fs.in);
+ acm->port.out_desc = ep_choose(cdev->gadget,
+ acm->hs.out, acm->fs.out);
gserial_connect(&acm->port, acm->port_num);
} else
@@ -536,6 +539,21 @@ static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req)
acm_notify_serial_state(acm);
}
+#ifdef CONFIG_USB_DUN_SUPPORT
+int acm_notify(void *dev, u16 state)
+{
+ struct f_acm *acm;
+ if (dev) {
+ acm = (struct f_acm *)dev;
+ acm->serial_state = state;
+ acm_notify_serial_state(acm);
+ } else {
+ printk(KERN_DEBUG "usb: %s not ready\n", __func__);
+ return -EAGAIN;
+ }
+ return 0;
+}
+#endif
/* connect == the TTY link is open */
static void acm_connect(struct gserial *port)
@@ -669,6 +687,9 @@ acm_bind(struct usb_configuration *c, struct usb_function *f)
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
acm->port.in->name, acm->port.out->name,
acm->notify->name);
+#ifdef CONFIG_USB_DUN_SUPPORT
+ modem_register(acm);
+#endif
return 0;
fail:
@@ -697,7 +718,11 @@ acm_unbind(struct usb_configuration *c, struct usb_function *f)
usb_free_descriptors(f->hs_descriptors);
usb_free_descriptors(f->descriptors);
gs_free_req(acm->notify, acm->notify_req);
+ kfree(acm->port.func.name);
kfree(acm);
+#ifdef CONFIG_USB_DUN_SUPPORT
+ modem_unregister();
+#endif
}
/* Some controllers can't support CDC ACM ... */
@@ -768,7 +793,11 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num)
acm->port.disconnect = acm_disconnect;
acm->port.send_break = acm_send_break;
- acm->port.func.name = "acm";
+ acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num);
+ if (!acm->port.func.name) {
+ kfree(acm);
+ return -ENOMEM;
+ }
acm->port.func.strings = acm_strings;
/* descriptors are per-instance copies */
acm->port.func.bind = acm_bind;
diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c
new file mode 100644
index 0000000..e4d8b6e
--- /dev/null
+++ b/drivers/usb/gadget/f_adb.c
@@ -0,0 +1,652 @@
+/*
+ * Gadget Driver for Android ADB
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#define ADB_BULK_BUFFER_SIZE 4096
+
+/* number of tx requests to allocate */
+#define TX_REQ_MAX 4
+
+static const char adb_shortname[] = "android_adb";
+
+struct adb_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+
+ int online;
+ int error;
+
+ atomic_t read_excl;
+ atomic_t write_excl;
+ atomic_t open_excl;
+
+ struct list_head tx_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ struct usb_request *rx_req;
+ int rx_done;
+};
+
+static struct usb_interface_descriptor adb_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xFF,
+ .bInterfaceSubClass = 0x42,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor adb_superspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor adb_superspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor adb_superspeed_bulk_comp_desc = {
+ .bLength = sizeof adb_superspeed_bulk_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+};
+
+static struct usb_endpoint_descriptor adb_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor adb_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor adb_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor adb_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_fullspeed_out_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_highspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_highspeed_out_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *ss_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_superspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_superspeed_bulk_comp_desc,
+ (struct usb_descriptor_header *) &adb_superspeed_out_desc,
+ (struct usb_descriptor_header *) &adb_superspeed_bulk_comp_desc,
+ NULL,
+};
+
+
+/* temporary variable used between adb_open() and adb_gadget_bind() */
+static struct adb_dev *_adb_dev;
+
+static inline struct adb_dev *func_to_adb(struct usb_function *f)
+{
+ return container_of(f, struct adb_dev, function);
+}
+
+
+static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void adb_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int adb_lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void adb_unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+void adb_req_put(struct adb_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+struct usb_request *adb_req_get(struct adb_dev *dev, struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void adb_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ if (req->status != 0)
+ dev->error = 1;
+
+ adb_req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void adb_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->error = 1;
+
+ wake_up(&dev->read_wq);
+}
+
+static int adb_create_bulk_endpoints(struct adb_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ /* now allocate requests for our endpoints */
+ req = adb_request_new(dev->ep_out, ADB_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = adb_complete_out;
+ dev->rx_req = req;
+
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = adb_request_new(dev->ep_in, ADB_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = adb_complete_in;
+ adb_req_put(dev, &dev->tx_idle, req);
+ }
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "adb_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t adb_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct adb_dev *dev = fp->private_data;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret;
+
+ pr_debug("adb_read(%d)\n", count);
+ if (!_adb_dev)
+ return -ENODEV;
+
+ if (count > ADB_BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ if (adb_lock(&dev->read_excl))
+ return -EBUSY;
+
+ /* we will block until we're online */
+ while (!(dev->online || dev->error)) {
+ pr_debug("adb_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ (dev->online || dev->error));
+ if (ret < 0) {
+ adb_unlock(&dev->read_excl);
+ return ret;
+ }
+ }
+ if (dev->error) {
+ r = -EIO;
+ goto done;
+ }
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req;
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC);
+ if (ret < 0) {
+ pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret);
+ r = -EIO;
+ dev->error = 1;
+ goto done;
+ } else {
+ pr_debug("rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ dev->error = 1;
+ r = ret;
+ usb_ep_dequeue(dev->ep_out, req);
+ goto done;
+ }
+ if (!dev->error) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ pr_debug("rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ r = xfer;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+
+ } else
+ r = -EIO;
+
+done:
+ adb_unlock(&dev->read_excl);
+ pr_debug("adb_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t adb_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct adb_dev *dev = fp->private_data;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+ if (!_adb_dev)
+ return -ENODEV;
+ pr_debug("adb_write(%d)\n", count);
+
+ if (adb_lock(&dev->write_excl))
+ return -EBUSY;
+
+ while (count > 0) {
+ if (dev->error) {
+ pr_debug("adb_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ (req = adb_req_get(dev, &dev->tx_idle)) || dev->error);
+
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+
+ if (req != 0) {
+ if (count > ADB_BULK_BUFFER_SIZE)
+ xfer = ADB_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ if (copy_from_user(req->buf, buf, xfer)) {
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC);
+ if (ret < 0) {
+ pr_debug("adb_write: xfer error %d\n", ret);
+ dev->error = 1;
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+ }
+
+ if (req)
+ adb_req_put(dev, &dev->tx_idle, req);
+
+ adb_unlock(&dev->write_excl);
+ pr_debug("adb_write returning %d\n", r);
+ return r;
+}
+
+static int adb_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "adb_open\n");
+ if (!_adb_dev)
+ return -ENODEV;
+
+ if (adb_lock(&_adb_dev->open_excl))
+ return -EBUSY;
+
+ fp->private_data = _adb_dev;
+
+ /* clear the error latch */
+ _adb_dev->error = 0;
+
+ return 0;
+}
+
+static int adb_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "adb_release\n");
+ adb_unlock(&_adb_dev->open_excl);
+ return 0;
+}
+
+/* file operations for ADB device /dev/android_adb */
+static struct file_operations adb_fops = {
+ .owner = THIS_MODULE,
+ .read = adb_read,
+ .write = adb_write,
+ .open = adb_open,
+ .release = adb_release,
+};
+
+static struct miscdevice adb_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = adb_shortname,
+ .fops = &adb_fops,
+};
+
+
+
+
+static int
+adb_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct adb_dev *dev = func_to_adb(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "adb_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ adb_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = adb_create_bulk_endpoints(dev, &adb_fullspeed_in_desc,
+ &adb_fullspeed_out_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ adb_highspeed_in_desc.bEndpointAddress =
+ adb_fullspeed_in_desc.bEndpointAddress;
+ adb_highspeed_out_desc.bEndpointAddress =
+ adb_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ /* support super speed hardware */
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ adb_superspeed_in_desc.bEndpointAddress =
+ adb_fullspeed_in_desc.bEndpointAddress;
+ adb_superspeed_out_desc.bEndpointAddress =
+ adb_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+adb_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct adb_dev *dev = func_to_adb(f);
+ struct usb_request *req;
+
+
+ dev->online = 0;
+ dev->error = 1;
+
+ wake_up(&dev->read_wq);
+
+ adb_request_free(dev->rx_req, dev->ep_out);
+ while ((req = adb_req_get(dev, &dev->tx_idle)))
+ adb_request_free(req, dev->ep_in);
+}
+
+static int adb_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct adb_dev *dev = func_to_adb(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt);
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &adb_highspeed_in_desc,
+ &adb_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &adb_highspeed_out_desc,
+ &adb_fullspeed_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ dev->online = 1;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void adb_function_disable(struct usb_function *f)
+{
+ struct adb_dev *dev = func_to_adb(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "adb_function_disable cdev %p\n", cdev);
+ dev->online = 0;
+ dev->error = 1;
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int adb_bind_config(struct usb_configuration *c)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ printk(KERN_INFO "adb_bind_config\n");
+
+ dev->cdev = c->cdev;
+ dev->function.name = "adb";
+ dev->function.descriptors = fs_adb_descs;
+ dev->function.hs_descriptors = hs_adb_descs;
+ dev->function.ss_descriptors = ss_adb_descs;
+ dev->function.bind = adb_function_bind;
+ dev->function.unbind = adb_function_unbind;
+ dev->function.set_alt = adb_function_set_alt;
+ dev->function.disable = adb_function_disable;
+
+ return usb_add_function(c, &dev->function);
+}
+
+static int adb_setup(void)
+{
+ struct adb_dev *dev;
+ int ret;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->lock);
+
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+
+ atomic_set(&dev->open_excl, 0);
+ atomic_set(&dev->read_excl, 0);
+ atomic_set(&dev->write_excl, 0);
+
+ INIT_LIST_HEAD(&dev->tx_idle);
+
+ _adb_dev = dev;
+
+ ret = misc_register(&adb_device);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ kfree(dev);
+ printk(KERN_ERR "adb gadget driver failed to initialize\n");
+ return ret;
+}
+
+static void adb_cleanup(void)
+{
+ misc_deregister(&adb_device);
+
+ kfree(_adb_dev);
+ _adb_dev = NULL;
+}
diff --git a/drivers/usb/gadget/f_dm.c b/drivers/usb/gadget/f_dm.c
new file mode 100644
index 0000000..04eec37
--- /dev/null
+++ b/drivers/usb/gadget/f_dm.c
@@ -0,0 +1,310 @@
+/*
+ * f_dm.c - generic USB serial function driver
+ * modified from f_serial.c and f_diag.c
+ * ttyGS*
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * This software is distributed under the terms of the GNU General
+ * Public License ("GPL") as published by the Free Software Foundation,
+ * either version 2 of that License or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+
+#include "u_serial.h"
+#include "gadget_chips.h"
+
+/*
+ * This function packages a simple "generic serial" port with no real
+ * control mechanisms, just raw data transfer over two bulk endpoints.
+ *
+ * Because it's not standardized, this isn't as interoperable as the
+ * CDC ACM driver. However, for many purposes it's just as functional
+ * if you can arrange appropriate host side drivers.
+ */
+
+struct dm_descs {
+ struct usb_endpoint_descriptor *in;
+ struct usb_endpoint_descriptor *out;
+};
+
+struct f_dm {
+ struct gserial port;
+ u8 data_id;
+ u8 port_num;
+
+ struct dm_descs fs;
+ struct dm_descs hs;
+};
+
+static inline struct f_dm *func_to_dm(struct usb_function *f)
+{
+ return container_of(f, struct f_dm, port.func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* interface descriptor: */
+static struct usb_interface_descriptor dm_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = 0x10,
+ .bInterfaceProtocol = 0x01,
+ /* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor dm_fs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor dm_fs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *dm_fs_function[] = {
+ (struct usb_descriptor_header *) &dm_interface_desc,
+ (struct usb_descriptor_header *) &dm_fs_in_desc,
+ (struct usb_descriptor_header *) &dm_fs_out_desc,
+ NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor dm_hs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor dm_hs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *dm_hs_function[] = {
+ (struct usb_descriptor_header *) &dm_interface_desc,
+ (struct usb_descriptor_header *) &dm_hs_in_desc,
+ (struct usb_descriptor_header *) &dm_hs_out_desc,
+ NULL,
+};
+
+/* string descriptors: */
+#define F_DM_IDX 0
+
+static struct usb_string dm_string_defs[] = {
+ [F_DM_IDX].s = "Samsung Android DM",
+ { /* ZEROES END LIST */ },
+};
+
+static struct usb_gadget_strings dm_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = dm_string_defs,
+};
+
+static struct usb_gadget_strings *dm_strings[] = {
+ &dm_string_table,
+ NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int dm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct f_dm *dm = func_to_dm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int status;
+
+ /* we know alt == 0, so this is an activation or a reset */
+
+ if (dm->port.in->driver_data) {
+ DBG(cdev, "reset generic ttyGS%d\n", dm->port_num);
+ gserial_disconnect(&dm->port);
+ } else {
+ DBG(cdev, "activate generic ttyGS%d\n", dm->port_num);
+ }
+ dm->port.in_desc = ep_choose(cdev->gadget,
+ dm->hs.in, dm->fs.in);
+ dm->port.out_desc = ep_choose(cdev->gadget,
+ dm->hs.out, dm->fs.out);
+ status = gserial_connect(&dm->port, dm->port_num);
+ printk(KERN_DEBUG "usb: %s run generic_connect(%d)", __func__,
+ dm->port_num);
+
+ if (status < 0) {
+ printk(KERN_ERR "fail to activate generic ttyGS%d\n",
+ dm->port_num);
+
+ return status;
+ }
+
+ return 0;
+}
+
+static void dm_disable(struct usb_function *f)
+{
+ struct f_dm *dm = func_to_dm(f);
+
+ printk(KERN_DEBUG "usb: %s generic ttyGS%d deactivated\n", __func__,
+ dm->port_num);
+ gserial_disconnect(&dm->port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* serial function driver setup/binding */
+
+static int
+dm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_dm *dm = func_to_dm(f);
+ int status;
+ struct usb_ep *ep;
+
+ /* allocate instance-specific interface IDs */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ dm->data_id = status;
+ dm_interface_desc.bInterfaceNumber = status;
+
+ status = -ENODEV;
+
+ /* allocate instance-specific endpoints */
+ ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_in_desc);
+ if (!ep)
+ goto fail;
+ dm->port.in = ep;
+ ep->driver_data = cdev; /* claim */
+
+ ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_out_desc);
+ if (!ep)
+ goto fail;
+ dm->port.out = ep;
+ ep->driver_data = cdev; /* claim */
+ printk(KERN_INFO "[%s] in =0x%p , out =0x%p\n", __func__,
+ dm->port.in, dm->port.out);
+
+ /* copy descriptors, and track endpoint copies */
+ f->descriptors = usb_copy_descriptors(dm_fs_function);
+
+ dm->fs.in = usb_find_endpoint(dm_fs_function,
+ f->descriptors, &dm_fs_in_desc);
+ dm->fs.out = usb_find_endpoint(dm_fs_function,
+ f->descriptors, &dm_fs_out_desc);
+
+
+ /* support all relevant hardware speeds... we expect that when
+ * hardware is dual speed, all bulk-capable endpoints work at
+ * both speeds
+ */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ dm_hs_in_desc.bEndpointAddress =
+ dm_fs_in_desc.bEndpointAddress;
+ dm_hs_out_desc.bEndpointAddress =
+ dm_fs_out_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->hs_descriptors = usb_copy_descriptors(dm_hs_function);
+
+ dm->hs.in = usb_find_endpoint(dm_hs_function,
+ f->hs_descriptors, &dm_hs_in_desc);
+ dm->hs.out = usb_find_endpoint(dm_hs_function,
+ f->hs_descriptors, &dm_hs_out_desc);
+ }
+
+ printk(KERN_DEBUG "usb: %s generic ttyGS%d: %s speed IN/%s OUT/%s\n",
+ __func__,
+ dm->port_num,
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ dm->port.in->name, dm->port.out->name);
+ return 0;
+
+fail:
+ /* we might as well release our claims on endpoints */
+ if (dm->port.out)
+ dm->port.out->driver_data = NULL;
+ if (dm->port.in)
+ dm->port.in->driver_data = NULL;
+
+ printk(KERN_ERR "%s: can't bind, err %d\n", f->name, status);
+
+ return status;
+}
+
+static void
+dm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+ kfree(func_to_dm(f));
+ printk(KERN_DEBUG "usb: %s\n", __func__);
+}
+
+/*
+ * dm_bind_config - add a generic serial function to a configuration
+ * @c: the configuration to support the serial instance
+ * @port_num: /dev/ttyGS* port this interface will use
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gserial_setup() with enough ports to
+ * handle all the ones it binds. Caller is also responsible
+ * for calling @gserial_cleanup() before module unload.
+ */
+int dm_bind_config(struct usb_configuration *c, u8 port_num)
+{
+ struct f_dm *dm;
+ int status;
+
+ /* REVISIT might want instance-specific strings to help
+ * distinguish instances ...
+ */
+
+ /* maybe allocate device-global string ID */
+ if (dm_string_defs[F_DM_IDX].id == 0) {
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ dm_string_defs[F_DM_IDX].id = status;
+ }
+
+ /* allocate and initialize one new instance */
+ dm = kzalloc(sizeof *dm, GFP_KERNEL);
+ if (!dm)
+ return -ENOMEM;
+
+ dm->port_num = port_num;
+
+ dm->port.func.name = "dm";
+ dm->port.func.strings = dm_strings;
+ dm->port.func.bind = dm_bind;
+ dm->port.func.unbind = dm_unbind;
+ dm->port.func.set_alt = dm_set_alt;
+ dm->port.func.disable = dm_disable;
+
+ status = usb_add_function(c, &dm->port.func);
+ if (status)
+ kfree(dm);
+ return status;
+}
diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c
index 544257a..84747ae 100644
--- a/drivers/usb/gadget/f_ecm.c
+++ b/drivers/usb/gadget/f_ecm.c
@@ -66,6 +66,7 @@ struct f_ecm {
struct ecm_ep_descs fs;
struct ecm_ep_descs hs;
+ struct ecm_ep_descs ss;
struct usb_ep *notify;
struct usb_endpoint_descriptor *notify_desc;
@@ -86,7 +87,9 @@ static inline struct f_ecm *func_to_ecm(struct usb_function *f)
/* peak (theoretical) bulk transfer rate in bits-per-second */
static inline unsigned ecm_bitrate(struct usb_gadget *g)
{
- if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+ if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
+ return 13 * 1024 * 8 * 1000 * 8;
+ else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
return 13 * 512 * 8 * 1000 * 8;
else
return 19 * 64 * 1 * 1000 * 8;
@@ -274,6 +277,76 @@ static struct usb_descriptor_header *ecm_hs_function[] = {
NULL,
};
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_ecm_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT),
+ .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ecm_intr_comp_desc = {
+ .bLength = sizeof ss_ecm_intr_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 3 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+ .wBytesPerInterval = cpu_to_le16(ECM_STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ss_ecm_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_ecm_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_ecm_bulk_comp_desc = {
+ .bLength = sizeof ss_ecm_bulk_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+};
+
+static struct usb_descriptor_header *ecm_ss_function[] = {
+ /* CDC ECM control descriptors */
+ (struct usb_descriptor_header *) &ecm_control_intf,
+ (struct usb_descriptor_header *) &ecm_header_desc,
+ (struct usb_descriptor_header *) &ecm_union_desc,
+ (struct usb_descriptor_header *) &ecm_desc,
+
+ /* NOTE: status endpoint might need to be removed */
+ (struct usb_descriptor_header *) &ss_ecm_notify_desc,
+ (struct usb_descriptor_header *) &ss_ecm_intr_comp_desc,
+
+ /* data interface, altsettings 0 and 1 */
+ (struct usb_descriptor_header *) &ecm_data_nop_intf,
+ (struct usb_descriptor_header *) &ecm_data_intf,
+ (struct usb_descriptor_header *) &ss_ecm_in_desc,
+ (struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc,
+ (struct usb_descriptor_header *) &ss_ecm_out_desc,
+ (struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc,
+ NULL,
+};
+
/* string descriptors: */
static struct usb_string ecm_string_defs[] = {
@@ -466,7 +539,11 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
usb_ep_disable(ecm->notify);
} else {
VDBG(cdev, "init ecm ctrl %d\n", intf);
- ecm->notify_desc = ep_choose(cdev->gadget,
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER)
+ ecm->notify_desc = ecm->ss.notify;
+ else
+ ecm->notify_desc = ep_choose(cdev->gadget,
ecm->hs.notify,
ecm->fs.notify);
}
@@ -485,10 +562,16 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (!ecm->port.in) {
DBG(cdev, "init ecm\n");
- ecm->port.in = ep_choose(cdev->gadget,
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER) {
+ ecm->port.in = ecm->ss.in;
+ ecm->port.out = ecm->ss.out;
+ } else {
+ ecm->port.in = ep_choose(cdev->gadget,
ecm->hs.in, ecm->fs.in);
- ecm->port.out = ep_choose(cdev->gadget,
+ ecm->port.out = ep_choose(cdev->gadget,
ecm->hs.out, ecm->fs.out);
+ }
}
/* CDC Ethernet only sends data in non-default altsettings.
@@ -697,6 +780,26 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
f->hs_descriptors, &hs_ecm_notify_desc);
}
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ ss_ecm_in_desc.bEndpointAddress =
+ fs_ecm_in_desc.bEndpointAddress;
+ ss_ecm_out_desc.bEndpointAddress =
+ fs_ecm_out_desc.bEndpointAddress;
+ ss_ecm_notify_desc.bEndpointAddress =
+ fs_ecm_notify_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->ss_descriptors = usb_copy_descriptors(ecm_ss_function);
+ if (!f->ss_descriptors)
+ goto fail;
+
+ ecm->ss.in = usb_find_endpoint(ecm_ss_function,
+ f->ss_descriptors, &ss_ecm_in_desc);
+ ecm->ss.out = usb_find_endpoint(ecm_ss_function,
+ f->ss_descriptors, &ss_ecm_out_desc);
+ ecm->ss.notify = usb_find_endpoint(ecm_ss_function,
+ f->ss_descriptors, &ss_ecm_notify_desc);
+ }
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
* until we're activated via set_alt().
@@ -706,6 +809,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
ecm->port.close = ecm_close;
DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
ecm->port.in_ep->name, ecm->port.out_ep->name,
ecm->notify->name);
@@ -714,6 +818,8 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f)
fail:
if (f->descriptors)
usb_free_descriptors(f->descriptors);
+ if (f->hs_descriptors)
+ usb_free_descriptors(f->hs_descriptors);
if (ecm->notify_req) {
kfree(ecm->notify_req->buf);
@@ -740,6 +846,8 @@ ecm_unbind(struct usb_configuration *c, struct usb_function *f)
DBG(c->cdev, "ecm unbind\n");
+ if (gadget_is_superspeed(c->cdev->gadget))
+ usb_free_descriptors(f->ss_descriptors);
if (gadget_is_dualspeed(c->cdev->gadget))
usb_free_descriptors(f->hs_descriptors);
usb_free_descriptors(f->descriptors);
diff --git a/drivers/usb/gadget/f_eem.c b/drivers/usb/gadget/f_eem.c
index b3c3042..3c25cf5 100644
--- a/drivers/usb/gadget/f_eem.c
+++ b/drivers/usb/gadget/f_eem.c
@@ -46,6 +46,7 @@ struct f_eem {
struct eem_ep_descs fs;
struct eem_ep_descs hs;
+ struct eem_ep_descs ss;
};
static inline struct f_eem *func_to_eem(struct usb_function *f)
@@ -123,6 +124,45 @@ static struct usb_descriptor_header *eem_hs_function[] __initdata = {
NULL,
};
+/* super speed support: */
+
+static struct usb_endpoint_descriptor eem_ss_in_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor eem_ss_out_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc __initdata = {
+ .bLength = sizeof eem_ss_bulk_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+};
+
+static struct usb_descriptor_header *eem_ss_function[] __initdata = {
+ /* CDC EEM control descriptors */
+ (struct usb_descriptor_header *) &eem_intf,
+ (struct usb_descriptor_header *) &eem_ss_in_desc,
+ (struct usb_descriptor_header *) &eem_ss_bulk_comp_desc,
+ (struct usb_descriptor_header *) &eem_ss_out_desc,
+ (struct usb_descriptor_header *) &eem_ss_bulk_comp_desc,
+ NULL,
+};
+
/* string descriptors: */
static struct usb_string eem_string_defs[] = {
@@ -178,10 +218,16 @@ static int eem_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (!eem->port.in) {
DBG(cdev, "init eem\n");
- eem->port.in = ep_choose(cdev->gadget,
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER) {
+ eem->port.in = eem->ss.in;
+ eem->port.out = eem->ss.out;
+ } else {
+ eem->port.in = ep_choose(cdev->gadget,
eem->hs.in, eem->fs.in);
- eem->port.out = ep_choose(cdev->gadget,
+ eem->port.out = ep_choose(cdev->gadget,
eem->hs.out, eem->fs.out);
+ }
}
/* zlps should not occur because zero-length EEM packets
@@ -279,7 +325,25 @@ eem_bind(struct usb_configuration *c, struct usb_function *f)
f->hs_descriptors, &eem_hs_out_desc);
}
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ eem_ss_in_desc.bEndpointAddress =
+ eem_fs_in_desc.bEndpointAddress;
+ eem_ss_out_desc.bEndpointAddress =
+ eem_fs_out_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->ss_descriptors = usb_copy_descriptors(eem_ss_function);
+ if (!f->ss_descriptors)
+ goto fail;
+
+ eem->ss.in = usb_find_endpoint(eem_ss_function,
+ f->ss_descriptors, &eem_ss_in_desc);
+ eem->ss.out = usb_find_endpoint(eem_ss_function,
+ f->ss_descriptors, &eem_ss_out_desc);
+ }
+
DBG(cdev, "CDC Ethernet (EEM): %s speed IN/%s OUT/%s\n",
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
eem->port.in_ep->name, eem->port.out_ep->name);
return 0;
@@ -287,6 +351,8 @@ eem_bind(struct usb_configuration *c, struct usb_function *f)
fail:
if (f->descriptors)
usb_free_descriptors(f->descriptors);
+ if (f->hs_descriptors)
+ usb_free_descriptors(f->hs_descriptors);
/* we might as well release our claims on endpoints */
if (eem->port.out)
@@ -306,6 +372,8 @@ eem_unbind(struct usb_configuration *c, struct usb_function *f)
DBG(c->cdev, "eem unbind\n");
+ if (gadget_is_superspeed(c->cdev->gadget))
+ usb_free_descriptors(f->ss_descriptors);
if (gadget_is_dualspeed(c->cdev->gadget))
usb_free_descriptors(f->hs_descriptors);
usb_free_descriptors(f->descriptors);
diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c
index efb58f9..4616b1f 100644
--- a/drivers/usb/gadget/f_mass_storage.c
+++ b/drivers/usb/gadget/f_mass_storage.c
@@ -406,6 +406,11 @@ struct fsg_common {
*/
char inquiry_string[8 + 16 + 4 + 1];
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ char vendor_string[8 + 1];
+ char product_string[16 + 1];
+#endif
+
struct kref ref;
};
@@ -775,8 +780,17 @@ static int do_read(struct fsg_common *common)
/* Carry out the file reads */
amount_left = common->data_size_from_cmnd;
- if (unlikely(amount_left == 0))
+ if (unlikely(amount_left == 0)) {
+ /* Wait for the next buffer to become available */
+ bh = common->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+ bh->inreq->length = 0;
return -EIO; /* No default reply */
+ }
for (;;) {
/*
@@ -793,9 +807,22 @@ static int do_read(struct fsg_common *common)
amount = min((loff_t)amount,
curlun->file_length - file_offset);
partial_page = file_offset & (PAGE_CACHE_SIZE - 1);
- if (partial_page > 0)
- amount = min(amount, (unsigned int)PAGE_CACHE_SIZE -
- partial_page);
+ if (common->gadget->speed != USB_SPEED_SUPER) {
+ if (partial_page > 0)
+ amount = min(amount, (unsigned int)PAGE_CACHE_SIZE -
+ partial_page);
+ }
+
+ if (common->gadget->speed == USB_SPEED_SUPER) {
+ /* The packet should be multiple of MPS, which is 1024. */
+ if (amount < amount_left) {
+ unsigned mod = amount % common->bulk_out_maxpacket;
+ if (mod && (amount - mod)) {
+ // not a multiple of maxpacket
+ amount -= mod;
+ }
+ }
+ }
/* Wait for the next buffer to become available */
bh = common->next_buffhd_to_fill;
@@ -876,7 +903,7 @@ static int do_write(struct fsg_common *common)
int get_some_more;
u32 amount_left_to_req, amount_left_to_write;
loff_t usb_offset, file_offset, file_offset_tmp;
- unsigned int amount;
+ unsigned int amount, diff;
unsigned int partial_page;
ssize_t nwritten;
int rc;
@@ -946,9 +973,12 @@ static int do_write(struct fsg_common *common)
amount = min((loff_t)amount,
curlun->file_length - usb_offset);
partial_page = usb_offset & (PAGE_CACHE_SIZE - 1);
- if (partial_page > 0)
- amount = min(amount,
- (unsigned int)PAGE_CACHE_SIZE - partial_page);
+ if (common->gadget->speed != USB_SPEED_SUPER) {
+ if (partial_page > 0)
+ amount = min(amount,
+ (unsigned int) PAGE_CACHE_SIZE -
+ partial_page);
+ }
if (amount == 0) {
get_some_more = 0;
@@ -969,6 +999,17 @@ static int do_write(struct fsg_common *common)
continue;
}
+ if (common->gadget->speed == USB_SPEED_SUPER) {
+ /* The packet should be multiple of MPS, which is 1024. */
+ if (amount < amount_left_to_write) {
+ unsigned mod = amount % common->bulk_out_maxpacket;
+ if (mod && (amount - mod)) {
+ // not a multiple of maxpacket
+ amount -= mod;
+ }
+ }
+ }
+
/* Get the next buffer */
usb_offset += amount;
common->usb_amount_left -= amount;
@@ -1008,6 +1049,12 @@ static int do_write(struct fsg_common *common)
}
amount = bh->outreq->actual;
+ if (amount > bh->outreq->length) {
+ diff = amount - bh->outreq->length;
+ amount = bh->outreq->length;
+ } else {
+ diff = 0;
+ }
if (curlun->file_length - file_offset < amount) {
LERROR(curlun,
"write %u @ %llu beyond end %llu\n",
@@ -1040,6 +1087,15 @@ static int do_write(struct fsg_common *common)
amount_left_to_write -= nwritten;
common->residue -= nwritten;
+ if (diff) {
+ LDBG(curlun, "host sent too much data: %d\n", diff);
+ LDBG(curlun, "data_size=%d residue=%d amt_left=%d\n",
+ common->data_size, common->residue, common->usb_amount_left);
+ common->data_size = common->residue;
+ common->usb_amount_left = 0;
+ break;
+ }
+
/* If an error occurred, report it and its position */
if (nwritten < amount) {
curlun->sense_data = SS_WRITE_ERROR;
@@ -1200,6 +1256,9 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh)
{
struct fsg_lun *curlun = common->curlun;
u8 *buf = (u8 *) bh->buf;
+#if defined(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE)
+ static char new_product_name[16 + 1];
+#endif
if (!curlun) { /* Unsupported LUNs are okay */
common->bad_lun_okay = 1;
@@ -1217,6 +1276,23 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh)
buf[5] = 0; /* No special options */
buf[6] = 0;
buf[7] = 0;
+
+#if defined(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE)
+ strncpy(new_product_name, common->product_string, 16);
+ new_product_name[16] = '\0';
+ if (common->product_string[0] &&
+ strlen(common->product_string) <= 11 && /* check string length */
+ common->lun > 0) {
+ strncat(new_product_name, " Card", 16);
+ new_product_name[16] = '\0';
+ }
+
+ snprintf(common->inquiry_string,
+ sizeof common->inquiry_string,
+ "%-8s%-16s%04x",
+ common->vendor_string,
+ new_product_name, 1);
+#endif
memcpy(buf + 8, common->inquiry_string, sizeof common->inquiry_string);
return 36;
}
@@ -2330,6 +2406,17 @@ static int enable_endpoint(struct fsg_common *common, struct usb_ep *ep,
int rc;
ep->driver_data = common;
+
+ if (common->gadget->speed == USB_SPEED_SUPER) {
+ if (ep == common->fsg->bulk_in)
+ ep->maxburst = fsg_ss_bulk_in_comp_desc.bMaxBurst;
+ else if (ep == common->fsg->bulk_out)
+ ep->maxburst = fsg_ss_bulk_out_comp_desc.bMaxBurst;
+ else
+ ep->maxburst = 0;
+ } else
+ ep->maxburst = 0;
+
rc = usb_ep_enable(ep, d);
if (rc)
ERROR(common, "can't enable %s, result %d\n", ep->name, rc);
@@ -2396,14 +2483,22 @@ reset:
fsg = common->fsg;
/* Enable the endpoints */
- d = fsg_ep_desc(common->gadget,
+ if (gadget_is_superspeed(common->gadget) &&
+ common->gadget->speed == USB_SPEED_SUPER)
+ d = &fsg_ss_bulk_in_desc;
+ else
+ d = fsg_ep_desc(common->gadget,
&fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc);
rc = enable_endpoint(common, fsg->bulk_in, d);
if (rc)
goto reset;
fsg->bulk_in_enabled = 1;
- d = fsg_ep_desc(common->gadget,
+ if (gadget_is_superspeed(common->gadget) &&
+ common->gadget->speed == USB_SPEED_SUPER)
+ d = &fsg_ss_bulk_out_desc;
+ else
+ d = fsg_ep_desc(common->gadget,
&fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc);
rc = enable_endpoint(common, fsg->bulk_out, d);
if (rc)
@@ -2779,6 +2874,9 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common,
curlun->ro = lcfg->cdrom || lcfg->ro;
curlun->initially_ro = curlun->ro;
curlun->removable = lcfg->removable;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ curlun->nofua = lcfg->nofua;
+#endif
curlun->dev.release = fsg_lun_release;
curlun->dev.parent = &gadget->dev;
/* curlun->dev.driver = &fsg_driver.driver; XXX */
@@ -2856,6 +2954,15 @@ buffhds_first_it:
: "File-CD Gadget"),
i);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /* Default INQUIRY strings */
+ strncpy(common->vendor_string, "SAMSUNG",
+ sizeof(common->vendor_string) - 1);
+ strncpy(common->product_string, "File-Stor Gadget",
+ sizeof(common->product_string) - 1);
+ common->product_string[sizeof(common->product_string) - 1] = '\0';
+#endif
+
/*
* Some peripheral controllers are known not to be able to
* halt bulk endpoints correctly. If one of them is present,
@@ -2976,6 +3083,7 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
fsg_common_put(common);
usb_free_descriptors(fsg->function.descriptors);
usb_free_descriptors(fsg->function.hs_descriptors);
+ usb_free_descriptors(fsg->function.ss_descriptors);
kfree(fsg);
}
@@ -3026,6 +3134,28 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
}
}
+ if (gadget_is_superspeed(gadget)) {
+ unsigned max_burst;
+
+ /* Calculate bMaxBurst, we know packet size is 1024 */
+ max_burst = min_t(unsigned, FSG_BUFLEN / 1024, 15);
+
+ fsg_ss_bulk_in_desc.bEndpointAddress =
+ fsg_fs_bulk_in_desc.bEndpointAddress;
+ fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst;
+
+ fsg_ss_bulk_out_desc.bEndpointAddress =
+ fsg_fs_bulk_out_desc.bEndpointAddress;
+ fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst;
+
+ f->ss_descriptors = usb_copy_descriptors(fsg_ss_function);
+ if (unlikely(!f->ss_descriptors)) {
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+ return -ENOMEM;
+ }
+ }
+
return 0;
autoconf_fail:
@@ -3052,7 +3182,7 @@ static int fsg_bind_config(struct usb_composite_dev *cdev,
if (unlikely(!fsg))
return -ENOMEM;
- fsg->function.name = FSG_DRIVER_DESC;
+ fsg->function.name = "mass_storage";
fsg->function.strings = fsg_strings_array;
fsg->function.bind = fsg_bind;
fsg->function.unbind = fsg_unbind;
diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c
new file mode 100644
index 0000000..04c6053
--- /dev/null
+++ b/drivers/usb/gadget/f_mtp.c
@@ -0,0 +1,1342 @@
+/*
+ * Gadget Function Driver for MTP
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb.h>
+#include <linux/usb_usual.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/f_mtp.h>
+
+#define MTP_BULK_BUFFER_SIZE 16384
+#define INTR_BUFFER_SIZE 28
+
+/* String IDs */
+#define INTERFACE_STRING_INDEX 0
+
+/* values for mtp_dev.state */
+#define STATE_OFFLINE 0 /* initial state, disconnected */
+#define STATE_READY 1 /* ready for userspace calls */
+#define STATE_BUSY 2 /* processing userspace calls */
+#define STATE_CANCELED 3 /* transaction canceled by host */
+#define STATE_ERROR 4 /* error from completion routine */
+
+/* number of tx and rx requests to allocate */
+#define TX_REQ_MAX 4
+#define RX_REQ_MAX 2
+#define INTR_REQ_MAX 5
+
+/* ID for Microsoft MTP OS String */
+#define MTP_OS_STRING_ID 0xEE
+
+/* MTP class reqeusts */
+#define MTP_REQ_CANCEL 0x64
+#define MTP_REQ_GET_EXT_EVENT_DATA 0x65
+#define MTP_REQ_RESET 0x66
+#define MTP_REQ_GET_DEVICE_STATUS 0x67
+
+/* constants for device status */
+#define MTP_RESPONSE_OK 0x2001
+#define MTP_RESPONSE_DEVICE_BUSY 0x2019
+
+static const char mtp_shortname[] = "mtp_usb";
+
+struct mtp_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+ struct usb_ep *ep_intr;
+
+ int state;
+
+ /* synchronize access to our device file */
+ atomic_t open_excl;
+ /* to enforce only one ioctl at a time */
+ atomic_t ioctl_excl;
+
+ struct list_head tx_idle;
+ struct list_head intr_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ wait_queue_head_t intr_wq;
+ struct usb_request *rx_req[RX_REQ_MAX];
+ int rx_done;
+
+ /* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and
+ * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue
+ */
+ struct workqueue_struct *wq;
+ struct work_struct send_file_work;
+ struct work_struct receive_file_work;
+ struct file *xfer_file;
+ loff_t xfer_file_offset;
+ int64_t xfer_file_length;
+ unsigned xfer_send_header;
+ uint16_t xfer_command;
+ uint32_t xfer_transaction_id;
+ int xfer_result;
+};
+
+static struct usb_interface_descriptor mtp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = 0,
+};
+
+static struct usb_interface_descriptor ptp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor mtp_superspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor mtp_superspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor mtp_superspeed_bulk_comp_desc = {
+ .bLength = sizeof adb_superspeed_bulk_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+};
+
+static struct usb_endpoint_descriptor mtp_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mtp_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_intr_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE),
+ .bInterval = 6,
+};
+
+static struct usb_ss_ep_comp_descriptor mtp_intr_comp_desc = {
+ .bLength = sizeof mtp_intr_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 3 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+ .wBytesPerInterval = __constant_cpu_to_le16(INTR_BUFFER_SIZE),
+};
+
+static struct usb_descriptor_header *fs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *ss_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ (struct usb_descriptor_header *) &mtp_intr_comp_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *fs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *ss_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ (struct usb_descriptor_header *) &mtp_intr_comp_desc,
+ NULL,
+};
+
+static struct usb_string mtp_string_defs[] = {
+ /* Naming interface "MTP" so libmtp will recognize us */
+ [INTERFACE_STRING_INDEX].s = "MTP",
+ { }, /* end of list */
+};
+
+static struct usb_gadget_strings mtp_string_table = {
+ .language = 0x0409, /* en-US */
+ .strings = mtp_string_defs,
+};
+
+static struct usb_gadget_strings *mtp_strings[] = {
+ &mtp_string_table,
+ NULL,
+};
+
+/* Microsoft MTP OS String */
+static u8 mtp_os_string[] = {
+ 18, /* sizeof(mtp_os_string) */
+ USB_DT_STRING,
+ /* Signature field: "MSFT100" */
+ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+ /* vendor code */
+ 1,
+ /* padding */
+ 0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mtp_ext_config_desc_header {
+ __le32 dwLength;
+ __u16 bcdVersion;
+ __le16 wIndex;
+ __u8 bCount;
+ __u8 reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mtp_ext_config_desc_function {
+ __u8 bFirstInterfaceNumber;
+ __u8 bInterfaceCount;
+ __u8 compatibleID[8];
+ __u8 subCompatibleID[8];
+ __u8 reserved[6];
+};
+
+/* MTP Extended Configuration Descriptor */
+struct {
+ struct mtp_ext_config_desc_header header;
+ struct mtp_ext_config_desc_function function;
+} mtp_ext_config_desc = {
+ .header = {
+ .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)),
+ .bcdVersion = __constant_cpu_to_le16(0x0100),
+ .wIndex = __constant_cpu_to_le16(4),
+ .bCount = __constant_cpu_to_le16(1),
+ },
+ .function = {
+ .bFirstInterfaceNumber = 0,
+ .bInterfaceCount = 1,
+ .compatibleID = { 'M', 'T', 'P' },
+ },
+};
+
+struct mtp_device_status {
+ __le16 wLength;
+ __le16 wCode;
+};
+
+/* temporary variable used between mtp_open() and mtp_gadget_bind() */
+static struct mtp_dev *_mtp_dev;
+
+static inline struct mtp_dev *func_to_mtp(struct usb_function *f)
+{
+ return container_of(f, struct mtp_dev, function);
+}
+
+static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void mtp_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int mtp_lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void mtp_unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+static void mtp_req_put(struct mtp_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request
+*mtp_req_get(struct mtp_dev *dev, struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ mtp_req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ wake_up(&dev->read_wq);
+}
+
+static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ mtp_req_put(dev, &dev->intr_idle, req);
+
+ wake_up(&dev->intr_wq);
+}
+
+static int mtp_create_bulk_endpoints(struct mtp_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc,
+ struct usb_endpoint_descriptor *intr_desc)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, intr_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_intr = ep;
+
+ /* now allocate requests for our endpoints */
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_in;
+ mtp_req_put(dev, &dev->tx_idle, req);
+ }
+ for (i = 0; i < RX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_out;
+ dev->rx_req[i] = req;
+ }
+ for (i = 0; i < INTR_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_intr;
+ mtp_req_put(dev, &dev->intr_idle, req);
+ }
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "mtp_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t mtp_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret = 0;
+
+ DBG(cdev, "mtp_read(%d)\n", count);
+
+ if (count > MTP_BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ /* we will block until we're online */
+ DBG(cdev, "mtp_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->state != STATE_OFFLINE);
+ if (ret < 0) {
+ r = ret;
+ goto done;
+ }
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req[0];
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ goto done;
+ } else {
+ DBG(cdev, "rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ r = ret;
+ usb_ep_dequeue(dev->ep_out, req);
+ goto done;
+ }
+ if (dev->state == STATE_BUSY) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ DBG(cdev, "rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ r = xfer;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+ } else
+ r = -EIO;
+
+done:
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t mtp_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int sendZLP = 0;
+ int ret;
+
+ DBG(cdev, "mtp_write(%d)\n", count);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ return -ENODEV;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ /* we need to send a zero length packet to signal the end of transfer
+ * if the transfer size is aligned to a packet boundary.
+ */
+ if ((count & (dev->ep_in->maxpacket - 1)) == 0) {
+ sendZLP = 1;
+ }
+
+ while (count > 0 || sendZLP) {
+ /* so we exit after sending ZLP */
+ if (count == 0)
+ sendZLP = 0;
+
+ if (dev->state != STATE_BUSY) {
+ DBG(cdev, "mtp_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = mtp_req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY));
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > MTP_BULK_BUFFER_SIZE)
+ xfer = MTP_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ if (xfer && copy_from_user(req->buf, buf, xfer)) {
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "mtp_write: xfer error %d\n", ret);
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ mtp_req_put(dev, &dev->tx_idle, req);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_write returning %d\n", r);
+ return r;
+}
+
+/* read from a local file and write to USB */
+static void send_file_work(struct work_struct *data) {
+ struct mtp_dev *dev = container_of(data, struct mtp_dev, send_file_work);
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ struct mtp_data_header *header;
+ struct file *filp;
+ loff_t offset;
+ int64_t count;
+ int xfer, ret, hdr_size;
+ int r = 0;
+ int sendZLP = 0;
+
+ /* read our parameters */
+ smp_rmb();
+ filp = dev->xfer_file;
+ offset = dev->xfer_file_offset;
+ count = dev->xfer_file_length;
+
+ DBG(cdev, "send_file_work(%lld %lld)\n", offset, count);
+
+ if (dev->xfer_send_header) {
+ hdr_size = sizeof(struct mtp_data_header);
+ count += hdr_size;
+ } else {
+ hdr_size = 0;
+ }
+
+ /* we need to send a zero length packet to signal the end of transfer
+ * if the transfer size is aligned to a packet boundary.
+ */
+ if ((count & (dev->ep_in->maxpacket - 1)) == 0) {
+ sendZLP = 1;
+ }
+
+ while (count > 0 || sendZLP) {
+ /* so we exit after sending ZLP */
+ if (count == 0)
+ sendZLP = 0;
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ (req = mtp_req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY);
+ if (dev->state == STATE_CANCELED) {
+ r = -ECANCELED;
+ break;
+ }
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > MTP_BULK_BUFFER_SIZE)
+ xfer = MTP_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+
+ if (hdr_size) {
+ /* prepend MTP data header */
+ header = (struct mtp_data_header *)req->buf;
+ header->length = __cpu_to_le32(count);
+ header->type = __cpu_to_le16(2); /* data packet */
+ header->command = __cpu_to_le16(dev->xfer_command);
+ header->transaction_id = __cpu_to_le32(dev->xfer_transaction_id);
+ }
+
+ ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, &offset);
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+ xfer = ret + hdr_size;
+ hdr_size = 0;
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "send_file_work: xfer error %d\n", ret);
+ dev->state = STATE_ERROR;
+ r = -EIO;
+ break;
+ }
+
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ mtp_req_put(dev, &dev->tx_idle, req);
+
+ DBG(cdev, "send_file_work returning %d\n", r);
+ /* write the result */
+ dev->xfer_result = r;
+ smp_wmb();
+}
+
+/* read from USB and write to a local file */
+static void receive_file_work(struct work_struct *data)
+{
+ struct mtp_dev *dev = container_of(data, struct mtp_dev, receive_file_work);
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *read_req = NULL, *write_req = NULL;
+ struct file *filp;
+ loff_t offset;
+ int64_t count;
+ int ret, cur_buf = 0;
+ int r = 0;
+
+ /* read our parameters */
+ smp_rmb();
+ filp = dev->xfer_file;
+ offset = dev->xfer_file_offset;
+ count = dev->xfer_file_length;
+
+ DBG(cdev, "receive_file_work(%lld)\n", count);
+
+ while (count > 0 || write_req) {
+ if (count > 0) {
+ /* queue a request */
+ read_req = dev->rx_req[cur_buf];
+ cur_buf = (cur_buf + 1) % RX_REQ_MAX;
+
+ read_req->length = (count > MTP_BULK_BUFFER_SIZE
+ ? MTP_BULK_BUFFER_SIZE : count);
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ }
+
+ if (write_req) {
+ DBG(cdev, "rx %p %d\n", write_req, write_req->actual);
+ ret = vfs_write(filp, write_req->buf, write_req->actual,
+ &offset);
+ DBG(cdev, "vfs_write %d\n", ret);
+ if (ret != write_req->actual) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ write_req = NULL;
+ }
+
+ if (read_req) {
+ /* wait for our last read to complete */
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->rx_done || dev->state != STATE_BUSY);
+ if (dev->state == STATE_CANCELED) {
+ r = -ECANCELED;
+ if (!dev->rx_done)
+ usb_ep_dequeue(dev->ep_out, read_req);
+ break;
+ }
+ /* if xfer_file_length is 0xFFFFFFFF, then we read until
+ * we get a zero length packet
+ */
+ if (count != 0xFFFFFFFF)
+ count -= read_req->actual;
+ if (read_req->actual < read_req->length) {
+ /* short packet is used to signal EOF for sizes > 4 gig */
+ DBG(cdev, "got short packet\n");
+ count = 0;
+ }
+
+ write_req = read_req;
+ read_req = NULL;
+ }
+ }
+
+ DBG(cdev, "receive_file_work returning %d\n", r);
+ /* write the result */
+ dev->xfer_result = r;
+ smp_wmb();
+}
+
+static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event)
+{
+ struct usb_request *req= NULL;
+ int ret;
+ int length = event->length;
+
+ DBG(dev->cdev, "mtp_send_event(%d)\n", event->length);
+
+ if (length < 0 || length > INTR_BUFFER_SIZE)
+ return -EINVAL;
+ if (dev->state == STATE_OFFLINE)
+ return -ENODEV;
+
+ ret = wait_event_interruptible_timeout(dev->intr_wq,
+ (req = mtp_req_get(dev, &dev->intr_idle)), msecs_to_jiffies(1000));
+ if (!req)
+ return -ETIME;
+
+ if (copy_from_user(req->buf, (void __user *)event->data, length)) {
+ mtp_req_put(dev, &dev->intr_idle, req);
+ return -EFAULT;
+ }
+ req->length = length;
+ ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL);
+ if (ret)
+ mtp_req_put(dev, &dev->intr_idle, req);
+
+ return ret;
+}
+
+static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct file *filp = NULL;
+ int ret = -EINVAL;
+
+ if (mtp_lock(&dev->ioctl_excl))
+ return -EBUSY;
+
+ switch (code) {
+ case MTP_SEND_FILE:
+ case MTP_RECEIVE_FILE:
+ case MTP_SEND_FILE_WITH_HEADER:
+ {
+ struct mtp_file_range mfr;
+ struct work_struct *work;
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ ret = -ECANCELED;
+ goto out;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ ret = -ENODEV;
+ goto out;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) {
+ ret = -EFAULT;
+ goto fail;
+ }
+ /* hold a reference to the file while we are working with it */
+ filp = fget(mfr.fd);
+ if (!filp) {
+ ret = -EBADF;
+ goto fail;
+ }
+
+ /* write the parameters */
+ dev->xfer_file = filp;
+ dev->xfer_file_offset = mfr.offset;
+ dev->xfer_file_length = mfr.length;
+ smp_wmb();
+
+ if (code == MTP_SEND_FILE_WITH_HEADER) {
+ work = &dev->send_file_work;
+ dev->xfer_send_header = 1;
+ dev->xfer_command = mfr.command;
+ dev->xfer_transaction_id = mfr.transaction_id;
+ } else if (code == MTP_SEND_FILE) {
+ work = &dev->send_file_work;
+ dev->xfer_send_header = 0;
+ } else {
+ work = &dev->receive_file_work;
+ }
+
+ /* We do the file transfer on a work queue so it will run
+ * in kernel context, which is necessary for vfs_read and
+ * vfs_write to use our buffers in the kernel address space.
+ */
+ queue_work(dev->wq, work);
+ /* wait for operation to complete */
+ flush_workqueue(dev->wq);
+ fput(filp);
+
+ /* read the result */
+ smp_rmb();
+ ret = dev->xfer_result;
+ break;
+ }
+ case MTP_SEND_EVENT:
+ {
+ struct mtp_event event;
+ /* return here so we don't change dev->state below,
+ * which would interfere with bulk transfer state.
+ */
+ if (copy_from_user(&event, (void __user *)value, sizeof(event)))
+ ret = -EFAULT;
+ else
+ ret = mtp_send_event(dev, &event);
+ goto out;
+ }
+ }
+
+fail:
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ ret = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+out:
+ mtp_unlock(&dev->ioctl_excl);
+ DBG(dev->cdev, "ioctl returning %d\n", ret);
+ return ret;
+}
+
+static int mtp_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "mtp_open\n");
+ if (mtp_lock(&_mtp_dev->open_excl))
+ return -EBUSY;
+
+ /* clear any error condition */
+ if (_mtp_dev->state != STATE_OFFLINE)
+ _mtp_dev->state = STATE_READY;
+
+ fp->private_data = _mtp_dev;
+ return 0;
+}
+
+static int mtp_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "mtp_release\n");
+
+ mtp_unlock(&_mtp_dev->open_excl);
+ return 0;
+}
+
+/* file operations for /dev/mtp_usb */
+static const struct file_operations mtp_fops = {
+ .owner = THIS_MODULE,
+ .read = mtp_read,
+ .write = mtp_write,
+ .unlocked_ioctl = mtp_ioctl,
+ .open = mtp_open,
+ .release = mtp_release,
+};
+
+static struct miscdevice mtp_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = mtp_shortname,
+ .fops = &mtp_fops,
+};
+
+static int mtp_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct mtp_dev *dev = _mtp_dev;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+ unsigned long flags;
+
+ VDBG(cdev, "mtp_ctrlrequest "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ /* Handle MTP OS string */
+ if (ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+ && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == USB_DT_STRING
+ && (w_value & 0xFF) == MTP_OS_STRING_ID) {
+ value = (w_length < sizeof(mtp_os_string)
+ ? w_length : sizeof(mtp_os_string));
+ memcpy(cdev->req->buf, mtp_os_string, value);
+ } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
+ /* Handle MTP OS descriptor */
+ DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (ctrl->bRequest == 1
+ && (ctrl->bRequestType & USB_DIR_IN)
+ && (w_index == 4 || w_index == 5)) {
+ value = (w_length < sizeof(mtp_ext_config_desc) ?
+ w_length : sizeof(mtp_ext_config_desc));
+ memcpy(cdev->req->buf, &mtp_ext_config_desc, value);
+ }
+ } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
+ DBG(cdev, "class request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0
+ && w_value == 0) {
+ DBG(cdev, "MTP_REQ_CANCEL\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->state == STATE_BUSY) {
+ dev->state = STATE_CANCELED;
+ wake_up(&dev->read_wq);
+ wake_up(&dev->write_wq);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ /* We need to queue a request to read the remaining
+ * bytes, but we don't actually need to look at
+ * the contents.
+ */
+ value = w_length;
+ } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS
+ && w_index == 0 && w_value == 0) {
+ struct mtp_device_status *status = cdev->req->buf;
+ status->wLength =
+ __constant_cpu_to_le16(sizeof(*status));
+
+ DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n");
+ spin_lock_irqsave(&dev->lock, flags);
+ /* device status is "busy" until we report
+ * the cancelation to userspace
+ */
+ if (dev->state == STATE_CANCELED)
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY);
+ else
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_OK);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ value = sizeof(*status);
+ }
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ int rc;
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ ERROR(cdev, "%s setup response queue error\n", __func__);
+ }
+ return value;
+}
+
+static int
+mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct mtp_dev *dev = func_to_mtp(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "mtp_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ mtp_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc,
+ &mtp_fullspeed_out_desc, &mtp_intr_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ mtp_highspeed_in_desc.bEndpointAddress =
+ mtp_fullspeed_in_desc.bEndpointAddress;
+ mtp_highspeed_out_desc.bEndpointAddress =
+ mtp_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ mtp_superspeed_in_desc.bEndpointAddress =
+ mtp_fullspeed_in_desc.bEndpointAddress;
+ mtp_superspeed_out_desc.bEndpointAddress =
+ mtp_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+mtp_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_mtp(f);
+ struct usb_request *req;
+ int i;
+
+ while ((req = mtp_req_get(dev, &dev->tx_idle)))
+ mtp_request_free(req, dev->ep_in);
+ for (i = 0; i < RX_REQ_MAX; i++)
+ mtp_request_free(dev->rx_req[i], dev->ep_out);
+ while ((req = mtp_req_get(dev, &dev->intr_idle)))
+ mtp_request_free(req, dev->ep_intr);
+ dev->state = STATE_OFFLINE;
+}
+
+static int mtp_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct mtp_dev *dev = func_to_mtp(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt);
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER)
+ ret = usb_ep_enable(dev->ep_in, &mtp_superspeed_in_desc);
+ else
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &mtp_highspeed_in_desc,
+ &mtp_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER)
+ ret = usb_ep_enable(dev->ep_out, &mtp_superspeed_out_desc);
+ else
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &mtp_highspeed_out_desc,
+ &mtp_fullspeed_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ ret = usb_ep_enable(dev->ep_intr, &mtp_intr_desc);
+ if (ret) {
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ dev->state = STATE_READY;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void mtp_function_disable(struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_mtp(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "mtp_function_disable\n");
+ dev->state = STATE_OFFLINE;
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_intr);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int mtp_bind_config(struct usb_configuration *c, bool ptp_config)
+{
+ struct mtp_dev *dev = _mtp_dev;
+ int ret = 0;
+
+ printk(KERN_INFO "mtp_bind_config\n");
+
+ /* allocate a string ID for our interface */
+ if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) {
+ ret = usb_string_id(c->cdev);
+ if (ret < 0)
+ return ret;
+ mtp_string_defs[INTERFACE_STRING_INDEX].id = ret;
+ mtp_interface_desc.iInterface = ret;
+ }
+
+ dev->cdev = c->cdev;
+ dev->function.name = "mtp";
+ dev->function.strings = mtp_strings;
+ if (ptp_config) {
+ dev->function.descriptors = fs_ptp_descs;
+ dev->function.hs_descriptors = hs_ptp_descs;
+ dev->function.ss_descriptors = ss_ptp_descs;
+ } else {
+ dev->function.descriptors = fs_mtp_descs;
+ dev->function.hs_descriptors = hs_mtp_descs;
+ dev->function.ss_descriptors = ss_mtp_descs;
+ }
+ dev->function.bind = mtp_function_bind;
+ dev->function.unbind = mtp_function_unbind;
+ dev->function.set_alt = mtp_function_set_alt;
+ dev->function.disable = mtp_function_disable;
+
+ return usb_add_function(c, &dev->function);
+}
+
+static int mtp_setup(void)
+{
+ struct mtp_dev *dev;
+ int ret;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->lock);
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+ init_waitqueue_head(&dev->intr_wq);
+ atomic_set(&dev->open_excl, 0);
+ atomic_set(&dev->ioctl_excl, 0);
+ INIT_LIST_HEAD(&dev->tx_idle);
+ INIT_LIST_HEAD(&dev->intr_idle);
+
+ dev->wq = create_singlethread_workqueue("f_mtp");
+ if (!dev->wq) {
+ ret = -ENOMEM;
+ goto err1;
+ }
+ INIT_WORK(&dev->send_file_work, send_file_work);
+ INIT_WORK(&dev->receive_file_work, receive_file_work);
+
+ _mtp_dev = dev;
+
+ ret = misc_register(&mtp_device);
+ if (ret)
+ goto err2;
+
+ return 0;
+
+err2:
+ destroy_workqueue(dev->wq);
+err1:
+ _mtp_dev = NULL;
+ kfree(dev);
+ printk(KERN_ERR "mtp gadget driver failed to initialize\n");
+ return ret;
+}
+
+static void mtp_cleanup(void)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (!dev)
+ return;
+
+ misc_deregister(&mtp_device);
+ destroy_workqueue(dev->wq);
+ _mtp_dev = NULL;
+ kfree(dev);
+}
diff --git a/drivers/usb/gadget/f_mtp.h b/drivers/usb/gadget/f_mtp.h
new file mode 100644
index 0000000..5892e3f
--- /dev/null
+++ b/drivers/usb/gadget/f_mtp.h
@@ -0,0 +1,56 @@
+/*
+ * Gadget Driver for Android MTP
+ *
+ * Copyright (C) 2009 Samsung Electronics.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 __F_MTP_H
+#define __F_MTP_H
+
+
+#define MTP_MAX_PACKET_LEN_FROM_APP 22
+
+#define MTP_ACM_ENABLE 0
+#define MTP_ONLY_ENABLE 1
+#define MTP_DISABLE 2
+#define MTP_CLEAR_HALT 3
+#define MTP_WRITE_INT_DATA 4
+#define SET_MTP_USER_PID 5
+#define GET_SETUP_DATA 6
+#define SET_SETUP_DATA 7
+#define SEND_RESET_ACK 8
+#define SET_ZLP_DATA 9
+#define GET_HIGH_FULL_SPEED 10
+#define SIG_SETUP 44
+
+/*PIMA15740-2000 spec*/
+#define USB_PTPREQUEST_CANCELIO 0x64 /* Cancel request */
+#define USB_PTPREQUEST_GETEVENT 0x65 /* Get extened event data */
+#define USB_PTPREQUEST_RESET 0x66 /* Reset Device */
+#define USB_PTPREQUEST_GETSTATUS 0x67 /* Get Device Status */
+#define USB_PTPREQUEST_CANCELIO_SIZE 6
+#define USB_PTPREQUEST_GETSTATUS_SIZE 12
+
+
+
+int mtp_function_add(struct usb_configuration *c);
+int mtp_function_config_changed(struct usb_composite_dev *cdev,
+ struct usb_configuration *c);
+int mtp_enable(void);
+void mtp_function_enable(int enable);
+
+struct usb_mtp_ctrlrequest {
+ struct usb_ctrlrequest setup;
+};
+
+#endif /* __F_MTP_H */
diff --git a/drivers/usb/gadget/f_mtp_samsung.c b/drivers/usb/gadget/f_mtp_samsung.c
new file mode 100644
index 0000000..aa58024
--- /dev/null
+++ b/drivers/usb/gadget/f_mtp_samsung.c
@@ -0,0 +1,1449 @@
+/*
+ * drivers/usb/gadget/f_mtp_samsung.c
+ *
+ * Function Driver for USB MTP,
+ * f_mtp_samsung.c -- MTP Driver, for MTP development,
+ *
+ * Copyright (C) 2009 by Samsung Electronics,
+ * Author:Deepak M.G. <deepak.guru@samsung.com>,
+ * Author:Madhukar.J <madhukar.j@samsung.com>,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ */
+
+/*
+ * f_mtp_samsung.c file is the driver for MTP device. Totally three
+ * EndPoints will be configured in which 2 Bulk End Points
+ * and 1 Interrupt End point. This driver will also register as
+ * misc driver and exposes file operation funtions to user space.
+ */
+
+/* Includes */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+#include <linux/usb_usual.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+#include <linux/hardirq.h>
+#include <linux/sched.h>
+#include <linux/usb/f_accessory.h>
+#include <asm-generic/siginfo.h>
+#include <linux/usb/android_composite.h>
+#include <linux/kernel.h>
+#include "f_mtp.h"
+#include "gadget_chips.h"
+
+/*-------------------------------------------------------------------------*/
+/*Only for Debug*/
+#define DEBUG_MTP 0
+/*#define CSY_TEST */
+
+#if DEBUG_MTP
+#define DEBUG_MTP_SETUP
+#define DEBUG_MTP_READ
+#define DEBUG_MTP_WRITE
+
+#else
+#undef DEBUG_MTP_SETUP
+#undef DEBUG_MTP_READ
+#undef DEBUG_MTP_WRITE
+#endif
+
+/*#define DEBUG_MTP_SETUP*/
+/*#define DEBUG_MTP_READ*/
+/*#define DEBUG_MTP_WRITE*/
+
+#ifdef DEBUG_MTP_SETUP
+#define DEBUG_MTPB(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_MTPB(fmt, args...) do {} while (0)
+#endif
+
+#ifdef DEBUG_MTP_READ
+#define DEBUG_MTPR(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_MTPR(fmt, args...) do {} while (0)
+#endif
+#ifdef DEBUG_MTP_WRITE
+#define DEBUG_MTPW(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_MTPW(fmt, args...) do {} while (0)
+#endif
+/*-------------------------------------------------------------------------*/
+
+#define MTPG_BULK_BUFFER_SIZE 4096
+#define INT_MAX_PACKET_SIZE 10
+
+/* number of rx and tx requests to allocate */
+#define MTPG_RX_REQ_MAX 4
+#define MTPG_MTPG_TX_REQ_MAX 4
+
+/* ID for Microsoft MTP OS String */
+#define MTPG_OS_STRING_ID 0xEE
+
+#define DRIVER_NAME "usb_mtp_gadget"
+
+static const char mtpg_longname[] = "mtp";
+static const char shortname[] = DRIVER_NAME;
+static int mtp_pid;
+
+/* MTP Device Structure*/
+struct mtpg_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ struct usb_gadget *gadget;
+
+ spinlock_t lock;
+
+ u8 config;
+ int online;
+ int error;
+ int read_ready;
+ struct list_head tx_idle;
+ struct list_head rx_idle;
+ struct list_head rx_done;
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+
+ struct usb_request *read_req;
+ unsigned char *read_buf;
+ unsigned read_count;
+
+ struct usb_ep *bulk_in;
+ struct usb_ep *bulk_out;
+ struct usb_ep *int_in;
+ struct usb_request *notify_req;
+
+ atomic_t read_excl;
+ atomic_t write_excl;
+ atomic_t ioctl_excl;
+ atomic_t open_excl;
+ atomic_t wintfd_excl;
+ char cancel_io_buf[USB_PTPREQUEST_CANCELIO_SIZE+1];
+};
+
+/* Global mtpg_dev Structure
+* the_mtpg variable be used between mtpg_open() and mtpg_function_bind() */
+static struct mtpg_dev *the_mtpg;
+
+/* Three full-speed and high-speed endpoint descriptors: bulk-in, bulk-out,
+ * and interrupt-in. */
+
+struct usb_interface_descriptor mtpg_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = 0,
+};
+
+static struct usb_interface_descriptor ptp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor fs_mtpg_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_endpoint_descriptor fs_mtpg_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_endpoint_descriptor int_fs_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = __constant_cpu_to_le16(64),
+ .bInterval = 0x04,
+};
+
+static struct usb_descriptor_header *fs_mtpg_desc[] = {
+ (struct usb_descriptor_header *) &mtpg_interface_desc,
+ (struct usb_descriptor_header *) &fs_mtpg_in_desc,
+ (struct usb_descriptor_header *) &fs_mtpg_out_desc,
+ (struct usb_descriptor_header *) &int_fs_notify_desc,
+ NULL,
+};
+
+static struct usb_endpoint_descriptor hs_mtpg_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ /*bEndpointAddress copied from fs_mtpg_in_desc
+ during mtpg_function_bind()*/
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_mtpg_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ /*bEndpointAddress copied from fs_mtpg_out_desc
+ during mtpg_function_bind()*/
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+ .bInterval = 1, /* NAK every 1 uframe */
+};
+
+static struct usb_endpoint_descriptor int_hs_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = __constant_cpu_to_le16(64),
+ .bInterval = INT_MAX_PACKET_SIZE + 4,
+};
+
+static struct usb_descriptor_header *hs_mtpg_desc[] = {
+ (struct usb_descriptor_header *) &mtpg_interface_desc,
+ (struct usb_descriptor_header *) &hs_mtpg_in_desc,
+ (struct usb_descriptor_header *) &hs_mtpg_out_desc,
+ (struct usb_descriptor_header *) &int_hs_notify_desc,
+ NULL
+};
+
+static struct usb_descriptor_header *fs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &fs_mtpg_in_desc,
+ (struct usb_descriptor_header *) &fs_mtpg_out_desc,
+ (struct usb_descriptor_header *) &int_fs_notify_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &hs_mtpg_in_desc,
+ (struct usb_descriptor_header *) &hs_mtpg_out_desc,
+ (struct usb_descriptor_header *) &int_hs_notify_desc,
+ NULL,
+};
+
+/* string IDs are assigned dynamically */
+#define F_MTP_IDX 0
+#define STRING_PRODUCT_IDX 1
+#define STRING_SERIAL_IDX 2
+
+/* default serial number takes at least two packets */
+static const char serial[] = "0123456789.0123456789.0123456789";
+
+static struct usb_string strings_dev_mtp[] = {
+ [F_MTP_IDX].s = "MTP",
+ [STRING_PRODUCT_IDX].s = mtpg_longname,
+ [STRING_SERIAL_IDX].s = serial,
+ { }, /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_mtp = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dev_mtp,
+};
+
+static struct usb_gadget_strings *mtpg_dev_strings[] = {
+ &stringtab_mtp,
+ NULL,
+};
+
+/* Microsoft MTP OS String */
+static u8 mtpg_os_string[] = {
+ 18, /* sizeof(mtpg_os_string) */
+ USB_DT_STRING,
+ /* Signature field: "MSFT100" */
+ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+ /* vendor code */
+ 1,
+ /* padding */
+ 0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mtpg_ext_config_desc_header {
+ __le32 dwLength;
+ __u16 bcdVersion;
+ __le16 wIndex;
+ __u8 bCount;
+ __u8 reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mtpg_ext_config_desc_function {
+ __u8 bFirstInterfaceNumber;
+ __u8 bInterfaceCount;
+ __u8 compatibleID[8];
+ __u8 subCompatibleID[8];
+ __u8 reserved[6];
+};
+
+/* MTP Extended Configuration Descriptor */
+struct {
+ struct mtpg_ext_config_desc_header header;
+ struct mtpg_ext_config_desc_function function;
+} mtpg_ext_config_desc = {
+ .header = {
+ .dwLength = __constant_cpu_to_le32
+ (sizeof(mtpg_ext_config_desc)),
+ .bcdVersion = __constant_cpu_to_le16(0x0100),
+ .wIndex = __constant_cpu_to_le16(4),
+ .bCount = __constant_cpu_to_le16(1),
+ },
+ .function = {
+ .bFirstInterfaceNumber = 0,
+ .bInterfaceCount = 1,
+ .compatibleID = { 'M', 'T', 'P' },
+ },
+};
+
+/* Function : Change config for multi configuration
+ * Parameter : int conf_num (config number)
+ * 0 - use mtp only without Samsung USB Driver
+ * 1 - use mtp + acm with Samsung USB Driver
+ * Description
+ * Below function is for samsung multi configuration
+ * feature made by soonyong,cho.
+ * Please add below handler to set_config_desc of function.
+ * Date : 2011-08-03
+ */
+static int mtp_set_config_desc(int conf_num)
+{
+ switch (conf_num) {
+ case 0:
+ mtpg_interface_desc.bInterfaceClass =
+ USB_CLASS_VENDOR_SPEC;
+ mtpg_interface_desc.bInterfaceSubClass =
+ USB_SUBCLASS_VENDOR_SPEC;
+ mtpg_interface_desc.bInterfaceProtocol =
+ 0x0;
+ break;
+ case 1:
+ mtpg_interface_desc.bInterfaceClass =
+ USB_CLASS_STILL_IMAGE;
+ mtpg_interface_desc.bInterfaceSubClass =
+ 0x01;
+ mtpg_interface_desc.bInterfaceProtocol =
+ 0x01;
+ break;
+
+ }
+ return 1;
+}
+
+/* -------------------------------------------------------------------------
+ * Main Functionalities Start!
+ * ------------------------------------------------------------------------- */
+static inline struct mtpg_dev *mtpg_func_to_dev(struct usb_function *f)
+{
+ return container_of(f, struct mtpg_dev, function);
+}
+
+static inline int _lock(atomic_t *excl)
+{
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void _unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+static void mtpg_req_put(struct mtpg_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request *mtpg_req_get(struct mtpg_dev *dev,
+ struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return req;
+}
+
+static int mtp_send_signal(int value)
+{
+ int ret;
+ struct siginfo info;
+ struct task_struct *t;
+ memset(&info, 0, sizeof(struct siginfo));
+ info.si_signo = SIG_SETUP;
+ info.si_code = SI_QUEUE;
+ info.si_int = value;
+ rcu_read_lock();
+ t = pid_task(find_vpid(mtp_pid), PIDTYPE_PID);
+ if (t == NULL) {
+ printk(KERN_DEBUG "no such pid\n");
+ rcu_read_unlock();
+ return -ENODEV;
+ }
+
+ rcu_read_unlock();
+ /*send the signal*/
+ ret = send_sig_info(SIG_SETUP, &info, t);
+ if (ret < 0) {
+ printk(KERN_ERR "[%s]error sending signal\n", __func__);
+ return ret;
+ }
+ return 0;
+
+}
+
+static int mtpg_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__);
+
+ if (_lock(&the_mtpg->open_excl)) {
+ printk(KERN_ERR "mtpg_open fn mtpg device busy\n");
+ return -EBUSY;
+ }
+
+ fp->private_data = the_mtpg;
+
+ /* clear the error latch */
+
+ DEBUG_MTPB("[%s] mtpg_open and clearing the error = 0\n", __func__);
+
+ the_mtpg->error = 0;
+
+ return 0;
+}
+
+static ssize_t mtpg_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtpg_dev *dev = fp->private_data;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret;
+
+ DEBUG_MTPR("[%s] and count = (%d)\n", __func__, count);
+
+ if (_lock(&dev->read_excl))
+ return -EBUSY;
+
+ while (!((dev->online || dev->error) && dev->read_ready)) {
+ DEBUG_MTPR("[%s] and line is = %d\n", __func__, __LINE__);
+ ret = wait_event_interruptible(dev->read_wq,
+ ((dev->online || dev->error) && dev->read_ready));
+ if (ret < 0) {
+ _unlock(&dev->read_excl);
+ printk(KERN_DEBUG "[%s]line is = %d,mtp_read ret<0\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ }
+
+ while (count > 0) {
+ DEBUG_MTPR("[%s] and line is = %d\n", __func__, __LINE__);
+
+ if (dev->error) {
+ r = -EIO;
+ printk(KERN_ERR "[%s]\t%d:dev->error so break r=%d\n",
+ __func__, __LINE__, r);
+ break;
+ }
+
+ /* if we have idle read requests, get them queued */
+ DEBUG_MTPR("[%s]\t%d: get request\n", __func__, __LINE__);
+ while ((req = mtpg_req_get(dev, &dev->rx_idle))) {
+requeue_req:
+ req->length = MTPG_BULK_BUFFER_SIZE;
+ DEBUG_MTPR("[%s]\t%d:usb-ep-queue\n",
+ __func__, __LINE__);
+ ret = usb_ep_queue(dev->bulk_out, req, GFP_ATOMIC);
+
+ DEBUG_MTPR("[%s]\t%d:Endpoint: %s\n",
+ __func__, __LINE__, dev->bulk_out->name);
+
+ if (ret < 0) {
+ r = -EIO;
+ dev->error = 1;
+ mtpg_req_put(dev, &dev->rx_idle, req);
+ printk(KERN_ERR "[%s]line[%d]FAIL r=%d\n",
+ __func__, __LINE__, r);
+ goto fail;
+ } else {
+ DEBUG_MTPR("[%s]rx req queue%p\n",
+ __func__, req);
+ }
+ }
+
+ DEBUG_MTPR("[%s]\t%d:read_count = %d\n",
+ __func__, __LINE__, dev->read_count);
+
+ /* if we have data pending, give it to userspace */
+ if (dev->read_count > 0) {
+ DEBUG_MTPR("[%s]\t%d: read_count = %d\n",
+ __func__, __LINE__, dev->read_count);
+ if (dev->read_count < count)
+ xfer = dev->read_count;
+ else
+ xfer = count;
+
+ DEBUG_MTPR("[%s]copy_to_user 0x%x bytes on EP %p\n",
+ __func__, dev->read_count, dev->bulk_out);
+
+ if (copy_to_user(buf, dev->read_buf, xfer)) {
+ r = -EFAULT;
+ printk(KERN_ERR "[%s]%d:cpytouer fail r=%d\n",
+ __func__, __LINE__, r);
+ break;
+ }
+
+ dev->read_buf += xfer;
+ dev->read_count -= xfer;
+ buf += xfer;
+ count -= xfer;
+
+ /* if we've emptied the buffer, release the request */
+ if (dev->read_count == 0) {
+ DEBUG_MTPR("[%s] and line is = %d\n",
+ __func__, __LINE__);
+ mtpg_req_put(dev, &dev->rx_idle, dev->read_req);
+ dev->read_req = 0;
+ }
+
+ /*Updating the buffer size and returnung
+ from mtpg_read */
+ r = xfer;
+ DEBUG_MTPR("[%s] \t %d: returning lenght %d\n",
+ __func__, __LINE__, r);
+ goto fail;
+ }
+
+ /* wait for a request to complete */
+ req = 0;
+ DEBUG_MTPR("[%s] and line is = %d\n", __func__, __LINE__);
+ ret = wait_event_interruptible(dev->read_wq,
+ ((req = mtpg_req_get(dev, &dev->rx_done))
+ || dev->error));
+ DEBUG_MTPR("[%s]\t%d: dev->error %d and req = %p\n",
+ __func__, __LINE__, dev->error, req);
+
+ if (req != 0) {
+ /* if we got a 0-len one we need to put it back into
+ ** service. if we made it the current read req we'd
+ ** be stuck forever
+ */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ dev->read_req = req;
+ dev->read_count = req->actual;
+ dev->read_buf = req->buf;
+
+ DEBUG_MTPR("[%s]\t%d: rx_req=%p req->actual=%d\n",
+ __func__, __LINE__, req, req->actual);
+ }
+
+ if (ret < 0) {
+ r = ret;
+ printk(KERN_DEBUG "[%s]\t%d after ret=%d brk ret=%d\n",
+ __func__, __LINE__, ret, r);
+ break;
+ }
+ }
+
+fail:
+ _unlock(&dev->read_excl);
+ DEBUG_MTPR("[%s]\t%d: RETURNING Back to USpace r=%d\n",
+ __func__, __LINE__, r);
+ return r;
+
+}
+
+static ssize_t mtpg_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtpg_dev *dev = fp->private_data;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+
+ DEBUG_MTPW("[%s] \t%d ep bulk_out name = %s\n",
+ __func__, __LINE__ , dev->bulk_out->name);
+
+ if (_lock(&dev->write_excl))
+ return -EBUSY;
+
+ while (count > 0) {
+ if (dev->error) {
+ r = -EIO;
+ printk(KERN_DEBUG "[%s]%d count>0 dev->error so brk\n",
+ __func__, __LINE__);
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = mtpg_req_get(dev, &dev->tx_idle))
+ || dev->error));
+
+ if (ret < 0) {
+ r = ret;
+ printk(KERN_DEBUG "[%s]\t%d ret = %d\n",
+ __func__, __LINE__, r);
+ break;
+ }
+
+ if (req != 0) {
+ if (count > MTPG_BULK_BUFFER_SIZE)
+ xfer = MTPG_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+
+ DEBUG_MTPW("[%s]\t%d copy_from_user length %d\n",
+ __func__, __LINE__, xfer);
+
+ if (copy_from_user(req->buf, buf, xfer)) {
+ printk(KERN_ERR "mtpwrite cpyfrmusr error\n");
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->bulk_in, req, GFP_ATOMIC);
+ if (ret < 0) {
+ dev->error = 1;
+ r = -EIO;
+ printk(KERN_ERR "[%s]\t%d ep_que ret=%d brk ret=%d\n",
+ __func__, __LINE__, ret, r);
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+ }
+
+ if (req) {
+ DEBUG_MTPW("[%s] \t%d mtpg_req_put\n", __func__, __LINE__);
+ mtpg_req_put(dev, &dev->tx_idle, req);
+ }
+
+ _unlock(&dev->write_excl);
+
+ DEBUG_MTPW("[%s]\t%d RETURN back to USpace r=%d\n",
+ __func__, __LINE__, r);
+ return r;
+}
+
+static void interrupt_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ printk(KERN_DEBUG "Finished Writing Interrupt Data\n");
+}
+
+static ssize_t interrupt_write(struct file *fd,
+ const char __user *buf, size_t count)
+{
+ struct mtpg_dev *dev = fd->private_data;
+ struct usb_request *req = 0;
+ int ret;
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+ req = dev->notify_req;
+
+ if (!req) {
+ printk(KERN_ERR "[%s]Alloc has failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (_lock(&dev->wintfd_excl)) {
+ printk(KERN_ERR "write failed on interrupt endpoint\n");
+ return -EBUSY;
+ }
+
+ if (copy_from_user(req->buf, buf, count)) {
+ printk(KERN_ERR "[%s]copy from user has failed\n", __func__);
+ return -EIO;
+ }
+
+ req->length = count;
+ req->complete = interrupt_complete;
+
+ ret = usb_ep_queue(dev->int_in, req, GFP_ATOMIC);
+
+ if (ret < 0) {
+ printk(KERN_ERR "[%s]usb_ep_queue failed\n", __func__);
+ return -EIO;
+ }
+
+ _unlock(&dev->wintfd_excl);
+ return ret;
+}
+
+static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg)
+{
+ struct mtpg_dev *dev = fd->private_data;
+ struct usb_composite_dev *cdev;
+ struct usb_request *req;
+ int status = 0;
+ int size = 0;
+ int ret_value = 0;
+ int max_pkt = 0;
+ char *buf_ptr = NULL;
+ char buf[USB_PTPREQUEST_GETSTATUS_SIZE+1] = {0};
+
+ cdev = dev->cdev;
+ if (!cdev) {
+ printk(KERN_ERR "usb: %s cdev not ready\n", __func__);
+ return -EAGAIN;
+ }
+ req = cdev->req;
+ if (!cdev->req) {
+ printk(KERN_ERR "usb: %s cdev->req not ready\n", __func__);
+ return -EAGAIN;
+ }
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+
+ switch (code) {
+ case MTP_ONLY_ENABLE:
+ printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE ioctl:\n",
+ __func__, __LINE__);
+ if (dev->cdev && dev->cdev->gadget) {
+ usb_gadget_disconnect(cdev->gadget);
+ printk(KERN_DEBUG "[%s:%d] B4 disconectng gadget\n",
+ __func__, __LINE__);
+ msleep(20);
+ usb_gadget_connect(cdev->gadget);
+ printk(KERN_DEBUG "[%s:%d] after usb_gadget_connect\n",
+ __func__, __LINE__);
+ }
+ status = 10;
+ printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE clearing error 0\n",
+ __func__, __LINE__);
+ the_mtpg->error = 0;
+ break;
+ case MTP_DISABLE:
+ /*mtp_function_enable(mtp_disable_desc);*/
+ if (dev->cdev && dev->cdev->gadget) {
+ usb_gadget_disconnect(dev->cdev->gadget);
+ mdelay(5);
+ usb_gadget_connect(dev->cdev->gadget);
+ }
+ break;
+ case MTP_CLEAR_HALT:
+ status = usb_ep_clear_halt(dev->bulk_in);
+ status = usb_ep_clear_halt(dev->bulk_out);
+ break;
+ case MTP_WRITE_INT_DATA:
+ printk(KERN_INFO "[%s]\t%d MTP intrpt_Write\n",
+ __func__, __LINE__);
+ ret_value = interrupt_write(fd, (const char *)arg,
+ MTP_MAX_PACKET_LEN_FROM_APP);
+ if (ret_value < 0) {
+ printk(KERN_ERR "[%s]\t%d interptFD failed\n",
+ __func__, __LINE__);
+ status = -EIO;
+ } else {
+ printk(KERN_DEBUG "[%s]\t%d intruptFD suces\n",
+ __func__, __LINE__);
+ status = MTP_MAX_PACKET_LEN_FROM_APP;
+ }
+ break;
+
+ case SET_MTP_USER_PID:
+ mtp_pid = arg;
+ printk(KERN_DEBUG "[%s]SET_MTP_USER_PID;pid=%d\tline=[%d]\n",
+ __func__, mtp_pid, __LINE__);
+ break;
+
+ case GET_SETUP_DATA:
+ buf_ptr = (char *)arg;
+ printk(KERN_DEBUG "[%s] GET_SETUP_DATA\tline = [%d]\n",
+ __func__, __LINE__);
+ if (copy_to_user(buf_ptr, dev->cancel_io_buf,
+ USB_PTPREQUEST_CANCELIO_SIZE)) {
+ status = -EIO;
+ printk(KERN_ERR "[%s]\t%d:coptousr failed\n",
+ __func__, __LINE__);
+ }
+ break;
+
+ case SEND_RESET_ACK:
+ /*req->zero = 1;*/
+ req->length = 0;
+ /*printk(KERN_DEBUG "[%s]SEND_RESET_ACK and usb_ep_queu
+ ZERO data size = %d\tline=[%d]\n",
+ __func__, size, __LINE__);*/
+ status = usb_ep_queue(cdev->gadget->ep0,
+ req, GFP_ATOMIC);
+ if (status < 0)
+ printk(KERN_ERR "[%s]ep_queue line = [%d]\n",
+ __func__, __LINE__);
+ break;
+
+ case SET_SETUP_DATA:
+ buf_ptr = (char *)arg;
+ if (copy_from_user(buf, buf_ptr,
+ USB_PTPREQUEST_GETSTATUS_SIZE)) {
+ status = -EIO;
+ printk(KERN_ERR "[%s]\t%d:copyfrmuser fail\n",
+ __func__, __LINE__);
+ break;
+ }
+ size = buf[0];
+ printk(KERN_DEBUG "[%s]SET_SETUP_DATA size=%d line=[%d]\n",
+ __func__, size, __LINE__);
+ memcpy(req->buf, buf, size);
+ req->zero = 0;
+ req->length = size;
+ status = usb_ep_queue(cdev->gadget->ep0, req,
+ GFP_ATOMIC);
+ if (status < 0)
+ printk(KERN_ERR "[%s]usbepqueue line=[%d]\n",
+ __func__, __LINE__);
+ break;
+
+ case SET_ZLP_DATA:
+ /*req->zero = 1;*/
+ req = mtpg_req_get(dev, &dev->tx_idle);
+ if (!req) {
+ printk(KERN_DEBUG "[%s] Failed to get ZLP_DATA\n",
+ __func__);
+ return -EAGAIN;
+ }
+ req->length = 0;
+ printk(KERN_DEBUG "[%s]ZLP_DATA data=%d\tline=[%d]\n",
+ __func__, size, __LINE__);
+ status = usb_ep_queue(dev->bulk_in, req, GFP_ATOMIC);
+ if (status < 0) {
+ printk(KERN_ERR "[%s]usbepqueue line=[%d]\n",
+ __func__, __LINE__);
+ } else {
+ printk(KERN_DEBUG "%sZLPstatus=%d\tline=%d\n",
+ __func__, __LINE__, status);
+ status = 20;
+ }
+ break;
+
+ case GET_HIGH_FULL_SPEED:
+ printk(KERN_DEBUG "[%s]GET_HIGH_FULLSPEED line=[%d]\n",
+ __func__, __LINE__);
+ max_pkt = dev->bulk_in->maxpacket;
+ printk(KERN_DEBUG "[%s] line = %d max_pkt = [%d]\n",
+ __func__, __LINE__, max_pkt);
+ if (max_pkt == 64)
+ status = 64;
+ else
+ status = 512;
+ break;
+ default:
+ status = -ENOTTY;
+ }
+ return status;
+}
+
+static int mtpg_release_device(struct inode *ip, struct file *fp)
+{
+ printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__);
+ if (the_mtpg != NULL)
+ _unlock(&the_mtpg->open_excl);
+ return 0;
+}
+
+/* file operations for MTP device /dev/usb_mtp_gadget */
+static const struct file_operations mtpg_fops = {
+ .owner = THIS_MODULE,
+ .read = mtpg_read,
+ .write = mtpg_write,
+ .open = mtpg_open,
+ .unlocked_ioctl = mtpg_ioctl,
+ .release = mtpg_release_device,
+};
+
+static struct miscdevice mtpg_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = shortname,
+ .fops = &mtpg_fops,
+};
+
+struct usb_request *alloc_ep_req(struct usb_ep *ep,
+ unsigned len, gfp_t kmalloc_flags)
+{
+ struct usb_request *req;
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (req) {
+ req->length = len;
+ req->buf = kmalloc(len, GFP_ATOMIC);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ req = NULL;
+ }
+ }
+ return req;
+}
+
+static void mtpg_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static struct usb_request *mtpg_request_new(struct usb_ep *ep, int buffer_size)
+{
+
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+
+ DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__);
+ if (!req) {
+ printk(KERN_ERR "[%s]\tline %d ERROR\n", __func__, __LINE__);
+ return NULL;
+ }
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void mtpg_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtpg_dev *dev = the_mtpg;
+ DEBUG_MTPB("[%s]\t %d req->status is = %d\n",
+ __func__, __LINE__, req->status);
+
+ if (req->status != 0)
+ dev->error = 1;
+
+ mtpg_req_put(dev, &dev->tx_idle, req);
+ wake_up(&dev->write_wq);
+}
+
+static void mtpg_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtpg_dev *dev = the_mtpg;
+
+ DEBUG_MTPB("[%s]\tline = [%d]req->status is = %d\n",
+ __func__, __LINE__, req->status);
+ if (req->status != 0) {
+ dev->error = 1;
+
+ DEBUG_MTPB("[%s]\t%d dev->error is=%d for rx_idle\n",
+ __func__, __LINE__, dev->error);
+ mtpg_req_put(dev, &dev->rx_idle, req);
+ } else {
+ DEBUG_MTPB("[%s]\t%d for rx_done\n", __func__, __LINE__);
+ mtpg_req_put(dev, &dev->rx_done, req);
+ }
+ wake_up(&dev->read_wq);
+}
+
+static void
+mtpg_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct mtpg_dev *dev = mtpg_func_to_dev(f);
+ struct usb_request *req;
+
+ printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__);
+
+ while ((req = mtpg_req_get(dev, &dev->rx_idle)))
+ mtpg_request_free(req, dev->bulk_out);
+
+ while ((req = mtpg_req_get(dev, &dev->tx_idle)))
+ mtpg_request_free(req, dev->bulk_in);
+}
+
+static int
+mtpg_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct mtpg_dev *mtpg = mtpg_func_to_dev(f);
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i, id;
+
+ /* Allocate string descriptor numbers ... note that string
+ * contents can be overridden by the composite_dev glue.
+ */
+
+ printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__);
+
+ id = usb_interface_id(c, f);
+ if (id < 0) {
+ printk(KERN_ERR "[%s]Error in usb_interface_id\n", __func__);
+ return id;
+ }
+
+ mtpg_interface_desc.bInterfaceNumber = id;
+
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_mtpg_in_desc);
+ if (!ep) {
+ printk(KERN_ERR "[%s]Error usb_ep_autoconfig IN\n", __func__);
+ goto autoconf_fail;
+ }
+ ep->driver_data = mtpg; /* claim the endpoint */
+ mtpg->bulk_in = ep;
+ the_mtpg->bulk_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_mtpg_out_desc);
+ if (!ep) {
+ printk(KERN_ERR "[%s]Eror usb_ep_autoconfig OUT\n", __func__);
+ goto autoconf_fail;
+ }
+ ep->driver_data = mtpg; /* claim the endpoint */
+ mtpg->bulk_out = ep;
+ the_mtpg->bulk_out = ep;
+
+ /* Interrupt Support for MTP */
+ ep = usb_ep_autoconfig(cdev->gadget, &int_fs_notify_desc);
+ if (!ep) {
+ printk(KERN_ERR "[%s]Eror usb_ep_autoconfig INT\n", __func__);
+ goto autoconf_fail;
+ }
+ ep->driver_data = mtpg;
+ mtpg->int_in = ep;
+ the_mtpg->int_in = ep;
+
+ mtpg->notify_req = alloc_ep_req(ep,
+ sizeof(struct usb_mtp_ctrlrequest) + 2,
+ GFP_ATOMIC);
+ if (!mtpg->notify_req)
+ goto out;
+
+ for (i = 0; i < MTPG_RX_REQ_MAX; i++) {
+ req = mtpg_request_new(mtpg->bulk_out, MTPG_BULK_BUFFER_SIZE);
+ if (!req)
+ goto out;
+ req->complete = mtpg_complete_out;
+ mtpg_req_put(mtpg, &mtpg->rx_idle, req);
+ }
+
+ for (i = 0; i < MTPG_MTPG_TX_REQ_MAX; i++) {
+ req = mtpg_request_new(mtpg->bulk_in, MTPG_BULK_BUFFER_SIZE);
+ if (!req)
+ goto out;
+ req->complete = mtpg_complete_in;
+ mtpg_req_put(mtpg, &mtpg->tx_idle, req);
+ }
+
+ if (gadget_is_dualspeed(cdev->gadget)) {
+
+ DEBUG_MTPB("[%s]\tdual speed line = [%d]\n",
+ __func__, __LINE__);
+
+ /* Assume endpoint addresses are the same for both speeds */
+ hs_mtpg_in_desc.bEndpointAddress =
+ fs_mtpg_in_desc.bEndpointAddress;
+ hs_mtpg_out_desc.bEndpointAddress =
+ fs_mtpg_out_desc.bEndpointAddress;
+ int_hs_notify_desc.bEndpointAddress =
+ int_fs_notify_desc.bEndpointAddress;
+
+ f->hs_descriptors = hs_mtpg_desc;
+ }
+
+ mtpg->cdev = cdev;
+ the_mtpg->cdev = cdev;
+
+ return 0;
+
+autoconf_fail:
+ printk(KERN_ERR "mtpg unable to autoconfigure all endpoints\n");
+ return -ENOTSUPP;
+out:
+ mtpg_function_unbind(c, f);
+ return -1;
+}
+
+static int mtpg_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct mtpg_dev *dev = mtpg_func_to_dev(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ if (dev->int_in->driver_data)
+ usb_ep_disable(dev->int_in);
+
+ ret = usb_ep_enable(dev->int_in,
+ ep_choose(cdev->gadget, &int_hs_notify_desc,
+ &int_fs_notify_desc));
+ if (ret) {
+ usb_ep_disable(dev->int_in);
+ dev->int_in->driver_data = NULL;
+ printk(KERN_ERR "[%s]Error in enabling INT EP\n", __func__);
+ return ret;
+ }
+ dev->int_in->driver_data = dev;
+
+ if (dev->bulk_in->driver_data)
+ usb_ep_disable(dev->bulk_in);
+
+ ret = usb_ep_enable(dev->bulk_in,
+ ep_choose(cdev->gadget, &hs_mtpg_in_desc,
+ &fs_mtpg_in_desc));
+ if (ret) {
+ usb_ep_disable(dev->bulk_in);
+ dev->bulk_in->driver_data = NULL;
+ printk(KERN_ERR "[%s] Enable Bulk-IN EP error%d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ dev->bulk_in->driver_data = dev;
+
+ if (dev->bulk_out->driver_data)
+ usb_ep_disable(dev->bulk_out);
+
+ ret = usb_ep_enable(dev->bulk_out,
+ ep_choose(cdev->gadget, &hs_mtpg_out_desc,
+ &fs_mtpg_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->bulk_out);
+ dev->bulk_out->driver_data = NULL;
+ printk(KERN_ERR "[%s] Enable Bulk-Out EP error%d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ dev->bulk_out->driver_data = dev;
+
+ dev->online = 1;
+ dev->error = 0;
+ dev->read_ready = 1;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ return 0;
+}
+
+static void mtpg_function_disable(struct usb_function *f)
+{
+ struct mtpg_dev *dev = mtpg_func_to_dev(f);
+
+ printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__);
+ dev->online = 0;
+ dev->error = 1;
+
+ usb_ep_disable(dev->int_in);
+ dev->int_in->driver_data = NULL;
+
+ usb_ep_disable(dev->bulk_in);
+ dev->bulk_in->driver_data = NULL;
+
+ usb_ep_disable(dev->bulk_out);
+ dev->bulk_out->driver_data = NULL;
+
+ wake_up(&dev->read_wq);
+}
+
+
+/*PIMA15740-2000 spec: Class specific setup request for MTP*/
+static void
+mtp_complete_cancel_io(struct usb_ep *ep, struct usb_request *req)
+{
+ int i;
+ struct mtpg_dev *dev = ep->driver_data;
+
+ DEBUG_MTPB("[%s]\tline = [%d]\n", __func__, __LINE__);
+ if (req->status != 0) {
+ DEBUG_MTPB("[%s]req->status !=0\tline = [%d]\n",
+ __func__, __LINE__);
+ return;
+ }
+
+ if (req->actual != USB_PTPREQUEST_CANCELIO_SIZE) {
+ DEBUG_MTPB("[%s]USB_PTPREQUEST_CANCELIO_SIZE line = [%d]\n",
+ __func__, __LINE__);
+ usb_ep_set_halt(ep);
+
+ } else {
+ memset(dev->cancel_io_buf, 0, USB_PTPREQUEST_CANCELIO_SIZE+1);
+ memcpy(dev->cancel_io_buf, req->buf,
+ USB_PTPREQUEST_CANCELIO_SIZE);
+ /*Debugging*/
+ for (i = 0; i < USB_PTPREQUEST_CANCELIO_SIZE; i++)
+ DEBUG_MTPB("[%s]cancel_io_buf[%d]=%x\tline = [%d]\n",
+ __func__, i, dev->cancel_io_buf[i], __LINE__);
+ mtp_send_signal(USB_PTPREQUEST_CANCELIO);
+ }
+
+}
+
+static int mtp_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct mtpg_dev *dev = the_mtpg;
+ struct usb_request *req = cdev->req;
+ int signal_request = 0;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ if (ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+ && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == USB_DT_STRING
+ && (w_value & 0xFF) == MTPG_OS_STRING_ID) {
+ value = (w_length < sizeof(mtpg_os_string)
+ ? w_length : sizeof(mtpg_os_string));
+ memcpy(cdev->req->buf, mtpg_os_string, value);
+ if (value >= 0) {
+ int rc;
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ printk(KERN_DEBUG "[%s:%d] setup queue error\n",
+ __func__, __LINE__);
+ }
+ return value;
+ } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
+ if (ctrl->bRequest == 1
+ && (ctrl->bRequestType & USB_DIR_IN)
+ && (w_index == 4 || w_index == 5)) {
+ value = (w_length < sizeof(mtpg_ext_config_desc) ?
+ w_length : sizeof(mtpg_ext_config_desc));
+ memcpy(cdev->req->buf, &mtpg_ext_config_desc, value);
+
+ if (value >= 0) {
+ int rc;
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ printk(KERN_DEBUG "[%s:%d] setup queue error\n",
+ __func__, __LINE__);
+ }
+ return value;
+ }
+ printk(KERN_DEBUG "mtp_ctrlrequest "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ }
+
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_PTPREQUEST_CANCELIO:
+ DEBUG_MTPB("[%s]\tline=[%d]w_v=%x, w_i=%x, w_l=%x\n",
+ __func__, __LINE__, w_value, w_index, w_length);
+ /* if (w_value == 0x00 && w_index ==
+ mtpg_interface_desc.bInterfaceNumber
+ && w_length == 0x06) */
+ if (w_value == 0x00 && w_length == 0x06) {
+ DEBUG_MTPB("[%s]PTPREQUESTCANCLIO line[%d]\n",
+ __func__, __LINE__);
+ value = w_length;
+ cdev->gadget->ep0->driver_data = dev;
+ req->complete = mtp_complete_cancel_io;
+ req->zero = 0;
+ req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0,
+ req, GFP_ATOMIC);
+ if (value < 0) {
+ printk(KERN_ERR "[%s:%d]Error usb_ep_queue\n",
+ __func__, __LINE__);
+ } else
+ DEBUG_MTPB("[%s] ep-queue-sucecc line[%d]\n",
+ __func__, __LINE__);
+ }
+ return value;
+ break;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_PTPREQUEST_RESET:
+ DEBUG_MTPB("[%s] USB_PTPREQUEST_RESET\tline = [%d]\n",
+ __func__, __LINE__);
+ signal_request = USB_PTPREQUEST_RESET;
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_PTPREQUEST_GETSTATUS:
+ signal_request = USB_PTPREQUEST_GETSTATUS;
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_PTPREQUEST_GETEVENT:
+ signal_request = USB_PTPREQUEST_GETEVENT;
+ break;
+
+ default:
+ DEBUG_MTPB("[%s] INVALID REQUEST \tline = [%d]\n",
+ __func__, __LINE__);
+ return value;
+ }
+
+ value = mtp_send_signal(signal_request);
+ return value;
+}
+
+static int mtp_bind_config(struct usb_configuration *c, bool ptp_config)
+{
+ struct mtpg_dev *mtpg = the_mtpg;
+ int status = 0;
+
+ if (strings_dev_mtp[F_MTP_IDX].id == 0) {
+ status = usb_string_id(c->cdev);
+
+ if (status < 0)
+ return status;
+
+ strings_dev_mtp[F_MTP_IDX].id = status;
+ mtpg_interface_desc.iInterface = status;
+ }
+
+ mtpg->cdev = c->cdev;
+ mtpg->function.name = mtpg_longname;
+ mtpg->function.strings = mtpg_dev_strings;
+
+ /*Test the switch */
+ if (ptp_config) {
+ mtpg->function.descriptors = fs_ptp_descs;
+ mtpg->function.hs_descriptors = hs_ptp_descs;
+ } else {
+ mtpg->function.descriptors = fs_mtpg_desc;
+ mtpg->function.hs_descriptors = hs_mtpg_desc;
+ }
+
+ mtpg->function.bind = mtpg_function_bind;
+ mtpg->function.unbind = mtpg_function_unbind;
+ mtpg->function.set_alt = mtpg_function_set_alt;
+ mtpg->function.disable = mtpg_function_disable;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ mtpg->function.set_config_desc = mtp_set_config_desc;
+#endif
+
+ return usb_add_function(c, &mtpg->function);
+}
+
+static int mtp_setup(void)
+{
+ struct mtpg_dev *mtpg;
+ int rc;
+
+ printk(KERN_DEBUG "[%s] \tline = [%d]\n", __func__, __LINE__);
+ mtpg = kzalloc(sizeof(*mtpg), GFP_KERNEL);
+ if (!mtpg) {
+ printk(KERN_ERR "mtpg_dev_alloc memory failed\n");
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&mtpg->lock);
+ init_waitqueue_head(&mtpg->read_wq);
+ init_waitqueue_head(&mtpg->write_wq);
+
+ atomic_set(&mtpg->open_excl, 0);
+ atomic_set(&mtpg->read_excl, 0);
+ atomic_set(&mtpg->write_excl, 0);
+ atomic_set(&mtpg->wintfd_excl, 0);
+
+ INIT_LIST_HEAD(&mtpg->rx_idle);
+ INIT_LIST_HEAD(&mtpg->rx_done);
+ INIT_LIST_HEAD(&mtpg->tx_idle);
+
+ /* the_mtpg must be set before calling usb_gadget_register_driver */
+ the_mtpg = mtpg;
+
+ rc = misc_register(&mtpg_device);
+ if (rc != 0) {
+ printk(KERN_ERR " misc_register of mtpg Failed\n");
+ goto err_misc_register;
+ }
+
+ return 0;
+
+err_misc_register:
+ the_mtpg = NULL;
+ kfree(mtpg);
+ printk(KERN_ERR "mtp gadget driver failed to initialize\n");
+ return rc;
+}
+
+static void mtp_cleanup(void)
+{
+ struct mtpg_dev *mtpg = the_mtpg;
+ printk(KERN_DEBUG "[%s:::%d]\n", __func__, __LINE__);
+
+ if (!mtpg)
+ return;
+
+ misc_deregister(&mtpg_device);
+ the_mtpg = NULL;
+ kfree(mtpg);
+}
+
+MODULE_AUTHOR("Deepak And Madhukar");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/f_ncm.c b/drivers/usb/gadget/f_ncm.c
index 86902a6..9cc5fb5 100644
--- a/drivers/usb/gadget/f_ncm.c
+++ b/drivers/usb/gadget/f_ncm.c
@@ -143,7 +143,7 @@ static struct usb_cdc_ncm_ntb_parameters ntb_parameters = {
#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */
#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */
-static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = {
+static struct usb_interface_assoc_descriptor ncm_iad_desc = {
.bLength = sizeof ncm_iad_desc,
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
@@ -157,7 +157,7 @@ static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = {
/* interface descriptor: */
-static struct usb_interface_descriptor ncm_control_intf __initdata = {
+static struct usb_interface_descriptor ncm_control_intf = {
.bLength = sizeof ncm_control_intf,
.bDescriptorType = USB_DT_INTERFACE,
@@ -169,7 +169,7 @@ static struct usb_interface_descriptor ncm_control_intf __initdata = {
/* .iInterface = DYNAMIC */
};
-static struct usb_cdc_header_desc ncm_header_desc __initdata = {
+static struct usb_cdc_header_desc ncm_header_desc = {
.bLength = sizeof ncm_header_desc,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_HEADER_TYPE,
@@ -177,7 +177,7 @@ static struct usb_cdc_header_desc ncm_header_desc __initdata = {
.bcdCDC = cpu_to_le16(0x0110),
};
-static struct usb_cdc_union_desc ncm_union_desc __initdata = {
+static struct usb_cdc_union_desc ncm_union_desc = {
.bLength = sizeof(ncm_union_desc),
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_UNION_TYPE,
@@ -185,7 +185,7 @@ static struct usb_cdc_union_desc ncm_union_desc __initdata = {
/* .bSlaveInterface0 = DYNAMIC */
};
-static struct usb_cdc_ether_desc ecm_desc __initdata = {
+static struct usb_cdc_ether_desc ecm_desc = {
.bLength = sizeof ecm_desc,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_ETHERNET_TYPE,
@@ -200,7 +200,7 @@ static struct usb_cdc_ether_desc ecm_desc __initdata = {
#define NCAPS (USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE)
-static struct usb_cdc_ncm_desc ncm_desc __initdata = {
+static struct usb_cdc_ncm_desc ncm_desc = {
.bLength = sizeof ncm_desc,
.bDescriptorType = USB_DT_CS_INTERFACE,
.bDescriptorSubType = USB_CDC_NCM_TYPE,
@@ -212,7 +212,7 @@ static struct usb_cdc_ncm_desc ncm_desc __initdata = {
/* the default data interface has no endpoints ... */
-static struct usb_interface_descriptor ncm_data_nop_intf __initdata = {
+static struct usb_interface_descriptor ncm_data_nop_intf = {
.bLength = sizeof ncm_data_nop_intf,
.bDescriptorType = USB_DT_INTERFACE,
@@ -227,7 +227,7 @@ static struct usb_interface_descriptor ncm_data_nop_intf __initdata = {
/* ... but the "real" data interface has two bulk endpoints */
-static struct usb_interface_descriptor ncm_data_intf __initdata = {
+static struct usb_interface_descriptor ncm_data_intf = {
.bLength = sizeof ncm_data_intf,
.bDescriptorType = USB_DT_INTERFACE,
@@ -242,7 +242,7 @@ static struct usb_interface_descriptor ncm_data_intf __initdata = {
/* full speed support: */
-static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = {
+static struct usb_endpoint_descriptor fs_ncm_notify_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -252,7 +252,7 @@ static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = {
.bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
};
-static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = {
+static struct usb_endpoint_descriptor fs_ncm_in_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -260,7 +260,7 @@ static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = {
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
-static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = {
+static struct usb_endpoint_descriptor fs_ncm_out_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -268,7 +268,7 @@ static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = {
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
-static struct usb_descriptor_header *ncm_fs_function[] __initdata = {
+static struct usb_descriptor_header *ncm_fs_function[] = {
(struct usb_descriptor_header *) &ncm_iad_desc,
/* CDC NCM control descriptors */
(struct usb_descriptor_header *) &ncm_control_intf,
@@ -287,7 +287,7 @@ static struct usb_descriptor_header *ncm_fs_function[] __initdata = {
/* high speed support: */
-static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = {
+static struct usb_endpoint_descriptor hs_ncm_notify_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -296,7 +296,7 @@ static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = {
.wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT),
.bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
};
-static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = {
+static struct usb_endpoint_descriptor hs_ncm_in_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -305,7 +305,7 @@ static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = {
.wMaxPacketSize = cpu_to_le16(512),
};
-static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = {
+static struct usb_endpoint_descriptor hs_ncm_out_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
@@ -314,7 +314,7 @@ static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = {
.wMaxPacketSize = cpu_to_le16(512),
};
-static struct usb_descriptor_header *ncm_hs_function[] __initdata = {
+static struct usb_descriptor_header *ncm_hs_function[] = {
(struct usb_descriptor_header *) &ncm_iad_desc,
/* CDC NCM control descriptors */
(struct usb_descriptor_header *) &ncm_control_intf,
@@ -816,11 +816,19 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (alt > 1)
goto fail;
- if (ncm->port.in_ep->driver_data) {
- DBG(cdev, "reset ncm\n");
- gether_disconnect(&ncm->port);
- ncm_reset_values(ncm);
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ if (alt == 0) {
+#endif
+ if (ncm->port.in_ep->driver_data) {
+ DBG(cdev, "reset ncm\n");
+ printk(KERN_DEBUG "usb: %s gether_disconnect\n",
+ __func__);
+ gether_disconnect(&ncm->port);
+ ncm_reset_values(ncm);
+ }
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
}
+#endif
/*
* CDC Network only sends data in non-default altsettings.
@@ -853,9 +861,17 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
return PTR_ERR(net);
}
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ /*
+ * we don't need below code, because devguru host driver can't
+ * accpet SpeedChange/Connect Notify while Alterate Setting
+ * which call ncm_set_alt()
+ */
+#else
spin_lock(&ncm->lock);
ncm_notify(ncm);
spin_unlock(&ncm->lock);
+#endif
} else
goto fail;
@@ -1163,8 +1179,7 @@ static void ncm_close(struct gether *geth)
/* ethernet function driver setup/binding */
-static int __init
-ncm_bind(struct usb_configuration *c, struct usb_function *f)
+static int ncm_bind(struct usb_configuration *c, struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct f_ncm *ncm = func_to_ncm(f);
@@ -1328,7 +1343,7 @@ ncm_unbind(struct usb_configuration *c, struct usb_function *f)
* Caller must have called @gether_setup(). Caller is also responsible
* for calling @gether_cleanup() before module unload.
*/
-int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
+int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
{
struct f_ncm *ncm;
int status;
@@ -1373,19 +1388,30 @@ int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
ncm = kzalloc(sizeof *ncm, GFP_KERNEL);
if (!ncm)
return -ENOMEM;
-
+ printk(KERN_DEBUG "usb: %s before MAC:%02X:%02X:%02X:%02X:%02X:%02X\n",
+ __func__, ethaddr[0], ethaddr[1],
+ ethaddr[2], ethaddr[3], ethaddr[4],
+ ethaddr[5]);
/* export host's Ethernet address in CDC format */
snprintf(ncm->ethaddr, sizeof ncm->ethaddr,
"%02X%02X%02X%02X%02X%02X",
ethaddr[0], ethaddr[1], ethaddr[2],
ethaddr[3], ethaddr[4], ethaddr[5]);
+ printk(KERN_DEBUG "usb: %s after MAC:%02X:%02X:%02X:%02X:%02X:%02X\n",
+ __func__, ncm->ethaddr[0], ncm->ethaddr[1],
+ ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4],
+ ncm->ethaddr[5]);
ncm_string_defs[1].s = ncm->ethaddr;
spin_lock_init(&ncm->lock);
ncm_reset_values(ncm);
ncm->port.is_fixed = true;
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ ncm->port.func.name = "ncm";
+#else
ncm->port.func.name = "cdc_network";
+#endif
ncm->port.func.strings = ncm_strings;
/* descriptors are per-instance copies */
ncm->port.func.bind = ncm_bind;
diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c
index fa12ec8..1f953b6 100644
--- a/drivers/usb/gadget/f_rndis.c
+++ b/drivers/usb/gadget/f_rndis.c
@@ -26,7 +26,7 @@
#include <linux/slab.h>
#include <linux/kernel.h>
-#include <linux/device.h>
+#include <linux/platform_device.h>
#include <linux/etherdevice.h>
#include <asm/atomic.h>
@@ -86,10 +86,14 @@ struct f_rndis {
struct gether port;
u8 ctrl_id, data_id;
u8 ethaddr[ETH_ALEN];
+ u32 vendorID;
+ const char *manufacturer;
int config;
+
struct rndis_ep_descs fs;
struct rndis_ep_descs hs;
+ struct rndis_ep_descs ss;
struct usb_ep *notify;
struct usb_endpoint_descriptor *notify_desc;
@@ -105,7 +109,9 @@ static inline struct f_rndis *func_to_rndis(struct usb_function *f)
/* peak (theoretical) bulk transfer rate in bits-per-second */
static unsigned int bitrate(struct usb_gadget *g)
{
- if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+ if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER)
+ return 13 * 1024 * 8 * 1000 * 8;
+ else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
return 13 * 512 * 8 * 1000 * 8;
else
return 19 * 64 * 1 * 1000 * 8;
@@ -187,12 +193,11 @@ static struct usb_interface_assoc_descriptor
rndis_iad_descriptor = {
.bLength = sizeof rndis_iad_descriptor,
.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
-
.bFirstInterface = 0, /* XXX, hardcoded */
.bInterfaceCount = 2, // control + data
.bFunctionClass = USB_CLASS_COMM,
.bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET,
- .bFunctionProtocol = USB_CDC_PROTO_NONE,
+ .bFunctionProtocol = USB_CDC_ACM_PROTO_VENDOR,
/* .iFunction = DYNAMIC */
};
@@ -285,6 +290,76 @@ static struct usb_descriptor_header *eth_hs_function[] = {
NULL,
};
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT),
+ .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+
+static struct usb_ss_ep_comp_descriptor ss_intr_comp_desc = {
+ .bLength = sizeof ss_intr_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 3 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+ .wBytesPerInterval = cpu_to_le16(STATUS_BYTECOUNT),
+};
+
+static struct usb_endpoint_descriptor ss_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_bulk_comp_desc = {
+ .bLength = sizeof ss_bulk_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+};
+
+static struct usb_descriptor_header *eth_ss_function[] = {
+ (struct usb_descriptor_header *) &rndis_iad_descriptor,
+
+ /* control interface matches ACM, not Ethernet */
+ (struct usb_descriptor_header *) &rndis_control_intf,
+ (struct usb_descriptor_header *) &header_desc,
+ (struct usb_descriptor_header *) &call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &rndis_acm_descriptor,
+ (struct usb_descriptor_header *) &rndis_union_desc,
+ (struct usb_descriptor_header *) &ss_notify_desc,
+ (struct usb_descriptor_header *) &ss_intr_comp_desc,
+
+ /* data interface has no altsetting */
+ (struct usb_descriptor_header *) &rndis_data_intf,
+ (struct usb_descriptor_header *) &ss_in_desc,
+ (struct usb_descriptor_header *) &ss_bulk_comp_desc,
+ (struct usb_descriptor_header *) &ss_out_desc,
+ (struct usb_descriptor_header *) &ss_bulk_comp_desc,
+ NULL,
+};
+
/* string descriptors: */
static struct usb_string rndis_string_defs[] = {
@@ -486,10 +561,14 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
usb_ep_disable(rndis->notify);
} else {
VDBG(cdev, "init rndis ctrl %d\n", intf);
- rndis->notify_desc = ep_choose(cdev->gadget,
- rndis->hs.notify,
- rndis->fs.notify);
}
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER)
+ rndis->notify_desc = rndis->ss.notify;
+ else
+ rndis->notify_desc = ep_choose(cdev->gadget,
+ rndis->hs.notify,
+ rndis->fs.notify);
usb_ep_enable(rndis->notify, rndis->notify_desc);
rndis->notify->driver_data = rndis;
@@ -503,10 +582,16 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
if (!rndis->port.in) {
DBG(cdev, "init rndis\n");
+ }
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER) {
+ rndis->port.in = rndis->ss.in;
+ rndis->port.out = rndis->ss.out;
+ } else {
rndis->port.in = ep_choose(cdev->gadget,
- rndis->hs.in, rndis->fs.in);
+ rndis->hs.in, rndis->fs.in);
rndis->port.out = ep_choose(cdev->gadget,
- rndis->hs.out, rndis->fs.out);
+ rndis->hs.out, rndis->fs.out);
}
/* Avoid ZLPs; they can be troublesome. */
@@ -695,6 +780,27 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
f->hs_descriptors, &hs_notify_desc);
}
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ ss_in_desc.bEndpointAddress =
+ fs_in_desc.bEndpointAddress;
+ ss_out_desc.bEndpointAddress =
+ fs_out_desc.bEndpointAddress;
+ ss_notify_desc.bEndpointAddress =
+ fs_notify_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->ss_descriptors = usb_copy_descriptors(eth_ss_function);
+ if (!f->ss_descriptors)
+ goto fail;
+
+ rndis->ss.in = usb_find_endpoint(eth_ss_function,
+ f->ss_descriptors, &ss_in_desc);
+ rndis->ss.out = usb_find_endpoint(eth_ss_function,
+ f->ss_descriptors, &ss_out_desc);
+ rndis->ss.notify = usb_find_endpoint(eth_ss_function,
+ f->ss_descriptors, &ss_notify_desc);
+ }
+
rndis->port.open = rndis_open;
rndis->port.close = rndis_close;
@@ -706,12 +812,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0);
rndis_set_host_mac(rndis->config, rndis->ethaddr);
-#if 0
-// FIXME
- if (rndis_set_param_vendor(rndis->config, vendorID,
- manufacturer))
- goto fail0;
-#endif
+ if (rndis_set_param_vendor(rndis->config, rndis->vendorID,
+ rndis->manufacturer))
+ goto fail;
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
@@ -719,12 +822,15 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f)
*/
DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
rndis->port.in_ep->name, rndis->port.out_ep->name,
rndis->notify->name);
return 0;
fail:
+ if (gadget_is_superspeed(c->cdev->gadget) && f->ss_descriptors)
+ usb_free_descriptors(f->ss_descriptors);
if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors)
usb_free_descriptors(f->hs_descriptors);
if (f->descriptors)
@@ -756,6 +862,10 @@ rndis_unbind(struct usb_configuration *c, struct usb_function *f)
rndis_deregister(rndis->config);
rndis_exit();
+ rndis_string_defs[0].id = 0;
+
+ if (gadget_is_superspeed(c->cdev->gadget))
+ usb_free_descriptors(f->ss_descriptors);
if (gadget_is_dualspeed(c->cdev->gadget))
usb_free_descriptors(f->hs_descriptors);
usb_free_descriptors(f->descriptors);
@@ -786,7 +896,8 @@ static inline bool can_support_rndis(struct usb_configuration *c)
* for calling @gether_cleanup() before module unload.
*/
int
-rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
+rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+ u32 vendorID, const char *manufacturer)
{
struct f_rndis *rndis;
int status;
@@ -831,6 +942,8 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
goto fail;
memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);
+ rndis->vendorID = vendorID;
+ rndis->manufacturer = manufacturer;
/* RNDIS activates when the host changes this filter */
rndis->port.cdc_filter = 0;
diff --git a/drivers/usb/gadget/f_sdb.c b/drivers/usb/gadget/f_sdb.c
new file mode 100644
index 0000000..ae717f9
--- /dev/null
+++ b/drivers/usb/gadget/f_sdb.c
@@ -0,0 +1,770 @@
+/*
+ * Gadget Driver for Samsung SDB (based on Android ADB)
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#define SDB_BULK_BUFFER_SIZE 4096
+
+/* number of tx requests to allocate */
+#define SDB_TX_REQ_MAX 4
+
+static const char sdb_shortname[] = "samsung_sdb";
+
+static DEFINE_MUTEX(sdb_lock);
+
+struct sdb_ep_descs {
+ struct usb_endpoint_descriptor *in;
+ struct usb_endpoint_descriptor *out;
+};
+
+struct f_sdb {
+ struct usb_function function;
+ u8 inf_id;
+
+ struct sdb_ep_descs fs;
+ struct sdb_ep_descs hs;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+
+ struct list_head bulk_in_q;
+};
+
+struct sdb_dev {
+ struct f_sdb *sdb_func;
+ spinlock_t lock;
+
+ int online;
+ int error;
+
+ atomic_t read_excl;
+ atomic_t write_excl;
+ atomic_t open_excl;
+
+ struct list_head *tx_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+
+ struct usb_request *rx_req;
+ int rx_done;
+};
+
+static struct usb_interface_descriptor sdb_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xFF,
+ .bInterfaceSubClass = 0x20,
+ .bInterfaceProtocol = 0x02,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_endpoint_descriptor sdb_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* .wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_endpoint_descriptor sdb_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* .wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_descriptor_header *fs_sdb_descs[] = {
+ (struct usb_descriptor_header *) &sdb_interface_desc,
+ (struct usb_descriptor_header *) &sdb_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &sdb_fullspeed_out_desc,
+ NULL,
+};
+
+static struct usb_endpoint_descriptor sdb_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ /* bEndpointAddress copied from sdb_fullspeed_in_desc
+ during sdb_function_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor sdb_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ /* bEndpointAddress copied from sdb_fullspeed_in_desc
+ during sdb_function_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *hs_sdb_descs[] = {
+ (struct usb_descriptor_header *) &sdb_interface_desc,
+ (struct usb_descriptor_header *) &sdb_highspeed_in_desc,
+ (struct usb_descriptor_header *) &sdb_highspeed_out_desc,
+ NULL,
+};
+
+/* string descriptors: */
+
+#define F_SDB_IDX 0
+
+/* static strings, in UTF-8 */
+static struct usb_string sdb_string_defs[] = {
+ [F_SDB_IDX].s = "Samsung SDB",
+ { /* ZEROES END LIST */ },
+};
+
+static struct usb_gadget_strings sdb_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = sdb_string_defs,
+};
+
+static struct usb_gadget_strings *sdb_strings[] = {
+ &sdb_string_table,
+ NULL,
+};
+
+/* temporary variable used between sdb_open() and sdb_gadget_bind() */
+static struct sdb_dev *_sdb_dev;
+
+
+static inline struct f_sdb *func_to_sdb(struct usb_function *f)
+{
+ return container_of(f, struct f_sdb, function);
+}
+
+
+static struct usb_request *sdb_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void sdb_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int _sdb_lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void _sdb_unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+static void sdb_req_put(struct sdb_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ if (!dev || !req)
+ return;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (head)
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request *sdb_req_get(struct sdb_dev *dev,
+ struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ if (!dev)
+ return NULL;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (!head)
+ req = NULL;
+ else {
+ if (list_empty(head)) {
+ req = NULL;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void sdb_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct sdb_dev *dev = _sdb_dev;
+ struct f_sdb *sdb_func = ep->driver_data;
+
+ if (req->status != 0)
+ dev->error = 1;
+
+ sdb_req_put(dev, &sdb_func->bulk_in_q, req);
+ wake_up(&dev->write_wq);
+}
+
+static void sdb_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct sdb_dev *dev = _sdb_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->error = 1;
+
+ wake_up(&dev->read_wq);
+}
+
+static int sdb_create_bulk_endpoints(struct f_sdb *sdb_func,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc)
+{
+ struct usb_composite_dev *cdev = sdb_func->function.config->cdev;
+ struct usb_request *req;
+ struct sdb_dev *dev = _sdb_dev;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "sdb_create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ ERROR(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = cdev; /* claim the endpoint */
+ sdb_func->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ ERROR(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for sdb ep_out got %s\n", ep->name);
+ ep->driver_data = cdev; /* claim the endpoint */
+ sdb_func->ep_out = ep;
+
+ /* now allocate requests for our endpoints */
+ req = sdb_request_new(sdb_func->ep_out, SDB_BULK_BUFFER_SIZE);
+ if (!req)
+ return -ENOMEM;
+ req->complete = sdb_complete_out;
+ dev->rx_req = req;
+
+ for (i = 0; i < SDB_TX_REQ_MAX; i++) {
+ req = sdb_request_new(sdb_func->ep_in, SDB_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = sdb_complete_in;
+ sdb_req_put(dev, &sdb_func->bulk_in_q, req);
+ }
+
+ return 0;
+
+fail:
+ while (!!(req = sdb_req_get(dev, &sdb_func->bulk_in_q)))
+ sdb_request_free(req, sdb_func->ep_in);
+
+ sdb_request_free(dev->rx_req, sdb_func->ep_out);
+ dev->rx_req = NULL;
+
+ if (sdb_func->ep_in)
+ sdb_func->ep_in->driver_data = NULL;
+ if (sdb_func->ep_out)
+ sdb_func->ep_out->driver_data = NULL;
+
+ printk(KERN_ERR "sdb_bind() could not allocate requests\n");
+ return -ENOMEM;
+}
+
+static ssize_t sdb_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct sdb_dev *dev = fp->private_data;
+ int r = count, xfer;
+ int ret;
+
+ if (count > SDB_BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ if (_sdb_lock(&dev->read_excl))
+ return -EBUSY;
+
+ /* we will block until we're online */
+ while (!(dev->online || dev->error)) {
+ ret = wait_event_interruptible(dev->read_wq,
+ (dev->online || dev->error));
+ if (ret < 0) {
+ _sdb_unlock(&dev->read_excl);
+ return ret;
+ }
+ }
+ if (dev->error) {
+ r = -EIO;
+ goto done;
+ }
+
+requeue_req:
+ /* queue a request */
+ mutex_lock(&sdb_lock);
+ if (!dev->sdb_func || !dev->rx_req)
+ ret = -ENODEV;
+ else {
+ dev->rx_req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->sdb_func->ep_out,
+ dev->rx_req, GFP_ATOMIC);
+ }
+ mutex_unlock(&sdb_lock);
+
+ if (ret < 0) {
+ r = -EIO;
+ dev->error = 1;
+ goto done;
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ dev->error = 1;
+ r = ret;
+ goto done;
+ }
+ if (!dev->error) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (dev->rx_req->actual == 0)
+ goto requeue_req;
+
+ mutex_lock(&sdb_lock);
+ if (!dev->sdb_func || !dev->rx_req)
+ r = -ENODEV;
+ else {
+ xfer = (dev->rx_req->actual < count)
+ ? dev->rx_req->actual : count;
+ if (copy_to_user(buf, dev->rx_req->buf, xfer))
+ r = -EFAULT;
+ }
+ mutex_unlock(&sdb_lock);
+ } else
+ r = -EIO;
+
+done:
+ _sdb_unlock(&dev->read_excl);
+ return r;
+}
+
+static ssize_t sdb_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct sdb_dev *dev = fp->private_data;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+ if (_sdb_lock(&dev->write_excl))
+ return -EBUSY;
+
+ while (count > 0) {
+ if (dev->error) {
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ (!!(req = sdb_req_get(dev, dev->tx_idle))
+ || dev->error));
+
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+
+ if (req != 0) {
+ if (count > SDB_BULK_BUFFER_SIZE)
+ xfer = SDB_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+
+ mutex_lock(&sdb_lock);
+ if (!dev->sdb_func) {
+ mutex_unlock(&sdb_lock);
+ r = -ENODEV;
+ break;
+ } else if (copy_from_user(req->buf, buf, xfer)) {
+ mutex_unlock(&sdb_lock);
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->sdb_func->ep_in,
+ req, GFP_ATOMIC);
+ mutex_unlock(&sdb_lock);
+
+ if (ret < 0) {
+ dev->error = 1;
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+ }
+
+ if (req)
+ sdb_req_put(dev, dev->tx_idle, req);
+
+ _sdb_unlock(&dev->write_excl);
+ return r;
+}
+
+static int sdb_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "sdb_open\n");
+ if (_sdb_lock(&_sdb_dev->open_excl))
+ return -EBUSY;
+
+ fp->private_data = _sdb_dev;
+
+ /* clear the error latch */
+ _sdb_dev->error = 0;
+
+ return 0;
+}
+
+static int sdb_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "sdb_release\n");
+
+ if (_sdb_dev != NULL)
+ _sdb_unlock(&_sdb_dev->open_excl);
+
+ return 0;
+}
+
+/* file operations for SDB device /dev/samsung_sdb */
+static const struct file_operations sdb_fops = {
+ .owner = THIS_MODULE,
+ .read = sdb_read,
+ .write = sdb_write,
+ .open = sdb_open,
+ .release = sdb_release,
+};
+
+static struct miscdevice sdb_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = sdb_shortname,
+ .fops = &sdb_fops,
+};
+
+static int
+sdb_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_sdb *sdb_func = func_to_sdb(f);
+ int id;
+ int ret;
+
+ DBG(cdev, "sdb_function_bind sdb_func: %p\n", sdb_func);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+
+ sdb_func->inf_id = id;
+ sdb_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = sdb_create_bulk_endpoints(sdb_func, &sdb_fullspeed_in_desc,
+ &sdb_fullspeed_out_desc);
+ if (ret)
+ return ret;
+
+ f->descriptors = usb_copy_descriptors(fs_sdb_descs);
+ if (!f->descriptors)
+ goto desc_alloc_fail;
+
+ sdb_func->fs.in = usb_find_endpoint(fs_sdb_descs, f->descriptors,
+ &sdb_fullspeed_in_desc);
+ sdb_func->fs.out = usb_find_endpoint(fs_sdb_descs, f->descriptors,
+ &sdb_fullspeed_out_desc);
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(cdev->gadget)) {
+ sdb_highspeed_in_desc.bEndpointAddress =
+ sdb_fullspeed_in_desc.bEndpointAddress;
+ sdb_highspeed_out_desc.bEndpointAddress =
+ sdb_fullspeed_out_desc.bEndpointAddress;
+
+ f->hs_descriptors = usb_copy_descriptors(hs_sdb_descs);
+ if (!f->hs_descriptors)
+ goto desc_alloc_fail;
+
+ sdb_func->hs.in = usb_find_endpoint(hs_sdb_descs,
+ f->hs_descriptors, &sdb_highspeed_in_desc);
+ sdb_func->hs.out = usb_find_endpoint(hs_sdb_descs,
+ f->hs_descriptors, &sdb_highspeed_out_desc);
+ }
+
+ return 0;
+
+desc_alloc_fail:
+ if (f->descriptors)
+ usb_free_descriptors(f->descriptors);
+
+ return -ENOMEM;
+}
+
+static void
+sdb_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct sdb_dev *dev = _sdb_dev;
+ struct f_sdb *sdb_func = func_to_sdb(f);
+ struct usb_request *req;
+
+ dev->online = 0;
+ dev->error = 1;
+
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+
+ mutex_lock(&sdb_lock);
+
+ while (!!(req = sdb_req_get(dev, &sdb_func->bulk_in_q)))
+ sdb_request_free(req, sdb_func->ep_in);
+
+ sdb_request_free(dev->rx_req, sdb_func->ep_out);
+
+ kfree(sdb_func);
+ dev->sdb_func = NULL;
+ dev->rx_req = NULL;
+
+ mutex_unlock(&sdb_lock);
+
+ wake_up(&dev->read_wq);
+ wake_up(&dev->write_wq);
+}
+
+static int sdb_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct f_sdb *sdb_func = func_to_sdb(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct sdb_dev *dev = _sdb_dev;
+ int ret;
+
+ if (sdb_func->inf_id != intf) {
+ printk(KERN_ERR "sdb_function_set_alt error wrong intf:%d alt:%d\n",
+ intf, alt);
+ return -EINVAL;
+ }
+
+ if (sdb_func->ep_in->driver_data)
+ usb_ep_disable(sdb_func->ep_in);
+ ret = usb_ep_enable(sdb_func->ep_in,
+ ep_choose(cdev->gadget,
+ sdb_func->hs.in, sdb_func->fs.in));
+ if (ret) {
+ printk(KERN_ERR "error, usb_ep_enable for sdb ep_in\n");
+ return ret;
+ }
+ sdb_func->ep_in->driver_data = sdb_func;
+
+ if (sdb_func->ep_out->driver_data)
+ usb_ep_disable(sdb_func->ep_out);
+ ret = usb_ep_enable(sdb_func->ep_out,
+ ep_choose(cdev->gadget,
+ sdb_func->hs.out, sdb_func->fs.out));
+ if (ret) {
+ usb_ep_disable(sdb_func->ep_in);
+ sdb_func->ep_in->driver_data = NULL;
+ printk(KERN_ERR "error, usb_ep_enable for sdb ep_out\n");
+ return ret;
+ }
+ sdb_func->ep_out->driver_data = sdb_func;
+
+ dev->tx_idle = &sdb_func->bulk_in_q;
+ dev->sdb_func = sdb_func;
+ dev->online = 1;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void sdb_function_disable(struct usb_function *f)
+{
+ struct sdb_dev *dev = _sdb_dev;
+ struct f_sdb *sdb_func = func_to_sdb(f);
+
+ dev->online = 0;
+ dev->error = 1;
+
+ spin_lock(&dev->lock);
+ dev->tx_idle = NULL;
+ spin_unlock(&dev->lock);
+
+ usb_ep_disable(sdb_func->ep_in);
+ sdb_func->ep_in->driver_data = NULL;
+
+ usb_ep_disable(sdb_func->ep_out);
+ sdb_func->ep_out->driver_data = NULL;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ wake_up(&dev->write_wq);
+}
+
+static int sdb_setup(struct usb_composite_dev *cdev)
+{
+ struct sdb_dev *dev;
+ int ret;
+
+ printk(KERN_INFO "sdb_bind_config\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ if (sdb_string_defs[F_SDB_IDX].id == 0) {
+ ret = usb_string_id(cdev);
+ if (ret < 0) {
+ kfree(dev);
+ return ret;
+ }
+ sdb_string_defs[F_SDB_IDX].id = ret;
+ sdb_interface_desc.iInterface = ret;
+ }
+
+ spin_lock_init(&dev->lock);
+
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+
+ atomic_set(&dev->open_excl, 0);
+ atomic_set(&dev->read_excl, 0);
+ atomic_set(&dev->write_excl, 0);
+
+
+ /* _sdb_dev must be set before calling usb_gadget_register_driver */
+ _sdb_dev = dev;
+
+ ret = misc_register(&sdb_device);
+ if (ret)
+ goto err1;
+
+ return 0;
+
+err1:
+ kfree(dev);
+ _sdb_dev = NULL;
+ printk(KERN_ERR "sdb gadget driver failed to initialize\n");
+ return ret;
+}
+
+static int sdb_bind_config(struct usb_configuration *c)
+{
+ int ret;
+ struct f_sdb *sdb_func;
+
+ if (!_sdb_dev) {
+ printk(KERN_ERR "Error There is no _sdb_dev!!\n");
+ return -ENODEV;
+ }
+
+ sdb_func = kzalloc(sizeof(*sdb_func), GFP_KERNEL);
+ if (!sdb_func) {
+ printk(KERN_ERR "sdb_func memory alloc failed !!!\n");
+ return -ENOMEM;
+ }
+
+ INIT_LIST_HEAD(&sdb_func->bulk_in_q);
+
+ sdb_func->function.name = "sdb";
+ sdb_func->function.strings = sdb_strings;
+ sdb_func->function.bind = sdb_function_bind;
+ sdb_func->function.unbind = sdb_function_unbind;
+ sdb_func->function.set_alt = sdb_function_set_alt;
+ sdb_func->function.disable = sdb_function_disable;
+
+ ret = usb_add_function(c, &sdb_func->function);
+ if (ret)
+ printk(KERN_ERR "Error in usb_add_function failed for sdb\n");
+
+ return ret;
+}
+
+static void sdb_cleanup(void)
+{
+ struct sdb_dev *dev = _sdb_dev;
+
+ misc_deregister(&sdb_device);
+
+ if (!dev)
+ return;
+ _sdb_dev = NULL;
+ kfree(dev);
+}
diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c
index 8675ca4..7e2ed9c 100644
--- a/drivers/usb/gadget/f_subset.c
+++ b/drivers/usb/gadget/f_subset.c
@@ -69,6 +69,7 @@ struct f_gether {
struct geth_descs fs;
struct geth_descs hs;
+ struct geth_descs ss;
};
static inline struct f_gether *func_to_geth(struct usb_function *f)
@@ -209,6 +210,46 @@ static struct usb_descriptor_header *hs_eth_function[] __initdata = {
NULL,
};
+/* super speed support: */
+
+static struct usb_endpoint_descriptor ss_subset_in_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_endpoint_descriptor ss_subset_out_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor ss_subset_bulk_comp_desc __initdata = {
+ .bLength = sizeof ss_subset_bulk_comp_desc,
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /* the following 2 values can be tweaked if necessary */
+ /* .bMaxBurst = 0, */
+ /* .bmAttributes = 0, */
+};
+
+static struct usb_descriptor_header *ss_eth_function[] __initdata = {
+ (struct usb_descriptor_header *) &subset_data_intf,
+ (struct usb_descriptor_header *) &mdlm_header_desc,
+ (struct usb_descriptor_header *) &mdlm_desc,
+ (struct usb_descriptor_header *) &mdlm_detail_desc,
+ (struct usb_descriptor_header *) &ether_desc,
+ (struct usb_descriptor_header *) &ss_subset_in_desc,
+ (struct usb_descriptor_header *) &ss_subset_bulk_comp_desc,
+ (struct usb_descriptor_header *) &ss_subset_out_desc,
+ (struct usb_descriptor_header *) &ss_subset_bulk_comp_desc,
+ NULL,
+};
+
/* string descriptors: */
static struct usb_string geth_string_defs[] = {
@@ -243,10 +284,16 @@ static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
}
DBG(cdev, "init + activate cdc subset\n");
- geth->port.in = ep_choose(cdev->gadget,
+ if (gadget_is_superspeed(cdev->gadget) &&
+ cdev->gadget->speed == USB_SPEED_SUPER) {
+ geth->port.in = geth->ss.in;
+ geth->port.out = geth->ss.out;
+ } else {
+ geth->port.in = ep_choose(cdev->gadget,
geth->hs.in, geth->fs.in);
- geth->port.out = ep_choose(cdev->gadget,
+ geth->port.out = ep_choose(cdev->gadget,
geth->hs.out, geth->fs.out);
+ }
net = gether_connect(&geth->port);
return IS_ERR(net) ? PTR_ERR(net) : 0;
@@ -322,17 +369,40 @@ geth_bind(struct usb_configuration *c, struct usb_function *f)
f->hs_descriptors, &hs_subset_out_desc);
}
+ if (gadget_is_superspeed(c->cdev->gadget)) {
+ ss_subset_in_desc.bEndpointAddress =
+ fs_subset_in_desc.bEndpointAddress;
+ ss_subset_out_desc.bEndpointAddress =
+ fs_subset_out_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->ss_descriptors = usb_copy_descriptors(ss_eth_function);
+ if (!f->ss_descriptors)
+ goto fail;
+
+ geth->ss.in = usb_find_endpoint(ss_eth_function,
+ f->ss_descriptors, &ss_subset_in_desc);
+ geth->ss.out = usb_find_endpoint(ss_eth_function,
+ f->ss_descriptors, &ss_subset_out_desc);
+ }
+
/* NOTE: all that is done without knowing or caring about
* the network link ... which is unavailable to this code
* until we're activated via set_alt().
*/
DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n",
+ gadget_is_superspeed(c->cdev->gadget) ? "super" :
gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
geth->port.in_ep->name, geth->port.out_ep->name);
return 0;
fail:
+ if (f->descriptors)
+ usb_free_descriptors(f->descriptors);
+ if (f->hs_descriptors)
+ usb_free_descriptors(f->hs_descriptors);
+
/* we might as well release our claims on endpoints */
if (geth->port.out)
geth->port.out_ep->driver_data = NULL;
@@ -347,6 +417,8 @@ fail:
static void
geth_unbind(struct usb_configuration *c, struct usb_function *f)
{
+ if (gadget_is_superspeed(c->cdev->gadget))
+ usb_free_descriptors(f->ss_descriptors);
if (gadget_is_dualspeed(c->cdev->gadget))
usb_free_descriptors(f->hs_descriptors);
usb_free_descriptors(f->descriptors);
diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
index bcdac7c..2db7cb2 100644
--- a/drivers/usb/gadget/gadget_chips.h
+++ b/drivers/usb/gadget/gadget_chips.h
@@ -142,6 +142,18 @@
#define gadget_is_s3c_hsudc(g) 0
#endif
+#ifdef CONFIG_USB_GADGET_S3C_OTGD
+#define gadget_is_s3c(g) !strcmp("s3c-udc", (g)->name)
+#else
+#define gadget_is_s3c(g) 0
+#endif
+
+#ifdef CONFIG_USB_EXYNOS_SS_UDC
+#define gadget_is_exynos_ss_udc(g) (!strcmp("exynos-ss-udc", (g)->name))
+#else
+#define gadget_is_exynos_ss_udc(g) 0
+#endif
+
#ifdef CONFIG_USB_GADGET_EG20T
#define gadget_is_pch(g) (!strcmp("pch_udc", (g)->name))
#else
@@ -215,6 +227,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
return 0x25;
else if (gadget_is_s3c_hsotg(gadget))
return 0x26;
+ else if (gadget_is_s3c(gadget))
+ return 0x26;
else if (gadget_is_pch(gadget))
return 0x27;
else if (gadget_is_ci13xxx_msm(gadget))
@@ -223,6 +237,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
return 0x29;
else if (gadget_is_s3c_hsudc(gadget))
return 0x30;
+ else if (gadget_is_exynos_ss_udc(gadget))
+ return 0x31;
return -ENOENT;
}
diff --git a/drivers/usb/gadget/gadget_gbhc/android.c b/drivers/usb/gadget/gadget_gbhc/android.c
new file mode 100644
index 0000000..ff70f91
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/android.c
@@ -0,0 +1,533 @@
+/*
+ * Gadget Driver for Android
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/platform_device.h>
+
+#include <linux/usb/android_composite.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/gadget.h>
+
+#include "gadget_chips.h"
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module. So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+#include "composite.c"
+
+MODULE_AUTHOR("Mike Lockwood");
+MODULE_DESCRIPTION("Android Composite USB Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+static const char longname[] = "Gadget Android";
+
+/* Default vendor and product IDs, overridden by platform data */
+#define VENDOR_ID 0x18D1
+#define PRODUCT_ID 0x0001
+
+struct android_dev {
+ struct usb_composite_dev *cdev;
+ struct usb_configuration *config;
+ int num_products;
+ struct android_usb_product *products;
+ int num_functions;
+ char **functions;
+
+ int vendor_id;
+ int product_id;
+ int version;
+};
+
+static struct android_dev *_android_dev;
+
+/* string IDs are assigned dynamically */
+
+#define STRING_MANUFACTURER_IDX 0
+#define STRING_PRODUCT_IDX 1
+#define STRING_SERIAL_IDX 2
+
+/* String Table */
+static struct usb_string strings_dev[] = {
+ /* These dummy values should be overridden by platform data */
+ [STRING_MANUFACTURER_IDX].s = "Android",
+ [STRING_PRODUCT_IDX].s = "Android",
+ [STRING_SERIAL_IDX].s = "0123456789ABCDEF",
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dev,
+};
+
+static struct usb_gadget_strings *dev_strings[] = {
+ &stringtab_dev,
+ NULL,
+};
+
+static struct usb_device_descriptor device_desc = {
+ .bLength = sizeof(device_desc),
+ .bDescriptorType = USB_DT_DEVICE,
+ .bcdUSB = __constant_cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_PER_INTERFACE,
+ .idVendor = __constant_cpu_to_le16(VENDOR_ID),
+ .idProduct = __constant_cpu_to_le16(PRODUCT_ID),
+ .bcdDevice = __constant_cpu_to_le16(0xffff),
+ .bNumConfigurations = 1,
+};
+
+static struct list_head _functions = LIST_HEAD_INIT(_functions);
+static bool _are_functions_bound;
+
+static struct android_usb_function *get_function(const char *name)
+{
+ struct android_usb_function *f;
+ list_for_each_entry(f, &_functions, list) {
+ if (!strcmp(name, f->name))
+ return f;
+ }
+ return 0;
+}
+
+static bool are_functions_registered(struct android_dev *dev)
+{
+ char **functions = dev->functions;
+ int i;
+
+ /* Look only for functions required by the board config */
+ for (i = 0; i < dev->num_functions; i++) {
+ char *name = *functions++;
+ bool is_match = false;
+ /* Could reuse get_function() here, but a reverse search
+ * should yield less comparisons overall */
+ struct android_usb_function *f;
+ list_for_each_entry_reverse(f, &_functions, list) {
+ if (!strcmp(name, f->name)) {
+ is_match = true;
+ break;
+ }
+ }
+ if (is_match)
+ continue;
+ else
+ return false;
+ }
+
+ return true;
+}
+
+static bool should_bind_functions(struct android_dev *dev)
+{
+ /* Don't waste time if the main driver hasn't bound */
+ if (!dev->config)
+ return false;
+
+ /* Don't waste time if we've already bound the functions */
+ if (_are_functions_bound)
+ return false;
+
+ /* This call is the most costly, so call it last */
+ if (!are_functions_registered(dev))
+ return false;
+
+ return true;
+}
+
+static void bind_functions(struct android_dev *dev)
+{
+ struct android_usb_function *f;
+ char **functions = dev->functions;
+ int i;
+
+ for (i = 0; i < dev->num_functions; i++) {
+ char *name = *functions++;
+ f = get_function(name);
+ if (f)
+ f->bind_config(dev->config);
+ else
+ printk(KERN_ERR "function %s not found in bind_functions\n", name);
+ }
+
+ _are_functions_bound = true;
+}
+
+static int android_bind_config(struct usb_configuration *c)
+{
+ struct android_dev *dev = _android_dev;
+
+ printk(KERN_DEBUG "android_bind_config\n");
+ dev->config = c;
+
+ if (should_bind_functions(dev))
+ bind_functions(dev);
+
+ return 0;
+}
+#ifdef CONFIG_USB_ANDROID_ACM
+ #define ANDROID_DEBUG_CONFIG_STRING "UMS + ACM + ADB(Debug mode)"
+ #define ANDROID_NO_DEBUG_CONFIG_STRING "UMS + ACM"
+#else
+ #define ANDROID_DEBUG_CONFIG_STRING "UMS + ADB (Debugging mode)"
+ #define ANDROID_NO_DEBUG_CONFIG_STRING "UMS Only (Not debugging mode)"
+#endif
+
+static int android_setup_config(struct usb_configuration *c,
+ const struct usb_ctrlrequest *ctrl);
+
+static struct usb_configuration android_config_driver = {
+ .label = ANDROID_NO_DEBUG_CONFIG_STRING,
+ .setup = android_setup_config,
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+ .bMaxPower = 0xFA, /* 500ma */
+};
+
+static int android_setup_config(struct usb_configuration *c,
+ const struct usb_ctrlrequest *ctrl)
+{
+ int i;
+ int ret = -EOPNOTSUPP;
+
+ for (i = 0; i < android_config_driver.next_interface_id; i++) {
+ if (android_config_driver.interface[i]->setup) {
+ ret = android_config_driver.interface[i]->setup(
+ android_config_driver.interface[i], ctrl);
+ if (ret >= 0)
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static int product_has_function(struct android_usb_product *p,
+ struct usb_function *f)
+{
+ char **functions = p->functions;
+ int count = p->num_functions;
+ const char *name = f->name;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ /* For functions with multiple instances, usb_function.name
+ * will have an index appended to the core name (ex: acm0),
+ * while android_usb_product.functions[i] will only have the
+ * core name (ex: acm). So, only compare up to the length of
+ * android_usb_product.functions[i].
+ */
+ if (!strncmp(name, functions[i], strlen(functions[i])))
+ return 1;
+ }
+ return 0;
+}
+
+static int product_matches_functions(struct android_usb_product *p)
+{
+ struct usb_function *f;
+ list_for_each_entry(f, &android_config_driver.functions, list) {
+ if (product_has_function(p, f) == !!f->disabled)
+ return 0;
+ }
+ return 1;
+}
+
+static int get_vendor_id(struct android_dev *dev)
+{
+ struct android_usb_product *p = dev->products;
+ int count = dev->num_products;
+ int i;
+
+ if (p) {
+ for (i = 0; i < count; i++, p++) {
+ if (p->vendor_id && product_matches_functions(p))
+ return p->vendor_id;
+ }
+ }
+ /* use default vendor ID */
+ return dev->vendor_id;
+}
+
+static int get_product_id(struct android_dev *dev)
+{
+ struct android_usb_product *p = dev->products;
+ int count = dev->num_products;
+ int i;
+
+ if (p) {
+ for (i = 0; i < count; i++, p++) {
+ if (product_matches_functions(p))
+ return p->product_id;
+ }
+ }
+ /* use default product ID */
+ return dev->product_id;
+}
+
+static int android_bind(struct usb_composite_dev *cdev)
+{
+ struct android_dev *dev = _android_dev;
+ struct usb_gadget *gadget = cdev->gadget;
+ int gcnum, id, ret;
+
+ printk(KERN_INFO "android_bind\n");
+
+ /* Allocate string descriptor numbers ... note that string
+ * contents can be overridden by the composite_dev glue.
+ */
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_MANUFACTURER_IDX].id = id;
+ device_desc.iManufacturer = id;
+
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_PRODUCT_IDX].id = id;
+ device_desc.iProduct = id;
+
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ strings_dev[STRING_SERIAL_IDX].id = id;
+ device_desc.iSerialNumber = id;
+
+ /* register our configuration */
+ ret = usb_add_config(cdev, &android_config_driver, android_bind_config);
+ if (ret) {
+ printk(KERN_ERR "usb_add_config failed\n");
+ return ret;
+ }
+
+ gcnum = usb_gadget_controller_number(gadget);
+ if (gcnum >= 0)
+ device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
+ else {
+ /* gadget zero is so simple (for now, no altsettings) that
+ * it SHOULD NOT have problems with bulk-capable hardware.
+ * so just warn about unrcognized controllers -- don't panic.
+ *
+ * things like configuration and altsetting numbering
+ * can need hardware-specific attention though.
+ */
+ pr_warning("%s: controller '%s' not recognized\n",
+ longname, gadget->name);
+ device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
+ }
+
+ usb_gadget_set_selfpowered(gadget);
+ dev->cdev = cdev;
+ device_desc.idVendor = __constant_cpu_to_le16(get_vendor_id(dev));
+ device_desc.idProduct = __constant_cpu_to_le16(get_product_id(dev));
+ cdev->desc.idVendor = device_desc.idVendor;
+ cdev->desc.idProduct = device_desc.idProduct;
+
+ return 0;
+}
+
+static struct usb_composite_driver android_usb_driver = {
+ .name = "android_usb",
+ .dev = &device_desc,
+ .strings = dev_strings,
+ .enable_function = android_enable_function,
+};
+
+void android_register_function(struct android_usb_function *f)
+{
+ struct android_dev *dev = _android_dev;
+
+ printk(KERN_INFO "android_register_function %s\n", f->name);
+ list_add_tail(&f->list, &_functions);
+
+ if (dev && should_bind_functions(dev))
+ bind_functions(dev);
+}
+
+void update_dev_desc(struct android_dev *dev)
+{
+ struct usb_function *f;
+ struct usb_function *last_enabled_f = NULL;
+ int num_enabled = 0;
+ int has_iad = 0;
+
+ dev->cdev->desc.bDeviceClass = USB_CLASS_PER_INTERFACE;
+ dev->cdev->desc.bDeviceSubClass = 0x00;
+ dev->cdev->desc.bDeviceProtocol = 0x00;
+
+ list_for_each_entry(f, &android_config_driver.functions, list) {
+ if (!f->disabled) {
+ num_enabled++;
+ last_enabled_f = f;
+ if (f->descriptors[0]->bDescriptorType ==
+ USB_DT_INTERFACE_ASSOCIATION)
+ has_iad = 1;
+ }
+ if (num_enabled > 1 && has_iad) {
+ dev->cdev->desc.bDeviceClass = USB_CLASS_MISC;
+ dev->cdev->desc.bDeviceSubClass = 0x02;
+ dev->cdev->desc.bDeviceProtocol = 0x01;
+ break;
+ }
+ }
+
+ if (num_enabled == 1) {
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ if (!strcmp(last_enabled_f->name, "rndis")) {
+#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS
+ dev->cdev->desc.bDeviceClass =
+ USB_CLASS_WIRELESS_CONTROLLER;
+#else
+ dev->cdev->desc.bDeviceClass = USB_CLASS_COMM;
+#endif
+ }
+#endif
+ }
+}
+
+void android_enable_function(struct usb_function *f, int enable)
+{
+ struct android_dev *dev = _android_dev;
+ int disable = !enable;
+
+ if (!!f->disabled != disable) {
+ usb_function_set_enabled(f, !disable);
+ if (!strcmp(f->name, "adb")) {
+ if (enable)
+ android_config_driver.label = ANDROID_DEBUG_CONFIG_STRING;
+ else
+ android_config_driver.label = ANDROID_NO_DEBUG_CONFIG_STRING;
+ }
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ if (!strcmp(f->name, "rndis")) {
+ struct usb_function *func;
+ /* Windows does not support other interfaces when RNDIS is enabled,
+ * so we disable UMS and MTP when RNDIS is on.
+ */
+ list_for_each_entry(func, &android_config_driver.functions, list) {
+ if (!strcmp(func->name, "usb_mass_storage")
+ || !strcmp(func->name, "mtp")) {
+ usb_function_set_enabled(func, !enable);
+ }
+ }
+ }
+#endif
+
+ update_dev_desc(dev);
+
+ device_desc.idVendor = __constant_cpu_to_le16(get_vendor_id(dev));
+ device_desc.idProduct = __constant_cpu_to_le16(get_product_id(dev));
+ if (dev->cdev) {
+ dev->cdev->desc.idVendor = device_desc.idVendor;
+ dev->cdev->desc.idProduct = device_desc.idProduct;
+ }
+ /* usb_composite_force_reset(dev->cdev); */
+
+ /* Force reenumeration */
+ if (dev->cdev && dev->cdev->gadget &&
+ dev->cdev->gadget->speed != USB_SPEED_UNKNOWN) {
+ usb_gadget_disconnect(dev->cdev->gadget);
+ msleep(10);
+ usb_gadget_connect(dev->cdev->gadget);
+ }
+ }
+}
+
+static int android_probe(struct platform_device *pdev)
+{
+ struct android_usb_platform_data *pdata = pdev->dev.platform_data;
+ struct android_dev *dev = _android_dev;
+
+ printk(KERN_INFO "android_probe pdata: %p\n", pdata);
+
+ if (pdata) {
+ dev->products = pdata->products;
+ dev->num_products = pdata->num_products;
+ dev->functions = pdata->functions;
+ dev->num_functions = pdata->num_functions;
+ if (pdata->vendor_id) {
+ dev->vendor_id = pdata->vendor_id;
+ device_desc.idVendor =
+ __constant_cpu_to_le16(pdata->vendor_id);
+ }
+ if (pdata->product_id) {
+ dev->product_id = pdata->product_id;
+ device_desc.idProduct =
+ __constant_cpu_to_le16(pdata->product_id);
+ }
+ if (pdata->version)
+ dev->version = pdata->version;
+
+ if (pdata->product_name)
+ strings_dev[STRING_PRODUCT_IDX].s = pdata->product_name;
+ if (pdata->manufacturer_name)
+ strings_dev[STRING_MANUFACTURER_IDX].s =
+ pdata->manufacturer_name;
+ if (pdata->serial_number)
+ strings_dev[STRING_SERIAL_IDX].s = pdata->serial_number;
+ }
+
+ return usb_composite_probe(&android_usb_driver, android_bind);
+}
+
+static struct platform_driver android_platform_driver = {
+ .driver = { .name = "android_usb", },
+ .probe = android_probe,
+};
+
+static int __init init(void)
+{
+ struct android_dev *dev;
+
+ printk(KERN_INFO "android init\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* set default values, which should be overridden by platform data */
+ dev->product_id = PRODUCT_ID;
+ _android_dev = dev;
+
+ return platform_driver_register(&android_platform_driver);
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+ usb_composite_unregister(&android_usb_driver);
+ platform_driver_unregister(&android_platform_driver);
+ kfree(_android_dev);
+ _android_dev = NULL;
+}
+module_exit(cleanup);
diff --git a/drivers/usb/gadget/gadget_gbhc/composite.c b/drivers/usb/gadget/gadget_gbhc/composite.c
new file mode 100644
index 0000000..c9ae8bb
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/composite.c
@@ -0,0 +1,1560 @@
+/*
+ * composite.c - infrastructure for Composite USB Gadgets
+ *
+ * Copyright (C) 2006-2008 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kallsyms.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/utsname.h>
+#include <linux/delay.h>
+#include <linux/kdev_t.h>
+#include <linux/usb/composite.h>
+
+
+/*
+ * The code in this file is utility code, used to build a gadget driver
+ * from one or more "function" drivers, one or more "configuration"
+ * objects, and a "usb_composite_driver" by gluing them together along
+ * with the relevant device-wide data.
+ */
+
+/* big enough to hold our biggest descriptor */
+#define USB_BUFSIZ 1024
+
+static struct usb_composite_driver *composite;
+static int (*composite_gadget_bind)(struct usb_composite_dev *cdev);
+
+/* Some systems will need runtime overrides for the product identifiers
+ * published in the device descriptor, either numbers or strings or both.
+ * String parameters are in UTF-8 (superset of ASCII's 7 bit characters).
+ */
+
+static ushort idVendor;
+module_param(idVendor, ushort, 0);
+MODULE_PARM_DESC(idVendor, "USB Vendor ID");
+
+static ushort idProduct;
+module_param(idProduct, ushort, 0);
+MODULE_PARM_DESC(idProduct, "USB Product ID");
+
+static ushort bcdDevice;
+module_param(bcdDevice, ushort, 0);
+MODULE_PARM_DESC(bcdDevice, "USB Device version (BCD)");
+
+static char *iManufacturer;
+module_param(iManufacturer, charp, 0);
+MODULE_PARM_DESC(iManufacturer, "USB Manufacturer string");
+
+static char *iProduct;
+module_param(iProduct, charp, 0);
+MODULE_PARM_DESC(iProduct, "USB Product string");
+
+static char *iSerialNumber;
+module_param(iSerialNumber, charp, 0);
+MODULE_PARM_DESC(iSerialNumber, "SerialNumber string");
+
+static char composite_manufacturer[50];
+
+/*-------------------------------------------------------------------------*/
+
+static ssize_t enable_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_function *f = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", !f->disabled);
+}
+
+static ssize_t enable_store(
+ struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct usb_function *f = dev_get_drvdata(dev);
+ struct usb_composite_driver *driver = f->config->cdev->driver;
+ int value;
+
+ sscanf(buf, "%d", &value);
+ if (driver->enable_function)
+ driver->enable_function(f, value);
+ else
+ usb_function_set_enabled(f, value);
+
+ return size;
+}
+
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store);
+
+void usb_function_set_enabled(struct usb_function *f, int enabled)
+{
+ f->disabled = !enabled;
+ kobject_uevent(&f->dev->kobj, KOBJ_CHANGE);
+}
+
+
+void usb_composite_force_reset(struct usb_composite_dev *cdev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ /* force reenumeration */
+ if (cdev && cdev->gadget && cdev->gadget->speed != USB_SPEED_UNKNOWN) {
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ usb_gadget_disconnect(cdev->gadget);
+ msleep(10);
+ usb_gadget_connect(cdev->gadget);
+ } else {
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ }
+}
+
+/**
+ * usb_add_function() - add a function to a configuration
+ * @config: the configuration
+ * @function: the function being added
+ * Context: single threaded during gadget setup
+ *
+ * After initialization, each configuration must have one or more
+ * functions added to it. Adding a function involves calling its @bind()
+ * method to allocate resources such as interface and string identifiers
+ * and endpoints.
+ *
+ * This function returns the value of the function's bind(), which is
+ * zero for success else a negative errno value.
+ */
+int usb_add_function(struct usb_configuration *config,
+ struct usb_function *function)
+{
+ struct usb_composite_dev *cdev = config->cdev;
+ int value = -EINVAL;
+ int index;
+
+ DBG(cdev, "adding '%s'/%p to config '%s'/%p\n",
+ function->name, function,
+ config->label, config);
+
+ if (!function->set_alt || !function->disable)
+ goto done;
+
+ index = atomic_inc_return(&cdev->driver->function_count);
+ function->dev = device_create(cdev->driver->class, NULL,
+ MKDEV(0, index), NULL, function->name);
+ if (IS_ERR(function->dev))
+ return PTR_ERR(function->dev);
+
+ value = device_create_file(function->dev, &dev_attr_enable);
+ if (value < 0) {
+ device_destroy(cdev->driver->class, MKDEV(0, index));
+ return value;
+ }
+ dev_set_drvdata(function->dev, function);
+
+ function->config = config;
+ list_add_tail(&function->list, &config->functions);
+
+ /* REVISIT *require* function->bind? */
+ if (function->bind) {
+ value = function->bind(config, function);
+ if (value < 0) {
+ list_del(&function->list);
+ function->config = NULL;
+ }
+ } else
+ value = 0;
+
+ /* We allow configurations that don't work at both speeds.
+ * If we run into a lowspeed Linux system, treat it the same
+ * as full speed ... it's the function drivers that will need
+ * to avoid bulk and ISO transfers.
+ */
+ if (!config->fullspeed && function->descriptors)
+ config->fullspeed = true;
+ if (!config->highspeed && function->hs_descriptors)
+ config->highspeed = true;
+
+done:
+ if (value)
+ DBG(cdev, "adding '%s'/%p --> %d\n",
+ function->name, function, value);
+ return value;
+}
+
+/**
+ * usb_function_deactivate - prevent function and gadget enumeration
+ * @function: the function that isn't yet ready to respond
+ *
+ * Blocks response of the gadget driver to host enumeration by
+ * preventing the data line pullup from being activated. This is
+ * normally called during @bind() processing to change from the
+ * initial "ready to respond" state, or when a required resource
+ * becomes available.
+ *
+ * For example, drivers that serve as a passthrough to a userspace
+ * daemon can block enumeration unless that daemon (such as an OBEX,
+ * MTP, or print server) is ready to handle host requests.
+ *
+ * Not all systems support software control of their USB peripheral
+ * data pullups.
+ *
+ * Returns zero on success, else negative errno.
+ */
+int usb_function_deactivate(struct usb_function *function)
+{
+ struct usb_composite_dev *cdev = function->config->cdev;
+ unsigned long flags;
+ int status = 0;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+
+ if (cdev->deactivations == 0)
+ status = usb_gadget_disconnect(cdev->gadget);
+ if (status == 0)
+ cdev->deactivations++;
+
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ return status;
+}
+
+/**
+ * usb_function_activate - allow function and gadget enumeration
+ * @function: function on which usb_function_activate() was called
+ *
+ * Reverses effect of usb_function_deactivate(). If no more functions
+ * are delaying their activation, the gadget driver will respond to
+ * host enumeration procedures.
+ *
+ * Returns zero on success, else negative errno.
+ */
+int usb_function_activate(struct usb_function *function)
+{
+ struct usb_composite_dev *cdev = function->config->cdev;
+ int status = 0;
+
+ spin_lock(&cdev->lock);
+
+ if (WARN_ON(cdev->deactivations == 0))
+ status = -EINVAL;
+ else {
+ cdev->deactivations--;
+ if (cdev->deactivations == 0)
+ status = usb_gadget_connect(cdev->gadget);
+ }
+
+ spin_unlock(&cdev->lock);
+ return status;
+}
+
+/**
+ * usb_interface_id() - allocate an unused interface ID
+ * @config: configuration associated with the interface
+ * @function: function handling the interface
+ * Context: single threaded during gadget setup
+ *
+ * usb_interface_id() is called from usb_function.bind() callbacks to
+ * allocate new interface IDs. The function driver will then store that
+ * ID in interface, association, CDC union, and other descriptors. It
+ * will also handle any control requests targeted at that interface,
+ * particularly changing its altsetting via set_alt(). There may
+ * also be class-specific or vendor-specific requests to handle.
+ *
+ * All interface identifier should be allocated using this routine, to
+ * ensure that for example different functions don't wrongly assign
+ * different meanings to the same identifier. Note that since interface
+ * identifiers are configuration-specific, functions used in more than
+ * one configuration (or more than once in a given configuration) need
+ * multiple versions of the relevant descriptors.
+ *
+ * Returns the interface ID which was allocated; or -ENODEV if no
+ * more interface IDs can be allocated.
+ */
+int usb_interface_id(struct usb_configuration *config,
+ struct usb_function *function)
+{
+ unsigned id = config->next_interface_id;
+
+ if (id < MAX_CONFIG_INTERFACES) {
+ config->interface[id] = function;
+ config->next_interface_id = id + 1;
+ return id;
+ }
+ return -ENODEV;
+}
+
+static int config_buf(struct usb_configuration *config,
+ enum usb_device_speed speed, void *buf, u8 type)
+{
+ struct usb_config_descriptor *c = buf;
+ struct usb_interface_descriptor *intf;
+ struct usb_interface_assoc_descriptor *iad = NULL;
+ void *next = buf + USB_DT_CONFIG_SIZE;
+ int len = USB_BUFSIZ - USB_DT_CONFIG_SIZE;
+ struct usb_function *f;
+ int status;
+ int interfaceCount = 0;
+ u8 *dest;
+
+ /* write the config descriptor */
+ c = buf;
+ c->bLength = USB_DT_CONFIG_SIZE;
+ c->bDescriptorType = type;
+ /* wTotalLength and bNumInterfaces are written later */
+ c->bConfigurationValue = config->bConfigurationValue;
+ c->iConfiguration = config->iConfiguration;
+ c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes;
+ c->bMaxPower = config->bMaxPower ? : (CONFIG_USB_GADGET_VBUS_DRAW / 2);
+
+ /* There may be e.g. OTG descriptors */
+ if (config->descriptors) {
+ status = usb_descriptor_fillbuf(next, len,
+ config->descriptors);
+ if (status < 0)
+ return status;
+ len -= status;
+ next += status;
+ }
+
+ /* add each function's descriptors */
+ list_for_each_entry(f, &config->functions, list) {
+ struct usb_descriptor_header **descriptors;
+ struct usb_descriptor_header *descriptor;
+
+ if (speed == USB_SPEED_HIGH)
+ descriptors = f->hs_descriptors;
+ else
+ descriptors = f->descriptors;
+ if (f->disabled || !descriptors || descriptors[0] == NULL)
+ continue;
+ status = usb_descriptor_fillbuf(next, len,
+ (const struct usb_descriptor_header **) descriptors);
+ if (status < 0)
+ return status;
+
+ /* set interface numbers dynamically */
+ dest = next;
+ while ((descriptor = *descriptors++) != NULL) {
+ intf = (struct usb_interface_descriptor *)dest;
+ if (intf->bDescriptorType == USB_DT_INTERFACE) {
+ /* don't increment bInterfaceNumber for alternate settings */
+ if (intf->bAlternateSetting == 0)
+ intf->bInterfaceNumber = interfaceCount++;
+ else
+ intf->bInterfaceNumber = interfaceCount - 1;
+ if (iad) {
+ iad->bFirstInterface =
+ intf->bInterfaceNumber;
+ iad = NULL;
+ }
+ } else if (intf->bDescriptorType ==
+ USB_DT_INTERFACE_ASSOCIATION) {
+ /* This will be first if it exists. Save
+ * a pointer to it so we can properly set
+ * bFirstInterface when we process the first
+ * interface.
+ */
+ iad = (struct usb_interface_assoc_descriptor *)
+ dest;
+ }
+ dest += intf->bLength;
+ }
+
+ len -= status;
+ next += status;
+ }
+
+ len = next - buf;
+ c->wTotalLength = cpu_to_le16(len);
+ c->bNumInterfaces = interfaceCount;
+ return len;
+}
+
+static int config_desc(struct usb_composite_dev *cdev, unsigned w_value)
+{
+ struct usb_gadget *gadget = cdev->gadget;
+ struct usb_configuration *c;
+ u8 type = w_value >> 8;
+ enum usb_device_speed speed = USB_SPEED_UNKNOWN;
+
+ if (gadget_is_dualspeed(gadget)) {
+ int hs = 0;
+
+ if (gadget->speed == USB_SPEED_HIGH)
+ hs = 1;
+ if (type == USB_DT_OTHER_SPEED_CONFIG)
+ hs = !hs;
+ if (hs)
+ speed = USB_SPEED_HIGH;
+
+ }
+
+ /* This is a lookup by config *INDEX* */
+ w_value &= 0xff;
+ list_for_each_entry(c, &cdev->configs, list) {
+ /* ignore configs that won't work at this speed */
+ if (speed == USB_SPEED_HIGH) {
+ if (!c->highspeed)
+ continue;
+ } else {
+ if (!c->fullspeed)
+ continue;
+ }
+ if (w_value == 0)
+ return config_buf(c, speed, cdev->req->buf, type);
+ w_value--;
+ }
+ return -EINVAL;
+}
+
+static int count_configs(struct usb_composite_dev *cdev, unsigned type)
+{
+ struct usb_gadget *gadget = cdev->gadget;
+ struct usb_configuration *c;
+ unsigned count = 0;
+ int hs = 0;
+
+ if (gadget_is_dualspeed(gadget)) {
+ if (gadget->speed == USB_SPEED_HIGH)
+ hs = 1;
+ if (type == USB_DT_DEVICE_QUALIFIER)
+ hs = !hs;
+ }
+ list_for_each_entry(c, &cdev->configs, list) {
+ /* ignore configs that won't work at this speed */
+ if (hs) {
+ if (!c->highspeed)
+ continue;
+ } else {
+ if (!c->fullspeed)
+ continue;
+ }
+ count++;
+ }
+ return count;
+}
+
+static void device_qual(struct usb_composite_dev *cdev)
+{
+ struct usb_qualifier_descriptor *qual = cdev->req->buf;
+
+ qual->bLength = sizeof(*qual);
+ qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER;
+ /* POLICY: same bcdUSB and device type info at both speeds */
+ qual->bcdUSB = cdev->desc.bcdUSB;
+ qual->bDeviceClass = cdev->desc.bDeviceClass;
+ qual->bDeviceSubClass = cdev->desc.bDeviceSubClass;
+ qual->bDeviceProtocol = cdev->desc.bDeviceProtocol;
+ /* ASSUME same EP0 fifo size at both speeds */
+ qual->bMaxPacketSize0 = cdev->desc.bMaxPacketSize0;
+ qual->bNumConfigurations = count_configs(cdev, USB_DT_DEVICE_QUALIFIER);
+ qual->bRESERVED = 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void reset_config(struct usb_composite_dev *cdev)
+{
+ struct usb_function *f;
+
+ DBG(cdev, "reset config\n");
+
+ list_for_each_entry(f, &cdev->config->functions, list) {
+ if (f->disable)
+ f->disable(f);
+
+ bitmap_zero(f->endpoints, 32);
+ }
+ cdev->config = NULL;
+}
+
+static int set_config(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl, unsigned number)
+{
+ struct usb_gadget *gadget = cdev->gadget;
+ struct usb_configuration *c = NULL;
+ int result = -EINVAL;
+ unsigned power = gadget_is_otg(gadget) ? 8 : 100;
+ int tmp;
+
+ if (cdev->config)
+ reset_config(cdev);
+
+ if (number) {
+ list_for_each_entry(c, &cdev->configs, list) {
+ if (c->bConfigurationValue == number) {
+ result = 0;
+ break;
+ }
+ }
+ if (result < 0)
+ goto done;
+ } else
+ result = 0;
+
+ INFO(cdev, "%s speed config #%d: %s\n",
+ ({ char *speed;
+ switch (gadget->speed) {
+ case USB_SPEED_LOW: speed = "low"; break;
+ case USB_SPEED_FULL: speed = "full"; break;
+ case USB_SPEED_HIGH: speed = "high"; break;
+ default: speed = "?"; break;
+ } ; speed; }), number, c ? c->label : "unconfigured");
+
+ if (!c)
+ goto done;
+
+ cdev->config = c;
+
+ /* Initialize all interfaces by setting them to altsetting zero. */
+ for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) {
+ struct usb_function *f = c->interface[tmp];
+ struct usb_descriptor_header **descriptors;
+
+ if (!f)
+ break;
+ if (f->disabled)
+ continue;
+
+ /*
+ * Record which endpoints are used by the function. This is used
+ * to dispatch control requests targeted at that endpoint to the
+ * function's setup callback instead of the current
+ * configuration's setup callback.
+ */
+ if (gadget->speed == USB_SPEED_HIGH)
+ descriptors = f->hs_descriptors;
+ else
+ descriptors = f->descriptors;
+
+ for (; *descriptors; ++descriptors) {
+ struct usb_endpoint_descriptor *ep;
+ int addr;
+
+ if ((*descriptors)->bDescriptorType != USB_DT_ENDPOINT)
+ continue;
+
+ ep = (struct usb_endpoint_descriptor *)*descriptors;
+ addr = ((ep->bEndpointAddress & 0x80) >> 3)
+ | (ep->bEndpointAddress & 0x0f);
+ set_bit(addr, f->endpoints);
+ }
+
+ result = f->set_alt(f, tmp, 0);
+ if (result < 0) {
+ DBG(cdev, "interface %d (%s/%p) alt 0 --> %d\n",
+ tmp, f->name, f, result);
+
+ reset_config(cdev);
+ goto done;
+ }
+
+ if (result == USB_GADGET_DELAYED_STATUS) {
+ DBG(cdev,
+ "%s: interface %d (%s) requested delayed status\n",
+ __func__, tmp, f->name);
+ cdev->delayed_status++;
+ DBG(cdev, "delayed_status count %d\n",
+ cdev->delayed_status);
+ }
+ }
+
+ /* when we return, be sure our power usage is valid */
+ power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW;
+done:
+ usb_gadget_vbus_draw(gadget, power);
+
+ schedule_work(&cdev->switch_work);
+
+ if (result >= 0 && cdev->delayed_status)
+ result = USB_GADGET_DELAYED_STATUS;
+ return result;
+}
+
+/**
+ * usb_add_config() - add a configuration to a device.
+ * @cdev: wraps the USB gadget
+ * @config: the configuration, with bConfigurationValue assigned
+ * @bind: the configuration's bind function
+ * Context: single threaded during gadget setup
+ *
+ * One of the main tasks of a composite @bind() routine is to
+ * add each of the configurations it supports, using this routine.
+ *
+ * This function returns the value of the configuration's @bind(), which
+ * is zero for success else a negative errno value. Binding configurations
+ * assigns global resources including string IDs, and per-configuration
+ * resources such as interface IDs and endpoints.
+ */
+int usb_add_config(struct usb_composite_dev *cdev,
+ struct usb_configuration *config,
+ int (*bind)(struct usb_configuration *))
+{
+ int status = -EINVAL;
+ struct usb_configuration *c;
+
+ DBG(cdev, "adding config #%u '%s'/%p\n",
+ config->bConfigurationValue,
+ config->label, config);
+
+ if (!config->bConfigurationValue || !bind)
+ goto done;
+
+ /* Prevent duplicate configuration identifiers */
+ list_for_each_entry(c, &cdev->configs, list) {
+ if (c->bConfigurationValue == config->bConfigurationValue) {
+ status = -EBUSY;
+ goto done;
+ }
+ }
+
+ config->cdev = cdev;
+ list_add_tail(&config->list, &cdev->configs);
+
+ INIT_LIST_HEAD(&config->functions);
+ config->next_interface_id = 0;
+
+ status = bind(config);
+ if (status < 0) {
+ list_del(&config->list);
+ config->cdev = NULL;
+ } else {
+ unsigned i;
+
+ DBG(cdev, "cfg %d/%p speeds:%s%s\n",
+ config->bConfigurationValue, config,
+ config->highspeed ? " high" : "",
+ config->fullspeed
+ ? (gadget_is_dualspeed(cdev->gadget)
+ ? " full"
+ : " full/low")
+ : "");
+
+ for (i = 0; i < MAX_CONFIG_INTERFACES; i++) {
+ struct usb_function *f = config->interface[i];
+
+ if (!f)
+ continue;
+ DBG(cdev, " interface %d = %s/%p\n",
+ i, f->name, f);
+ }
+ }
+
+ /* set_alt(), or next bind(), sets up
+ * ep->driver_data as needed.
+ */
+ usb_ep_autoconfig_reset(cdev->gadget);
+
+done:
+ if (status)
+ DBG(cdev, "added config '%s'/%u --> %d\n", config->label,
+ config->bConfigurationValue, status);
+ return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* We support strings in multiple languages ... string descriptor zero
+ * says which languages are supported. The typical case will be that
+ * only one language (probably English) is used, with I18N handled on
+ * the host side.
+ */
+
+static void collect_langs(struct usb_gadget_strings **sp, __le16 *buf)
+{
+ const struct usb_gadget_strings *s;
+ u16 language;
+ __le16 *tmp;
+
+ while (*sp) {
+ s = *sp;
+ language = cpu_to_le16(s->language);
+ for (tmp = buf; *tmp && tmp < &buf[126]; tmp++) {
+ if (*tmp == language)
+ goto repeat;
+ }
+ *tmp++ = language;
+repeat:
+ sp++;
+ }
+}
+
+static int lookup_string(
+ struct usb_gadget_strings **sp,
+ void *buf,
+ u16 language,
+ int id
+)
+{
+ struct usb_gadget_strings *s;
+ int value;
+
+ while (*sp) {
+ s = *sp++;
+ if (s->language != language)
+ continue;
+ value = usb_gadget_get_string(s, id, buf);
+ if (value > 0)
+ return value;
+ }
+ return -EINVAL;
+}
+
+static int get_string(struct usb_composite_dev *cdev,
+ void *buf, u16 language, int id)
+{
+ struct usb_configuration *c;
+ struct usb_function *f;
+ int len;
+ const char *str;
+
+ /* Yes, not only is USB's I18N support probably more than most
+ * folk will ever care about ... also, it's all supported here.
+ * (Except for UTF8 support for Unicode's "Astral Planes".)
+ */
+
+ /* 0 == report all available language codes */
+ if (id == 0) {
+ struct usb_string_descriptor *s = buf;
+ struct usb_gadget_strings **sp;
+
+ memset(s, 0, 256);
+ s->bDescriptorType = USB_DT_STRING;
+
+ sp = composite->strings;
+ if (sp)
+ collect_langs(sp, s->wData);
+
+ list_for_each_entry(c, &cdev->configs, list) {
+ sp = c->strings;
+ if (sp)
+ collect_langs(sp, s->wData);
+
+ list_for_each_entry(f, &c->functions, list) {
+ sp = f->strings;
+ if (sp)
+ collect_langs(sp, s->wData);
+ }
+ }
+
+ for (len = 0; len <= 126 && s->wData[len]; len++)
+ continue;
+ if (!len)
+ return -EINVAL;
+
+ s->bLength = 2 * (len + 1);
+ return s->bLength;
+ }
+
+ /* Otherwise, look up and return a specified string. First
+ * check if the string has not been overridden.
+ */
+ if (cdev->manufacturer_override == id)
+ str = iManufacturer ?: composite->iManufacturer ?:
+ composite_manufacturer;
+ else if (cdev->product_override == id)
+ str = iProduct ?: composite->iProduct;
+ else if (cdev->serial_override == id)
+ str = iSerialNumber;
+ else
+ str = NULL;
+ if (str) {
+ struct usb_gadget_strings strings = {
+ .language = language,
+ .strings = &(struct usb_string) { 0xff, str }
+ };
+ return usb_gadget_get_string(&strings, 0xff, buf);
+ }
+
+ /* String IDs are device-scoped, so we look up each string
+ * table we're told about. These lookups are infrequent;
+ * simpler-is-better here.
+ */
+ if (composite->strings) {
+ len = lookup_string(composite->strings, buf, language, id);
+ if (len > 0)
+ return len;
+ }
+ list_for_each_entry(c, &cdev->configs, list) {
+ if (c->strings) {
+ len = lookup_string(c->strings, buf, language, id);
+ if (len > 0)
+ return len;
+ }
+ list_for_each_entry(f, &c->functions, list) {
+ if (!f->strings)
+ continue;
+ len = lookup_string(f->strings, buf, language, id);
+ if (len > 0)
+ return len;
+ }
+ }
+ return -EINVAL;
+}
+
+/**
+ * usb_string_id() - allocate an unused string ID
+ * @cdev: the device whose string descriptor IDs are being allocated
+ * Context: single threaded during gadget setup
+ *
+ * @usb_string_id() is called from bind() callbacks to allocate
+ * string IDs. Drivers for functions, configurations, or gadgets will
+ * then store that ID in the appropriate descriptors and string table.
+ *
+ * All string identifier should be allocated using this,
+ * @usb_string_ids_tab() or @usb_string_ids_n() routine, to ensure
+ * that for example different functions don't wrongly assign different
+ * meanings to the same identifier.
+ */
+int usb_string_id(struct usb_composite_dev *cdev)
+{
+ if (cdev->next_string_id < 254) {
+ /* string id 0 is reserved by USB spec for list of
+ * supported languages */
+ /* 255 reserved as well? -- mina86 */
+ cdev->next_string_id++;
+ return cdev->next_string_id;
+ }
+ return -ENODEV;
+}
+
+/**
+ * usb_string_ids() - allocate unused string IDs in batch
+ * @cdev: the device whose string descriptor IDs are being allocated
+ * @str: an array of usb_string objects to assign numbers to
+ * Context: single threaded during gadget setup
+ *
+ * @usb_string_ids() is called from bind() callbacks to allocate
+ * string IDs. Drivers for functions, configurations, or gadgets will
+ * then copy IDs from the string table to the appropriate descriptors
+ * and string table for other languages.
+ *
+ * All string identifier should be allocated using this,
+ * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for
+ * example different functions don't wrongly assign different meanings
+ * to the same identifier.
+ */
+int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str)
+{
+ int next = cdev->next_string_id;
+
+ for (; str->s; ++str) {
+ if (unlikely(next >= 254))
+ return -ENODEV;
+ str->id = ++next;
+ }
+
+ cdev->next_string_id = next;
+
+ return 0;
+}
+
+/**
+ * usb_string_ids_n() - allocate unused string IDs in batch
+ * @c: the device whose string descriptor IDs are being allocated
+ * @n: number of string IDs to allocate
+ * Context: single threaded during gadget setup
+ *
+ * Returns the first requested ID. This ID and next @n-1 IDs are now
+ * valid IDs. At least provided that @n is non-zero because if it
+ * is, returns last requested ID which is now very useful information.
+ *
+ * @usb_string_ids_n() is called from bind() callbacks to allocate
+ * string IDs. Drivers for functions, configurations, or gadgets will
+ * then store that ID in the appropriate descriptors and string table.
+ *
+ * All string identifier should be allocated using this,
+ * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for
+ * example different functions don't wrongly assign different meanings
+ * to the same identifier.
+ */
+int usb_string_ids_n(struct usb_composite_dev *c, unsigned n)
+{
+ unsigned next = c->next_string_id;
+ if (unlikely(n > 254 || (unsigned)next + n > 254))
+ return -ENODEV;
+ c->next_string_id += n;
+ return next + 1;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ if (req->status || req->actual != req->length)
+ DBG((struct usb_composite_dev *) ep->driver_data,
+ "setup complete --> %d, %d/%d\n",
+ req->status, req->actual, req->length);
+}
+
+/*
+ * The setup() callback implements all the ep0 functionality that's
+ * not handled lower down, in hardware or the hardware driver(like
+ * device and endpoint feature flags, and their status). It's all
+ * housekeeping for the gadget function we're implementing. Most of
+ * the work is in config and function specific setup.
+ */
+static int
+composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct usb_request *req = cdev->req;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u8 intf = w_index & 0xFF;
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+ struct usb_function *f = NULL;
+ u8 endp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (!cdev->connected) {
+ cdev->connected = 1;
+ schedule_work(&cdev->switch_work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ /* partial re-init of the response message; the function or the
+ * gadget might need to intercept e.g. a control-OUT completion
+ * when we delegate to it.
+ */
+ req->zero = 0;
+ req->complete = composite_setup_complete;
+ req->length = 0;
+ gadget->ep0->driver_data = cdev;
+
+ switch (ctrl->bRequest) {
+
+ /* we handle all standard USB descriptors */
+ case USB_REQ_GET_DESCRIPTOR:
+ if (ctrl->bRequestType != USB_DIR_IN)
+ goto unknown;
+ switch (w_value >> 8) {
+
+ case USB_DT_DEVICE:
+ cdev->desc.bNumConfigurations =
+ count_configs(cdev, USB_DT_DEVICE);
+ value = min(w_length, (u16) sizeof cdev->desc);
+ memcpy(req->buf, &cdev->desc, value);
+ break;
+ case USB_DT_DEVICE_QUALIFIER:
+ if (!gadget_is_dualspeed(gadget))
+ break;
+ device_qual(cdev);
+ value = min_t(int, w_length,
+ sizeof(struct usb_qualifier_descriptor));
+ break;
+ case USB_DT_OTHER_SPEED_CONFIG:
+ if (!gadget_is_dualspeed(gadget))
+ break;
+ /* FALLTHROUGH */
+ case USB_DT_CONFIG:
+ value = config_desc(cdev, w_value);
+ if (value >= 0)
+ value = min(w_length, (u16) value);
+ break;
+ case USB_DT_STRING:
+ value = get_string(cdev, req->buf,
+ w_index, w_value & 0xff);
+
+ /* Allow functions to handle USB_DT_STRING.
+ * This is required for MTP.
+ */
+ if (value < 0) {
+ struct usb_configuration *cfg;
+ list_for_each_entry(cfg, &cdev->configs, list) {
+ if (cfg && cfg->setup) {
+ value = cfg->setup(cfg, ctrl);
+ if (value >= 0)
+ break;
+ }
+ }
+ }
+
+ if (value >= 0)
+ value = min(w_length, (u16) value);
+ break;
+ }
+ break;
+
+ /* any number of configs can work */
+ case USB_REQ_SET_CONFIGURATION:
+ if (ctrl->bRequestType != 0)
+ goto unknown;
+ if (gadget_is_otg(gadget)) {
+ if (gadget->a_hnp_support)
+ DBG(cdev, "HNP available\n");
+ else if (gadget->a_alt_hnp_support)
+ DBG(cdev, "HNP on another port\n");
+ else
+ VDBG(cdev, "HNP inactive\n");
+ }
+ spin_lock(&cdev->lock);
+ value = set_config(cdev, ctrl, w_value);
+ spin_unlock(&cdev->lock);
+ break;
+ case USB_REQ_GET_CONFIGURATION:
+ if (ctrl->bRequestType != USB_DIR_IN)
+ goto unknown;
+ if (cdev->config)
+ *(u8 *)req->buf = cdev->config->bConfigurationValue;
+ else
+ *(u8 *)req->buf = 0;
+ value = min(w_length, (u16) 1);
+ break;
+
+ /* function drivers must handle get/set altsetting; if there's
+ * no get() method, we know only altsetting zero works.
+ */
+ case USB_REQ_SET_INTERFACE:
+ if (ctrl->bRequestType != USB_RECIP_INTERFACE)
+ goto unknown;
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+ break;
+ f = cdev->config->interface[intf];
+ if (!f)
+ break;
+ if (w_value && !f->set_alt)
+ break;
+ value = f->set_alt(f, w_index, w_value);
+ if (value == USB_GADGET_DELAYED_STATUS) {
+ DBG(cdev,
+ "%s: interface %d (%s) requested delayed status\n",
+ __func__, intf, f->name);
+ cdev->delayed_status++;
+ DBG(cdev, "delayed_status count %d\n",
+ cdev->delayed_status);
+ }
+ break;
+ case USB_REQ_GET_INTERFACE:
+ if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
+ goto unknown;
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+ break;
+ f = cdev->config->interface[intf];
+ if (!f)
+ break;
+ /* lots of interfaces only need altsetting zero... */
+ value = f->get_alt ? f->get_alt(f, w_index) : 0;
+ if (value < 0)
+ break;
+ *((u8 *)req->buf) = value;
+ value = min(w_length, (u16) 1);
+ break;
+ default:
+unknown:
+ VDBG(cdev,
+ "non-core control req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ /* functions always handle their interfaces and endpoints...
+ * punt other recipients (other, WUSB, ...) to the current
+ * configuration code.
+ *
+ * REVISIT it could make sense to let the composite device
+ * take such requests too, if that's ever needed: to work
+ * in config 0, etc.
+ */
+ switch (ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_INTERFACE:
+ if (!cdev->config || intf >= MAX_CONFIG_INTERFACES)
+ break;
+ f = cdev->config->interface[intf];
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f);
+ list_for_each_entry(f, &cdev->config->functions, list) {
+ if (test_bit(endp, f->endpoints))
+ break;
+ }
+ if (&f->list == &cdev->config->functions)
+ f = NULL;
+ break;
+ }
+
+ if (f && f->setup)
+ value = f->setup(f, ctrl);
+ else {
+ struct usb_configuration *c;
+
+ c = cdev->config;
+ if (c && c->setup)
+ value = c->setup(c, ctrl);
+ }
+
+ /* If the vendor request is not processed (value < 0),
+ * call all device registered configure setup callbacks
+ * to process it.
+ * This is used to handle the following cases:
+ * - vendor request is for the device and arrives before
+ * setconfiguration.
+ * - Some devices are required to handle vendor request before
+ * setconfiguration such as MTP, USBNET.
+ */
+
+ if (value < 0) {
+ struct usb_configuration *cfg;
+
+ list_for_each_entry(cfg, &cdev->configs, list) {
+ if (cfg && cfg->setup)
+ value = cfg->setup(cfg, ctrl);
+ }
+ }
+
+ goto done;
+ }
+
+ /* respond with data transfer before status phase? */
+ if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) {
+ req->length = value;
+ req->zero = value < w_length;
+ value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0) {
+ DBG(cdev, "ep_queue --> %d\n", value);
+ req->status = 0;
+ composite_setup_complete(gadget->ep0, req);
+ }
+ } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) {
+ WARN(cdev,
+ "%s: Delayed status not supported for w_length != 0",
+ __func__);
+ }
+
+done:
+ /* device either stalls (value < 0) or reports success */
+ return value;
+}
+
+static void composite_disconnect(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ unsigned long flags;
+
+ /* REVISIT: should we have config and device level
+ * disconnect callbacks?
+ */
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ reset_config(cdev);
+
+ if (composite->disconnect)
+ composite->disconnect(cdev);
+
+ cdev->connected = 0;
+ schedule_work(&cdev->switch_work);
+ spin_unlock_irqrestore(&cdev->lock, flags);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static ssize_t composite_show_suspended(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_gadget *gadget = dev_to_usb_gadget(dev);
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+
+ return sprintf(buf, "%d\n", cdev->suspended);
+}
+
+static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL);
+
+static void
+composite_unbind(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+
+ /* composite_disconnect() must already have been called
+ * by the underlying peripheral controller driver!
+ * so there's no i/o concurrency that could affect the
+ * state protected by cdev->lock.
+ */
+ WARN_ON(cdev->config);
+
+ while (!list_empty(&cdev->configs)) {
+ struct usb_configuration *c;
+
+ c = list_first_entry(&cdev->configs,
+ struct usb_configuration, list);
+ while (!list_empty(&c->functions)) {
+ struct usb_function *f;
+
+ f = list_first_entry(&c->functions,
+ struct usb_function, list);
+ list_del(&f->list);
+ if (f->unbind) {
+ DBG(cdev, "unbind function '%s'/%p\n",
+ f->name, f);
+ f->unbind(c, f);
+ /* may free memory for "f" */
+ }
+ }
+ list_del(&c->list);
+ if (c->unbind) {
+ DBG(cdev, "unbind config '%s'/%p\n", c->label, c);
+ c->unbind(c);
+ /* may free memory for "c" */
+ }
+ }
+ if (composite->unbind)
+ composite->unbind(cdev);
+
+ if (cdev->req) {
+ kfree(cdev->req->buf);
+ usb_ep_free_request(gadget->ep0, cdev->req);
+ }
+ switch_dev_unregister(&cdev->sw_connected);
+ switch_dev_unregister(&cdev->sw_config);
+ device_remove_file(&gadget->dev, &dev_attr_suspended);
+ kfree(cdev);
+ set_gadget_data(gadget, NULL);
+ composite = NULL;
+}
+
+static u8 override_id(struct usb_composite_dev *cdev, u8 *desc)
+{
+ if (!*desc) {
+ int ret = usb_string_id(cdev);
+ if (unlikely(ret < 0))
+ WARNING(cdev, "failed to override string ID\n");
+ else
+ *desc = ret;
+ }
+
+ return *desc;
+}
+
+static void
+composite_switch_work(struct work_struct *data)
+{
+ struct usb_composite_dev *cdev =
+ container_of(data, struct usb_composite_dev, switch_work);
+ struct usb_configuration *config = cdev->config;
+ int connected;
+ unsigned long flags;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->connected != cdev->sw_connected.state) {
+ connected = cdev->connected;
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ switch_set_state(&cdev->sw_connected, connected);
+ } else {
+ spin_unlock_irqrestore(&cdev->lock, flags);
+ }
+
+ if (config)
+ switch_set_state(&cdev->sw_config, config->bConfigurationValue);
+ else
+ switch_set_state(&cdev->sw_config, 0);
+}
+
+static int composite_bind(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev;
+ int status = -ENOMEM;
+
+ cdev = kzalloc(sizeof *cdev, GFP_KERNEL);
+ if (!cdev)
+ return status;
+
+ spin_lock_init(&cdev->lock);
+ cdev->gadget = gadget;
+ set_gadget_data(gadget, cdev);
+ INIT_LIST_HEAD(&cdev->configs);
+
+ /* preallocate control response and buffer */
+ cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL);
+ if (!cdev->req)
+ goto fail;
+ cdev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL);
+ if (!cdev->req->buf)
+ goto fail;
+ cdev->req->complete = composite_setup_complete;
+ gadget->ep0->driver_data = cdev;
+
+ cdev->bufsiz = USB_BUFSIZ;
+ cdev->driver = composite;
+
+ /*
+ * As per USB compliance update, a device that is actively drawing
+ * more than 100mA from USB must report itself as bus-powered in
+ * the GetStatus(DEVICE) call.
+ */
+ if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW)
+ usb_gadget_set_selfpowered(gadget);
+
+ /* interface and string IDs start at zero via kzalloc.
+ * we force endpoints to start unassigned; few controller
+ * drivers will zero ep->driver_data.
+ */
+ usb_ep_autoconfig_reset(cdev->gadget);
+
+ /* composite gadget needs to assign strings for whole device (like
+ * serial number), register function drivers, potentially update
+ * power state and consumption, etc
+ */
+ status = composite_gadget_bind(cdev);
+ if (status < 0)
+ goto fail;
+
+ cdev->sw_connected.name = "usb_connected";
+ status = switch_dev_register(&cdev->sw_connected);
+ if (status < 0)
+ goto fail;
+ cdev->sw_config.name = "usb_configuration";
+ status = switch_dev_register(&cdev->sw_config);
+ if (status < 0)
+ goto fail;
+ INIT_WORK(&cdev->switch_work, composite_switch_work);
+
+ cdev->desc = *composite->dev;
+ cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
+
+ /* standardized runtime overrides for device ID data */
+ if (idVendor)
+ cdev->desc.idVendor = cpu_to_le16(idVendor);
+ if (idProduct)
+ cdev->desc.idProduct = cpu_to_le16(idProduct);
+ if (bcdDevice)
+ cdev->desc.bcdDevice = cpu_to_le16(bcdDevice);
+
+ /* string overrides */
+ if (iManufacturer || !cdev->desc.iManufacturer) {
+ if (!iManufacturer && !composite->iManufacturer &&
+ !*composite_manufacturer)
+ snprintf(composite_manufacturer,
+ sizeof composite_manufacturer,
+ "%s %s with %s",
+ init_utsname()->sysname,
+ init_utsname()->release,
+ gadget->name);
+
+ cdev->manufacturer_override =
+ override_id(cdev, &cdev->desc.iManufacturer);
+ }
+
+ if (iProduct || (!cdev->desc.iProduct && composite->iProduct))
+ cdev->product_override =
+ override_id(cdev, &cdev->desc.iProduct);
+
+ if (iSerialNumber)
+ cdev->serial_override =
+ override_id(cdev, &cdev->desc.iSerialNumber);
+
+ /* has userspace failed to provide a serial number? */
+ if (composite->needs_serial && !cdev->desc.iSerialNumber)
+ WARNING(cdev, "userspace failed to provide iSerialNumber\n");
+
+ /* finish up */
+ status = device_create_file(&gadget->dev, &dev_attr_suspended);
+ if (status)
+ goto fail;
+
+ INFO(cdev, "%s ready\n", composite->name);
+ return 0;
+
+fail:
+ composite_unbind(gadget);
+ return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void
+composite_suspend(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct usb_function *f;
+
+ /* REVISIT: should we have config level
+ * suspend/resume callbacks?
+ */
+ DBG(cdev, "suspend\n");
+ if (cdev->config) {
+ list_for_each_entry(f, &cdev->config->functions, list) {
+ if (f->suspend)
+ f->suspend(f);
+ }
+ }
+ if (composite->suspend)
+ composite->suspend(cdev);
+
+ cdev->suspended = 1;
+
+ usb_gadget_vbus_draw(gadget, 2);
+}
+
+static void
+composite_resume(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct usb_function *f;
+ u8 maxpower;
+
+ /* REVISIT: should we have config level
+ * suspend/resume callbacks?
+ */
+ DBG(cdev, "resume\n");
+ if (composite->resume)
+ composite->resume(cdev);
+ if (cdev->config) {
+ list_for_each_entry(f, &cdev->config->functions, list) {
+ if (f->resume)
+ f->resume(f);
+ }
+
+ maxpower = cdev->config->bMaxPower;
+
+ usb_gadget_vbus_draw(gadget, maxpower ?
+ (2 * maxpower) : CONFIG_USB_GADGET_VBUS_DRAW);
+ }
+
+ cdev->suspended = 0;
+}
+
+static int
+composite_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct usb_function *f = dev_get_drvdata(dev);
+
+ if (!f) {
+ /* this happens when the device is first created */
+ return 0;
+ }
+
+ if (add_uevent_var(env, "FUNCTION=%s", f->name))
+ return -ENOMEM;
+ if (add_uevent_var(env, "ENABLED=%d", !f->disabled))
+ return -ENOMEM;
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_gadget_driver composite_driver = {
+ .speed = USB_SPEED_HIGH,
+
+ .unbind = composite_unbind,
+
+ .setup = composite_setup,
+ .disconnect = composite_disconnect,
+
+ .suspend = composite_suspend,
+ .resume = composite_resume,
+
+ .driver = {
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * usb_composite_probe() - register a composite driver
+ * @driver: the driver to register
+ * @bind: the callback used to allocate resources that are shared across the
+ * whole device, such as string IDs, and add its configurations using
+ * @usb_add_config(). This may fail by returning a negative errno
+ * value; it should return zero on successful initialization.
+ * Context: single threaded during gadget setup
+ *
+ * This function is used to register drivers using the composite driver
+ * framework. The return value is zero, or a negative errno value.
+ * Those values normally come from the driver's @bind method, which does
+ * all the work of setting up the driver to match the hardware.
+ *
+ * On successful return, the gadget is ready to respond to requests from
+ * the host, unless one of its components invokes usb_gadget_disconnect()
+ * while it was binding. That would usually be done in order to wait for
+ * some userspace participation.
+ */
+int usb_composite_probe(struct usb_composite_driver *driver,
+ int (*bind)(struct usb_composite_dev *cdev))
+{
+ if (!driver || !driver->dev || !bind || composite)
+ return -EINVAL;
+
+ if (!driver->name)
+ driver->name = "composite";
+ if (!driver->iProduct)
+ driver->iProduct = driver->name;
+ composite_driver.function = (char *) driver->name;
+ composite_driver.driver.name = driver->name;
+ composite = driver;
+ composite_gadget_bind = bind;
+
+ driver->class = class_create(THIS_MODULE, "usb_composite");
+ if (IS_ERR(driver->class))
+ return PTR_ERR(driver->class);
+ driver->class->dev_uevent = composite_uevent;
+
+ return usb_gadget_probe_driver(&composite_driver, composite_bind);
+}
+
+/**
+ * usb_composite_unregister() - unregister a composite driver
+ * @driver: the driver to unregister
+ *
+ * This function is used to unregister drivers using the composite
+ * driver framework.
+ */
+void usb_composite_unregister(struct usb_composite_driver *driver)
+{
+ if (composite != driver)
+ return;
+ usb_gadget_unregister_driver(&composite_driver);
+}
+
+/**
+ * usb_composite_setup_continue() - Continue with the control transfer
+ * @cdev: the composite device who's control transfer was kept waiting
+ *
+ * This function must be called by the USB function driver to continue
+ * with the control transfer's data/status stage in case it had requested to
+ * delay the data/status stages. A USB function's setup handler (e.g. set_alt())
+ * can request the composite framework to delay the setup request's data/status
+ * stages by returning USB_GADGET_DELAYED_STATUS.
+ */
+void usb_composite_setup_continue(struct usb_composite_dev *cdev)
+{
+ int value;
+ struct usb_request *req = cdev->req;
+ unsigned long flags;
+
+ DBG(cdev, "%s\n", __func__);
+ spin_lock_irqsave(&cdev->lock, flags);
+
+ if (cdev->delayed_status == 0) {
+ WARN(cdev, "%s: Unexpected call\n", __func__);
+
+ } else if (--cdev->delayed_status == 0) {
+ DBG(cdev, "%s: Completing delayed status\n", __func__);
+ req->length = 0;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0) {
+ DBG(cdev, "ep_queue --> %d\n", value);
+ req->status = 0;
+ composite_setup_complete(cdev->gadget->ep0, req);
+ }
+ }
+
+ spin_unlock_irqrestore(&cdev->lock, flags);
+}
+
diff --git a/drivers/usb/gadget/gadget_gbhc/config.c b/drivers/usb/gadget/gadget_gbhc/config.c
new file mode 100644
index 0000000..09084fd
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/config.c
@@ -0,0 +1,192 @@
+/*
+ * usb/gadget/config.c -- simplify building config descriptors
+ *
+ * Copyright (C) 2003 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/device.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+
+/**
+ * usb_descriptor_fillbuf - fill buffer with descriptors
+ * @buf: Buffer to be filled
+ * @buflen: Size of buf
+ * @src: Array of descriptor pointers, terminated by null pointer.
+ *
+ * Copies descriptors into the buffer, returning the length or a
+ * negative error code if they can't all be copied. Useful when
+ * assembling descriptors for an associated set of interfaces used
+ * as part of configuring a composite device; or in other cases where
+ * sets of descriptors need to be marshaled.
+ */
+int
+usb_descriptor_fillbuf(void *buf, unsigned buflen,
+ const struct usb_descriptor_header **src)
+{
+ u8 *dest = buf;
+
+ if (!src)
+ return -EINVAL;
+
+ /* fill buffer from src[] until null descriptor ptr */
+ for (; NULL != *src; src++) {
+ unsigned len = (*src)->bLength;
+
+ if (len > buflen)
+ return -EINVAL;
+ memcpy(dest, *src, len);
+ buflen -= len;
+ dest += len;
+ }
+ return dest - (u8 *)buf;
+}
+
+
+/**
+ * usb_gadget_config_buf - builts a complete configuration descriptor
+ * @config: Header for the descriptor, including characteristics such
+ * as power requirements and number of interfaces.
+ * @desc: Null-terminated vector of pointers to the descriptors (interface,
+ * endpoint, etc) defining all functions in this device configuration.
+ * @buf: Buffer for the resulting configuration descriptor.
+ * @length: Length of buffer. If this is not big enough to hold the
+ * entire configuration descriptor, an error code will be returned.
+ *
+ * This copies descriptors into the response buffer, building a descriptor
+ * for that configuration. It returns the buffer length or a negative
+ * status code. The config.wTotalLength field is set to match the length
+ * of the result, but other descriptor fields (including power usage and
+ * interface count) must be set by the caller.
+ *
+ * Gadget drivers could use this when constructing a config descriptor
+ * in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the
+ * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed.
+ */
+int usb_gadget_config_buf(
+ const struct usb_config_descriptor *config,
+ void *buf,
+ unsigned length,
+ const struct usb_descriptor_header **desc
+)
+{
+ struct usb_config_descriptor *cp = buf;
+ int len;
+
+ /* config descriptor first */
+ if (length < USB_DT_CONFIG_SIZE || !desc)
+ return -EINVAL;
+ *cp = *config;
+
+ /* then interface/endpoint/class/vendor/... */
+ len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf,
+ length - USB_DT_CONFIG_SIZE, desc);
+ if (len < 0)
+ return len;
+ len += USB_DT_CONFIG_SIZE;
+ if (len > 0xffff)
+ return -EINVAL;
+
+ /* patch up the config descriptor */
+ cp->bLength = USB_DT_CONFIG_SIZE;
+ cp->bDescriptorType = USB_DT_CONFIG;
+ cp->wTotalLength = cpu_to_le16(len);
+ cp->bmAttributes |= USB_CONFIG_ATT_ONE;
+ return len;
+}
+
+/**
+ * usb_copy_descriptors - copy a vector of USB descriptors
+ * @src: null-terminated vector to copy
+ * Context: initialization code, which may sleep
+ *
+ * This makes a copy of a vector of USB descriptors. Its primary use
+ * is to support usb_function objects which can have multiple copies,
+ * each needing different descriptors. Functions may have static
+ * tables of descriptors, which are used as templates and customized
+ * with identifiers (for interfaces, strings, endpoints, and more)
+ * as needed by a given function instance.
+ */
+struct usb_descriptor_header **
+usb_copy_descriptors(struct usb_descriptor_header **src)
+{
+ struct usb_descriptor_header **tmp;
+ unsigned bytes;
+ unsigned n_desc;
+ void *mem;
+ struct usb_descriptor_header **ret;
+
+ /* count descriptors and their sizes; then add vector size */
+ for (bytes = 0, n_desc = 0, tmp = src; *tmp; tmp++, n_desc++)
+ bytes += (*tmp)->bLength;
+ bytes += (n_desc + 1) * sizeof(*tmp);
+
+ mem = kmalloc(bytes, GFP_KERNEL);
+ if (!mem)
+ return NULL;
+
+ /* fill in pointers starting at "tmp",
+ * to descriptors copied starting at "mem";
+ * and return "ret"
+ */
+ tmp = mem;
+ ret = mem;
+ mem += (n_desc + 1) * sizeof(*tmp);
+ while (*src) {
+ memcpy(mem, *src, (*src)->bLength);
+ *tmp = mem;
+ tmp++;
+ mem += (*src)->bLength;
+ src++;
+ }
+ *tmp = NULL;
+
+ return ret;
+}
+
+/**
+ * usb_find_endpoint - find a copy of an endpoint descriptor
+ * @src: original vector of descriptors
+ * @copy: copy of @src
+ * @match: endpoint descriptor found in @src
+ *
+ * This returns the copy of the @match descriptor made for @copy. Its
+ * intended use is to help remembering the endpoint descriptor to use
+ * when enabling a given endpoint.
+ */
+struct usb_endpoint_descriptor *
+usb_find_endpoint(
+ struct usb_descriptor_header **src,
+ struct usb_descriptor_header **copy,
+ struct usb_endpoint_descriptor *match
+)
+{
+ while (*src) {
+ if (*src == (void *) match)
+ return (void *)*copy;
+ src++;
+ copy++;
+ }
+ return NULL;
+}
diff --git a/drivers/usb/gadget/gadget_gbhc/epautoconf.c b/drivers/usb/gadget/gadget_gbhc/epautoconf.c
new file mode 100644
index 0000000..06b4c64
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/epautoconf.c
@@ -0,0 +1,361 @@
+/*
+ * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers
+ *
+ * Copyright (C) 2004 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+
+#include <linux/ctype.h>
+#include <linux/string.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "gadget_chips.h"
+
+
+/* we must assign addresses for configurable endpoints (like net2280) */
+static unsigned epnum;
+
+// #define MANY_ENDPOINTS
+#ifdef MANY_ENDPOINTS
+/* more than 15 configurable endpoints */
+static unsigned in_epnum;
+#endif
+
+
+/*
+ * This should work with endpoints from controller drivers sharing the
+ * same endpoint naming convention. By example:
+ *
+ * - ep1, ep2, ... address is fixed, not direction or type
+ * - ep1in, ep2out, ... address and direction are fixed, not type
+ * - ep1-bulk, ep2-bulk, ... address and type are fixed, not direction
+ * - ep1in-bulk, ep2out-iso, ... all three are fixed
+ * - ep-* ... no functionality restrictions
+ *
+ * Type suffixes are "-bulk", "-iso", or "-int". Numbers are decimal.
+ * Less common restrictions are implied by gadget_is_*().
+ *
+ * NOTE: each endpoint is unidirectional, as specified by its USB
+ * descriptor; and isn't specific to a configuration or altsetting.
+ */
+static int
+ep_matches (
+ struct usb_gadget *gadget,
+ struct usb_ep *ep,
+ struct usb_endpoint_descriptor *desc
+)
+{
+ u8 type;
+ const char *tmp;
+ u16 max;
+
+ /* endpoint already claimed? */
+ if (NULL != ep->driver_data)
+ return 0;
+
+ /* only support ep0 for portable CONTROL traffic */
+ type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+ if (USB_ENDPOINT_XFER_CONTROL == type)
+ return 0;
+
+ /* some other naming convention */
+ if ('e' != ep->name[0])
+ return 0;
+
+ /* type-restriction: "-iso", "-bulk", or "-int".
+ * direction-restriction: "in", "out".
+ */
+ if ('-' != ep->name[2]) {
+ tmp = strrchr (ep->name, '-');
+ if (tmp) {
+ switch (type) {
+ case USB_ENDPOINT_XFER_INT:
+ /* bulk endpoints handle interrupt transfers,
+ * except the toggle-quirky iso-synch kind
+ */
+ if ('s' == tmp[2]) // == "-iso"
+ return 0;
+ /* for now, avoid PXA "interrupt-in";
+ * it's documented as never using DATA1.
+ */
+ if (gadget_is_pxa (gadget)
+ && 'i' == tmp [1])
+ return 0;
+ break;
+ case USB_ENDPOINT_XFER_BULK:
+ if ('b' != tmp[1]) // != "-bulk"
+ return 0;
+ break;
+ case USB_ENDPOINT_XFER_ISOC:
+ if ('s' != tmp[2]) // != "-iso"
+ return 0;
+ }
+ } else {
+ tmp = ep->name + strlen (ep->name);
+ }
+
+ /* direction-restriction: "..in-..", "out-.." */
+ tmp--;
+ if (!isdigit (*tmp)) {
+ if (desc->bEndpointAddress & USB_DIR_IN) {
+ if ('n' != *tmp)
+ return 0;
+ } else {
+ if ('t' != *tmp)
+ return 0;
+ }
+ }
+ }
+
+ /*
+ * If the protocol driver hasn't yet decided on wMaxPacketSize
+ * and wants to know the maximum possible, provide the info.
+ */
+ if (desc->wMaxPacketSize == 0)
+ desc->wMaxPacketSize = cpu_to_le16(ep->maxpacket);
+
+ /* endpoint maxpacket size is an input parameter, except for bulk
+ * where it's an output parameter representing the full speed limit.
+ * the usb spec fixes high speed bulk maxpacket at 512 bytes.
+ */
+ max = 0x7ff & le16_to_cpu(desc->wMaxPacketSize);
+ switch (type) {
+ case USB_ENDPOINT_XFER_INT:
+ /* INT: limit 64 bytes full speed, 1024 high speed */
+ if (!gadget->is_dualspeed && max > 64)
+ return 0;
+ /* FALLTHROUGH */
+
+ case USB_ENDPOINT_XFER_ISOC:
+ /* ISO: limit 1023 bytes full speed, 1024 high speed */
+ if (ep->maxpacket < max)
+ return 0;
+ if (!gadget->is_dualspeed && max > 1023)
+ return 0;
+
+ /* BOTH: "high bandwidth" works only at high speed */
+ if ((desc->wMaxPacketSize & cpu_to_le16(3<<11))) {
+ if (!gadget->is_dualspeed)
+ return 0;
+ /* configure your hardware with enough buffering!! */
+ }
+ break;
+ }
+
+ /* MATCH!! */
+
+ /* report address */
+ desc->bEndpointAddress &= USB_DIR_IN;
+ if (isdigit (ep->name [2])) {
+ u8 num = simple_strtoul (&ep->name [2], NULL, 10);
+ desc->bEndpointAddress |= num;
+#ifdef MANY_ENDPOINTS
+ } else if (desc->bEndpointAddress & USB_DIR_IN) {
+ if (++in_epnum > 15)
+ return 0;
+ desc->bEndpointAddress = USB_DIR_IN | in_epnum;
+#endif
+ } else {
+ if (++epnum > 15)
+ return 0;
+ desc->bEndpointAddress |= epnum;
+ }
+
+ /* report (variable) full speed bulk maxpacket */
+ if (USB_ENDPOINT_XFER_BULK == type) {
+ int size = ep->maxpacket;
+
+ /* min() doesn't work on bitfields with gcc-3.5 */
+ if (size > 64)
+ size = 64;
+ desc->wMaxPacketSize = cpu_to_le16(size);
+ }
+ return 1;
+}
+
+static struct usb_ep *
+find_ep (struct usb_gadget *gadget, const char *name)
+{
+ struct usb_ep *ep;
+
+ list_for_each_entry (ep, &gadget->ep_list, ep_list) {
+ if (0 == strcmp (ep->name, name))
+ return ep;
+ }
+ return NULL;
+}
+
+/**
+ * usb_ep_autoconfig - choose an endpoint matching the descriptor
+ * @gadget: The device to which the endpoint must belong.
+ * @desc: Endpoint descriptor, with endpoint direction and transfer mode
+ * initialized. For periodic transfers, the maximum packet
+ * size must also be initialized. This is modified on success.
+ *
+ * By choosing an endpoint to use with the specified descriptor, this
+ * routine simplifies writing gadget drivers that work with multiple
+ * USB device controllers. The endpoint would be passed later to
+ * usb_ep_enable(), along with some descriptor.
+ *
+ * That second descriptor won't always be the same as the first one.
+ * For example, isochronous endpoints can be autoconfigured for high
+ * bandwidth, and then used in several lower bandwidth altsettings.
+ * Also, high and full speed descriptors will be different.
+ *
+ * Be sure to examine and test the results of autoconfiguration on your
+ * hardware. This code may not make the best choices about how to use the
+ * USB controller, and it can't know all the restrictions that may apply.
+ * Some combinations of driver and hardware won't be able to autoconfigure.
+ *
+ * On success, this returns an un-claimed usb_ep, and modifies the endpoint
+ * descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value
+ * is initialized as if the endpoint were used at full speed. To prevent
+ * the endpoint from being returned by a later autoconfig call, claim it
+ * by assigning ep->driver_data to some non-null value.
+ *
+ * On failure, this returns a null endpoint descriptor.
+ */
+struct usb_ep *usb_ep_autoconfig (
+ struct usb_gadget *gadget,
+ struct usb_endpoint_descriptor *desc
+)
+{
+ struct usb_ep *ep;
+ u8 type;
+
+ type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+
+ /* First, apply chip-specific "best usage" knowledge.
+ * This might make a good usb_gadget_ops hook ...
+ */
+ if (gadget_is_net2280 (gadget) && type == USB_ENDPOINT_XFER_INT) {
+ /* ep-e, ep-f are PIO with only 64 byte fifos */
+ ep = find_ep (gadget, "ep-e");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep-f");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+
+ } else if (gadget_is_goku (gadget)) {
+ if (USB_ENDPOINT_XFER_INT == type) {
+ /* single buffering is enough */
+ ep = find_ep (gadget, "ep3-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ } else if (USB_ENDPOINT_XFER_BULK == type
+ && (USB_DIR_IN & desc->bEndpointAddress)) {
+ /* DMA may be available */
+ ep = find_ep (gadget, "ep2-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ }
+
+#ifdef CONFIG_BLACKFIN
+ } else if (gadget_is_musbhdrc(gadget)) {
+ if ((USB_ENDPOINT_XFER_BULK == type) ||
+ (USB_ENDPOINT_XFER_ISOC == type)) {
+ if (USB_DIR_IN & desc->bEndpointAddress)
+ ep = find_ep (gadget, "ep5in");
+ else
+ ep = find_ep (gadget, "ep6out");
+ } else if (USB_ENDPOINT_XFER_INT == type) {
+ if (USB_DIR_IN & desc->bEndpointAddress)
+ ep = find_ep(gadget, "ep1in");
+ else
+ ep = find_ep(gadget, "ep2out");
+ } else
+ ep = NULL;
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+#endif
+ } else if (gadget_is_s3c(gadget)) {
+ if (USB_ENDPOINT_XFER_INT == type) {
+ /* single buffering is enough */
+ ep = find_ep (gadget, "ep3-int");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep6-int");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep9-int");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ } else if (USB_ENDPOINT_XFER_BULK == type
+ && (USB_DIR_IN & desc->bEndpointAddress)) {
+ ep = find_ep (gadget, "ep2-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep5-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep8-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ } else if (USB_ENDPOINT_XFER_BULK == type
+ && !(USB_DIR_IN & desc->bEndpointAddress)) {
+ ep = find_ep (gadget, "ep1-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep4-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep7-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ }
+ }
+
+ /* Second, look at endpoints until an unclaimed one looks usable */
+ list_for_each_entry (ep, &gadget->ep_list, ep_list) {
+ if (ep_matches (gadget, ep, desc))
+ return ep;
+ }
+
+ /* Fail */
+ return NULL;
+}
+
+/**
+ * usb_ep_autoconfig_reset - reset endpoint autoconfig state
+ * @gadget: device for which autoconfig state will be reset
+ *
+ * Use this for devices where one configuration may need to assign
+ * endpoint resources very differently from the next one. It clears
+ * state such as ep->driver_data and the record of assigned endpoints
+ * used by usb_ep_autoconfig().
+ */
+void usb_ep_autoconfig_reset (struct usb_gadget *gadget)
+{
+ struct usb_ep *ep;
+
+ list_for_each_entry (ep, &gadget->ep_list, ep_list) {
+ ep->driver_data = NULL;
+ }
+#ifdef MANY_ENDPOINTS
+ in_epnum = 0;
+#endif
+ epnum = 0;
+}
+
diff --git a/drivers/usb/gadget/gadget_gbhc/ether.c b/drivers/usb/gadget/gadget_gbhc/ether.c
new file mode 100644
index 0000000..1690c9d
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/ether.c
@@ -0,0 +1,421 @@
+/*
+ * ether.c -- Ethernet gadget driver, with CDC and non-CDC options
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+
+
+#if defined USB_ETH_RNDIS
+# undef USB_ETH_RNDIS
+#endif
+#ifdef CONFIG_USB_ETH_RNDIS
+# define USB_ETH_RNDIS y
+#endif
+
+#include "u_ether.h"
+
+
+/*
+ * Ethernet gadget driver -- with CDC and non-CDC options
+ * Builds on hardware support for a full duplex link.
+ *
+ * CDC Ethernet is the standard USB solution for sending Ethernet frames
+ * using USB. Real hardware tends to use the same framing protocol but look
+ * different for control features. This driver strongly prefers to use
+ * this USB-IF standard as its open-systems interoperability solution;
+ * most host side USB stacks (except from Microsoft) support it.
+ *
+ * This is sometimes called "CDC ECM" (Ethernet Control Model) to support
+ * TLA-soup. "CDC ACM" (Abstract Control Model) is for modems, and a new
+ * "CDC EEM" (Ethernet Emulation Model) is starting to spread.
+ *
+ * There's some hardware that can't talk CDC ECM. We make that hardware
+ * implement a "minimalist" vendor-agnostic CDC core: same framing, but
+ * link-level setup only requires activating the configuration. Only the
+ * endpoint descriptors, and product/vendor IDs, are relevant; no control
+ * operations are available. Linux supports it, but other host operating
+ * systems may not. (This is a subset of CDC Ethernet.)
+ *
+ * It turns out that if you add a few descriptors to that "CDC Subset",
+ * (Windows) host side drivers from MCCI can treat it as one submode of
+ * a proprietary scheme called "SAFE" ... without needing to know about
+ * specific product/vendor IDs. So we do that, making it easier to use
+ * those MS-Windows drivers. Those added descriptors make it resemble a
+ * CDC MDLM device, but they don't change device behavior at all. (See
+ * MCCI Engineering report 950198 "SAFE Networking Functions".)
+ *
+ * A third option is also in use. Rather than CDC Ethernet, or something
+ * simpler, Microsoft pushes their own approach: RNDIS. The published
+ * RNDIS specs are ambiguous and appear to be incomplete, and are also
+ * needlessly complex. They borrow more from CDC ACM than CDC ECM.
+ */
+
+#define DRIVER_DESC "Ethernet Gadget"
+#define DRIVER_VERSION "Memorial Day 2008"
+
+#ifdef USB_ETH_RNDIS
+#define PREFIX "RNDIS/"
+#else
+#define PREFIX ""
+#endif
+
+/*
+ * This driver aims for interoperability by using CDC ECM unless
+ *
+ * can_support_ecm()
+ *
+ * returns false, in which case it supports the CDC Subset. By default,
+ * that returns true; most hardware has no problems with CDC ECM, that's
+ * a good default. Previous versions of this driver had no default; this
+ * version changes that, removing overhead for new controller support.
+ *
+ * IF YOUR HARDWARE CAN'T SUPPORT CDC ECM, UPDATE THAT ROUTINE!
+ */
+
+static inline bool has_rndis(void)
+{
+#ifdef USB_ETH_RNDIS
+ return true;
+#else
+ return false;
+#endif
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module. So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "composite.c"
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+
+#include "f_ecm.c"
+#include "f_subset.c"
+#ifdef USB_ETH_RNDIS
+#include "f_rndis.c"
+#include "rndis.c"
+#endif
+#include "f_eem.c"
+#include "u_ether.c"
+
+/*-------------------------------------------------------------------------*/
+
+/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!!
+ * Instead: allocate your own, using normal USB-IF procedures.
+ */
+
+/* Thanks to NetChip Technologies for donating this product ID.
+ * It's for devices with only CDC Ethernet configurations.
+ */
+#define CDC_VENDOR_NUM 0x0525 /* NetChip */
+#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */
+
+/* For hardware that can't talk CDC, we use the same vendor ID that
+ * ARM Linux has used for ethernet-over-usb, both with sa1100 and
+ * with pxa250. We're protocol-compatible, if the host-side drivers
+ * use the endpoint descriptors. bcdDevice (version) is nonzero, so
+ * drivers that need to hard-wire endpoint numbers have a hook.
+ *
+ * The protocol is a minimal subset of CDC Ether, which works on any bulk
+ * hardware that's not deeply broken ... even on hardware that can't talk
+ * RNDIS (like SA-1100, with no interrupt endpoint, or anything that
+ * doesn't handle control-OUT).
+ */
+#define SIMPLE_VENDOR_NUM 0x049f
+#define SIMPLE_PRODUCT_NUM 0x505a
+
+/* For hardware that can talk RNDIS and either of the above protocols,
+ * use this ID ... the windows INF files will know it. Unless it's
+ * used with CDC Ethernet, Linux 2.4 hosts will need updates to choose
+ * the non-RNDIS configuration.
+ */
+#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */
+#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */
+
+/* For EEM gadgets */
+#define EEM_VENDOR_NUM 0x1d6b /* Linux Foundation */
+#define EEM_PRODUCT_NUM 0x0102 /* EEM Gadget */
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_device_descriptor device_desc = {
+ .bLength = sizeof device_desc,
+ .bDescriptorType = USB_DT_DEVICE,
+
+ .bcdUSB = cpu_to_le16 (0x0200),
+
+ .bDeviceClass = USB_CLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ /* .bMaxPacketSize0 = f(hardware) */
+
+ /* Vendor and product id defaults change according to what configs
+ * we support. (As does bNumConfigurations.) These values can
+ * also be overridden by module parameters.
+ */
+ .idVendor = cpu_to_le16 (CDC_VENDOR_NUM),
+ .idProduct = cpu_to_le16 (CDC_PRODUCT_NUM),
+ /* .bcdDevice = f(hardware) */
+ /* .iManufacturer = DYNAMIC */
+ /* .iProduct = DYNAMIC */
+ /* NO SERIAL NUMBER */
+ .bNumConfigurations = 1,
+};
+
+static struct usb_otg_descriptor otg_descriptor = {
+ .bLength = sizeof otg_descriptor,
+ .bDescriptorType = USB_DT_OTG,
+
+ /* REVISIT SRP-only hardware is possible, although
+ * it would not be called "OTG" ...
+ */
+ .bmAttributes = USB_OTG_SRP | USB_OTG_HNP,
+};
+
+static const struct usb_descriptor_header *otg_desc[] = {
+ (struct usb_descriptor_header *) &otg_descriptor,
+ NULL,
+};
+
+
+/* string IDs are assigned dynamically */
+
+#define STRING_MANUFACTURER_IDX 0
+#define STRING_PRODUCT_IDX 1
+
+static char manufacturer[50];
+
+static struct usb_string strings_dev[] = {
+ [STRING_MANUFACTURER_IDX].s = manufacturer,
+ [STRING_PRODUCT_IDX].s = PREFIX DRIVER_DESC,
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dev,
+};
+
+static struct usb_gadget_strings *dev_strings[] = {
+ &stringtab_dev,
+ NULL,
+};
+
+static u8 hostaddr[ETH_ALEN];
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * We may not have an RNDIS configuration, but if we do it needs to be
+ * the first one present. That's to make Microsoft's drivers happy,
+ * and to follow DOCSIS 1.0 (cable modem standard).
+ */
+static int __init rndis_do_config(struct usb_configuration *c)
+{
+ /* FIXME alloc iConfiguration string, set it in c->strings */
+
+ if (gadget_is_otg(c->cdev->gadget)) {
+ c->descriptors = otg_desc;
+ c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+ }
+
+ return rndis_bind_config(c, hostaddr);
+}
+
+static struct usb_configuration rndis_config_driver = {
+ .label = "RNDIS",
+ .bConfigurationValue = 2,
+ /* .iConfiguration = DYNAMIC */
+ .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
+};
+
+/*-------------------------------------------------------------------------*/
+
+#ifdef CONFIG_USB_ETH_EEM
+static int use_eem = 1;
+#else
+static int use_eem;
+#endif
+module_param(use_eem, bool, 0);
+MODULE_PARM_DESC(use_eem, "use CDC EEM mode");
+
+/*
+ * We _always_ have an ECM, CDC Subset, or EEM configuration.
+ */
+static int __init eth_do_config(struct usb_configuration *c)
+{
+ /* FIXME alloc iConfiguration string, set it in c->strings */
+
+ if (gadget_is_otg(c->cdev->gadget)) {
+ c->descriptors = otg_desc;
+ c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+ }
+
+ if (use_eem)
+ return eem_bind_config(c);
+ else if (can_support_ecm(c->cdev->gadget))
+ return ecm_bind_config(c, hostaddr);
+ else
+ return geth_bind_config(c, hostaddr);
+}
+
+static struct usb_configuration eth_config_driver = {
+ /* .label = f(hardware) */
+ .bConfigurationValue = 1,
+ /* .iConfiguration = DYNAMIC */
+ .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init eth_bind(struct usb_composite_dev *cdev)
+{
+ int gcnum;
+ struct usb_gadget *gadget = cdev->gadget;
+ int status;
+
+ /* set up network link layer */
+ status = gether_setup(cdev->gadget, hostaddr);
+ if (status < 0)
+ return status;
+
+ /* set up main config label and device descriptor */
+ if (use_eem) {
+ /* EEM */
+ eth_config_driver.label = "CDC Ethernet (EEM)";
+ device_desc.idVendor = cpu_to_le16(EEM_VENDOR_NUM);
+ device_desc.idProduct = cpu_to_le16(EEM_PRODUCT_NUM);
+ } else if (can_support_ecm(cdev->gadget)) {
+ /* ECM */
+ eth_config_driver.label = "CDC Ethernet (ECM)";
+ } else {
+ /* CDC Subset */
+ eth_config_driver.label = "CDC Subset/SAFE";
+
+ device_desc.idVendor = cpu_to_le16(SIMPLE_VENDOR_NUM);
+ device_desc.idProduct = cpu_to_le16(SIMPLE_PRODUCT_NUM);
+ if (!has_rndis())
+ device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC;
+ }
+
+ if (has_rndis()) {
+ /* RNDIS plus ECM-or-Subset */
+ device_desc.idVendor = cpu_to_le16(RNDIS_VENDOR_NUM);
+ device_desc.idProduct = cpu_to_le16(RNDIS_PRODUCT_NUM);
+ device_desc.bNumConfigurations = 2;
+ }
+
+ gcnum = usb_gadget_controller_number(gadget);
+ if (gcnum >= 0)
+ device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum);
+ else {
+ /* We assume that can_support_ecm() tells the truth;
+ * but if the controller isn't recognized at all then
+ * that assumption is a bit more likely to be wrong.
+ */
+ dev_warn(&gadget->dev,
+ "controller '%s' not recognized; trying %s\n",
+ gadget->name,
+ eth_config_driver.label);
+ device_desc.bcdDevice =
+ cpu_to_le16(0x0300 | 0x0099);
+ }
+
+
+ /* Allocate string descriptor numbers ... note that string
+ * contents can be overridden by the composite_dev glue.
+ */
+
+ /* device descriptor strings: manufacturer, product */
+ snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
+ init_utsname()->sysname, init_utsname()->release,
+ gadget->name);
+ status = usb_string_id(cdev);
+ if (status < 0)
+ goto fail;
+ strings_dev[STRING_MANUFACTURER_IDX].id = status;
+ device_desc.iManufacturer = status;
+
+ status = usb_string_id(cdev);
+ if (status < 0)
+ goto fail;
+ strings_dev[STRING_PRODUCT_IDX].id = status;
+ device_desc.iProduct = status;
+
+ /* register our configuration(s); RNDIS first, if it's used */
+ if (has_rndis()) {
+ status = usb_add_config(cdev, &rndis_config_driver,
+ rndis_do_config);
+ if (status < 0)
+ goto fail;
+ }
+
+ status = usb_add_config(cdev, &eth_config_driver, eth_do_config);
+ if (status < 0)
+ goto fail;
+
+ dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n",
+ DRIVER_DESC);
+
+ return 0;
+
+fail:
+ gether_cleanup();
+ return status;
+}
+
+static int __exit eth_unbind(struct usb_composite_dev *cdev)
+{
+ gether_cleanup();
+ return 0;
+}
+
+static struct usb_composite_driver eth_driver = {
+ .name = "g_ether",
+ .dev = &device_desc,
+ .strings = dev_strings,
+ .unbind = __exit_p(eth_unbind),
+};
+
+MODULE_DESCRIPTION(PREFIX DRIVER_DESC);
+MODULE_AUTHOR("David Brownell, Benedikt Spanger");
+MODULE_LICENSE("GPL");
+
+static int __init init(void)
+{
+ return usb_composite_probe(&eth_driver, eth_bind);
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+ usb_composite_unregister(&eth_driver);
+}
+module_exit(cleanup);
diff --git a/drivers/usb/gadget/gadget_gbhc/f_acm.c b/drivers/usb/gadget/gadget_gbhc/f_acm.c
new file mode 100644
index 0000000..cf2e7fc
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/f_acm.c
@@ -0,0 +1,842 @@
+/*
+ * f_acm.c -- USB CDC serial (ACM) function driver
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 by David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ * Copyright (C) 2009 by Samsung Electronics
+ * Author: Michal Nazarewicz (m.nazarewicz@samsung.com)
+ *
+ * This software is distributed under the terms of the GNU General
+ * Public License ("GPL") as published by the Free Software Foundation,
+ * either version 2 of that License or (at your option) any later version.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/usb/android_composite.h>
+
+#include "u_serial.h"
+#include "gadget_chips.h"
+
+
+/*
+ * This CDC ACM function support just wraps control functions and
+ * notifications around the generic serial-over-usb code.
+ *
+ * Because CDC ACM is standardized by the USB-IF, many host operating
+ * systems have drivers for it. Accordingly, ACM is the preferred
+ * interop solution for serial-port type connections. The control
+ * models are often not necessary, and in any case don't do much in
+ * this bare-bones implementation.
+ *
+ * Note that even MS-Windows has some support for ACM. However, that
+ * support is somewhat broken because when you use ACM in a composite
+ * device, having multiple interfaces confuses the poor OS. It doesn't
+ * seem to understand CDC Union descriptors. The new "association"
+ * descriptors (roughly equivalent to CDC Unions) may sometimes help.
+ */
+
+struct acm_ep_descs {
+ struct usb_endpoint_descriptor *in;
+ struct usb_endpoint_descriptor *out;
+ struct usb_endpoint_descriptor *notify;
+};
+
+struct f_acm {
+ struct gserial port;
+ u8 ctrl_id, data_id;
+ u8 port_num;
+
+ u8 pending;
+
+ /* lock is mostly for pending and notify_req ... they get accessed
+ * by callbacks both from tty (open/close/break) under its spinlock,
+ * and notify_req.complete() which can't use that lock.
+ */
+ spinlock_t lock;
+
+ struct acm_ep_descs fs;
+ struct acm_ep_descs hs;
+
+ struct usb_ep *notify;
+ struct usb_endpoint_descriptor *notify_desc;
+ struct usb_request *notify_req;
+
+ struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */
+
+ /* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */
+ u16 port_handshake_bits;
+#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */
+#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */
+
+ /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */
+ u16 serial_state;
+#define ACM_CTRL_OVERRUN (1 << 6)
+#define ACM_CTRL_PARITY (1 << 5)
+#define ACM_CTRL_FRAMING (1 << 4)
+#define ACM_CTRL_RI (1 << 3)
+#define ACM_CTRL_BRK (1 << 2)
+#define ACM_CTRL_DSR (1 << 1)
+#define ACM_CTRL_DCD (1 << 0)
+};
+
+static inline struct f_acm *func_to_acm(struct usb_function *f)
+{
+ return container_of(f, struct f_acm, port.func);
+}
+
+static inline struct f_acm *port_to_acm(struct gserial *p)
+{
+ return container_of(p, struct f_acm, port);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* notification endpoint uses smallish and infrequent fixed-size messages */
+
+#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */
+#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */
+
+/* interface and class descriptors: */
+
+static struct usb_interface_assoc_descriptor
+acm_iad_descriptor = {
+ .bLength = sizeof acm_iad_descriptor,
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+ /* .bFirstInterface = DYNAMIC, */
+ .bInterfaceCount = 2, // control + data
+ .bFunctionClass = USB_CLASS_COMM,
+ .bFunctionSubClass = USB_CDC_SUBCLASS_ACM,
+ .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
+ /* .iFunction = DYNAMIC */
+};
+
+
+static struct usb_interface_descriptor acm_control_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_COMM,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
+ .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_interface_descriptor acm_data_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc acm_header_desc = {
+ .bLength = sizeof(acm_header_desc),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_HEADER_TYPE,
+ .bcdCDC = cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor
+acm_call_mgmt_descriptor = {
+ .bLength = sizeof(acm_call_mgmt_descriptor),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
+ .bmCapabilities = 0,
+ /* .bDataInterface = DYNAMIC */
+};
+
+static struct usb_cdc_acm_descriptor acm_descriptor = {
+ .bLength = sizeof(acm_descriptor),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_ACM_TYPE,
+ .bmCapabilities = USB_CDC_CAP_LINE,
+};
+
+static struct usb_cdc_union_desc acm_union_desc = {
+ .bLength = sizeof(acm_union_desc),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_UNION_TYPE,
+ /* .bMasterInterface0 = DYNAMIC */
+ /* .bSlaveInterface0 = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor acm_fs_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET),
+ .bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL,
+};
+
+static struct usb_endpoint_descriptor acm_fs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor acm_fs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *acm_fs_function[] = {
+ (struct usb_descriptor_header *) &acm_iad_descriptor,
+ (struct usb_descriptor_header *) &acm_control_interface_desc,
+ (struct usb_descriptor_header *) &acm_header_desc,
+ (struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &acm_descriptor,
+ (struct usb_descriptor_header *) &acm_union_desc,
+ (struct usb_descriptor_header *) &acm_fs_notify_desc,
+ (struct usb_descriptor_header *) &acm_data_interface_desc,
+ (struct usb_descriptor_header *) &acm_fs_in_desc,
+ (struct usb_descriptor_header *) &acm_fs_out_desc,
+ NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor acm_hs_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET),
+ .bInterval = GS_LOG2_NOTIFY_INTERVAL+4,
+};
+
+static struct usb_endpoint_descriptor acm_hs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor acm_hs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *acm_hs_function[] = {
+ (struct usb_descriptor_header *) &acm_iad_descriptor,
+ (struct usb_descriptor_header *) &acm_control_interface_desc,
+ (struct usb_descriptor_header *) &acm_header_desc,
+ (struct usb_descriptor_header *) &acm_call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &acm_descriptor,
+ (struct usb_descriptor_header *) &acm_union_desc,
+ (struct usb_descriptor_header *) &acm_hs_notify_desc,
+ (struct usb_descriptor_header *) &acm_data_interface_desc,
+ (struct usb_descriptor_header *) &acm_hs_in_desc,
+ (struct usb_descriptor_header *) &acm_hs_out_desc,
+ NULL,
+};
+
+/* string descriptors: */
+
+#define ACM_CTRL_IDX 0
+#define ACM_DATA_IDX 1
+#define ACM_IAD_IDX 2
+
+/* static strings, in UTF-8 */
+static struct usb_string acm_string_defs[] = {
+ [ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)",
+ [ACM_DATA_IDX].s = "CDC ACM Data",
+ [ACM_IAD_IDX ].s = "CDC Serial",
+ { /* ZEROES END LIST */ },
+};
+
+static struct usb_gadget_strings acm_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = acm_string_defs,
+};
+
+static struct usb_gadget_strings *acm_strings[] = {
+ &acm_string_table,
+ NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+/* ACM control ... data handling is delegated to tty library code.
+ * The main task of this function is to activate and deactivate
+ * that code based on device state; track parameters like line
+ * speed, handshake state, and so on; and issue notifications.
+ */
+
+static void acm_complete_set_line_coding(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct f_acm *acm = ep->driver_data;
+ struct usb_composite_dev *cdev = acm->port.func.config->cdev;
+
+ if (req->status != 0) {
+ DBG(cdev, "acm ttyGS%d completion, err %d\n",
+ acm->port_num, req->status);
+ return;
+ }
+
+ /* normal completion */
+ if (req->actual != sizeof(acm->port_line_coding)) {
+ DBG(cdev, "acm ttyGS%d short resp, len %d\n",
+ acm->port_num, req->actual);
+ usb_ep_set_halt(ep);
+ } else {
+ struct usb_cdc_line_coding *value = req->buf;
+
+ /* REVISIT: we currently just remember this data.
+ * If we change that, (a) validate it first, then
+ * (b) update whatever hardware needs updating,
+ * (c) worry about locking. This is information on
+ * the order of 9600-8-N-1 ... most of which means
+ * nothing unless we control a real RS232 line.
+ */
+ acm->port_line_coding = *value;
+ }
+}
+
+static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_acm *acm = func_to_acm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ /* composite driver infrastructure handles everything except
+ * CDC class messages; interface activation uses set_alt().
+ *
+ * Note CDC spec table 4 lists the ACM request profile. It requires
+ * encapsulated command support ... we don't handle any, and respond
+ * to them by stalling. Options include get/set/clear comm features
+ * (not that useful) and SEND_BREAK.
+ */
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+
+ /* SET_LINE_CODING ... just read and save what the host sends */
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_REQ_SET_LINE_CODING:
+ if (w_length != sizeof(struct usb_cdc_line_coding)
+ || w_index != acm->ctrl_id)
+ goto invalid;
+
+ value = w_length;
+ cdev->gadget->ep0->driver_data = acm;
+ req->complete = acm_complete_set_line_coding;
+ break;
+
+ /* GET_LINE_CODING ... return what host sent, or initial value */
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_REQ_GET_LINE_CODING:
+ if (w_index != acm->ctrl_id)
+ goto invalid;
+
+ value = min_t(unsigned, w_length,
+ sizeof(struct usb_cdc_line_coding));
+ memcpy(req->buf, &acm->port_line_coding, value);
+ break;
+
+ /* SET_CONTROL_LINE_STATE ... save what the host sent */
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_REQ_SET_CONTROL_LINE_STATE:
+ if (w_index != acm->ctrl_id)
+ goto invalid;
+
+ value = 0;
+
+ /* FIXME we should not allow data to flow until the
+ * host sets the ACM_CTRL_DTR bit; and when it clears
+ * that bit, we should return to that no-flow state.
+ */
+ acm->port_handshake_bits = w_value;
+ break;
+
+ default:
+invalid:
+ VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n",
+ acm->port_num, ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ req->zero = 0;
+ req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0)
+ ERROR(cdev, "acm response on ttyGS%d, err %d\n",
+ acm->port_num, value);
+ }
+
+ /* device either stalls (value < 0) or reports success */
+ return value;
+}
+
+static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct f_acm *acm = func_to_acm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ /* we know alt == 0, so this is an activation or a reset */
+
+ if (intf == acm->ctrl_id) {
+ if (acm->notify->driver_data) {
+ VDBG(cdev, "reset acm control interface %d\n", intf);
+ usb_ep_disable(acm->notify);
+ } else {
+ VDBG(cdev, "init acm ctrl interface %d\n", intf);
+ }
+ acm->notify_desc = ep_choose(cdev->gadget,
+ acm->hs.notify,
+ acm->fs.notify);
+ usb_ep_enable(acm->notify, acm->notify_desc);
+ acm->notify->driver_data = acm;
+
+ } else if (intf == acm->data_id) {
+ if (acm->port.in->driver_data) {
+ DBG(cdev, "reset acm ttyGS%d\n", acm->port_num);
+ gserial_disconnect(&acm->port);
+ } else {
+ DBG(cdev, "activate acm ttyGS%d\n", acm->port_num);
+ }
+ acm->port.in_desc = ep_choose(cdev->gadget,
+ acm->hs.in, acm->fs.in);
+ acm->port.out_desc = ep_choose(cdev->gadget,
+ acm->hs.out, acm->fs.out);
+ gserial_connect(&acm->port, acm->port_num);
+
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+static void acm_disable(struct usb_function *f)
+{
+ struct f_acm *acm = func_to_acm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num);
+ gserial_disconnect(&acm->port);
+ usb_ep_disable(acm->notify);
+ acm->notify->driver_data = NULL;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * acm_cdc_notify - issue CDC notification to host
+ * @acm: wraps host to be notified
+ * @type: notification type
+ * @value: Refer to cdc specs, wValue field.
+ * @data: data to be sent
+ * @length: size of data
+ * Context: irqs blocked, acm->lock held, acm_notify_req non-null
+ *
+ * Returns zero on success or a negative errno.
+ *
+ * See section 6.3.5 of the CDC 1.1 specification for information
+ * about the only notification we issue: SerialState change.
+ */
+static int acm_cdc_notify(struct f_acm *acm, u8 type, u16 value,
+ void *data, unsigned length)
+{
+ struct usb_ep *ep = acm->notify;
+ struct usb_request *req;
+ struct usb_cdc_notification *notify;
+ const unsigned len = sizeof(*notify) + length;
+ void *buf;
+ int status;
+
+ req = acm->notify_req;
+ acm->notify_req = NULL;
+ acm->pending = false;
+
+ req->length = len;
+ notify = req->buf;
+ buf = notify + 1;
+
+ notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS
+ | USB_RECIP_INTERFACE;
+ notify->bNotificationType = type;
+ notify->wValue = cpu_to_le16(value);
+ notify->wIndex = cpu_to_le16(acm->ctrl_id);
+ notify->wLength = cpu_to_le16(length);
+ memcpy(buf, data, length);
+
+ /* ep_queue() can complete immediately if it fills the fifo... */
+ spin_unlock(&acm->lock);
+ status = usb_ep_queue(ep, req, GFP_ATOMIC);
+ spin_lock(&acm->lock);
+
+ if (status < 0) {
+ ERROR(acm->port.func.config->cdev,
+ "acm ttyGS%d can't notify serial state, %d\n",
+ acm->port_num, status);
+ acm->notify_req = req;
+ }
+
+ return status;
+}
+
+static int acm_notify_serial_state(struct f_acm *acm)
+{
+ struct usb_composite_dev *cdev = acm->port.func.config->cdev;
+ int status;
+
+ spin_lock(&acm->lock);
+ if (acm->notify_req) {
+ DBG(cdev, "acm ttyGS%d serial state %04x\n",
+ acm->port_num, acm->serial_state);
+ status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE,
+ 0, &acm->serial_state, sizeof(acm->serial_state));
+ } else {
+ acm->pending = true;
+ status = 0;
+ }
+ spin_unlock(&acm->lock);
+ return status;
+}
+
+static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_acm *acm = req->context;
+ u8 doit = false;
+
+ /* on this call path we do NOT hold the port spinlock,
+ * which is why ACM needs its own spinlock
+ */
+ spin_lock(&acm->lock);
+ if (req->status != -ESHUTDOWN)
+ doit = acm->pending;
+ acm->notify_req = req;
+ spin_unlock(&acm->lock);
+
+ if (doit)
+ acm_notify_serial_state(acm);
+}
+
+/* connect == the TTY link is open */
+
+static void acm_connect(struct gserial *port)
+{
+ struct f_acm *acm = port_to_acm(port);
+
+ acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD;
+ acm_notify_serial_state(acm);
+}
+
+static void acm_disconnect(struct gserial *port)
+{
+ struct f_acm *acm = port_to_acm(port);
+
+ acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD);
+ acm_notify_serial_state(acm);
+}
+
+static int acm_send_break(struct gserial *port, int duration)
+{
+ struct f_acm *acm = port_to_acm(port);
+ u16 state;
+
+ state = acm->serial_state;
+ state &= ~ACM_CTRL_BRK;
+ if (duration)
+ state |= ACM_CTRL_BRK;
+
+ acm->serial_state = state;
+ return acm_notify_serial_state(acm);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ACM function driver setup/binding */
+static int
+acm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_acm *acm = func_to_acm(f);
+ int status;
+ struct usb_ep *ep;
+
+ /* allocate instance-specific interface IDs, and patch descriptors */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ acm->ctrl_id = status;
+ acm_iad_descriptor.bFirstInterface = status;
+
+ acm_control_interface_desc.bInterfaceNumber = status;
+ acm_union_desc .bMasterInterface0 = status;
+
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ acm->data_id = status;
+
+ acm_data_interface_desc.bInterfaceNumber = status;
+ acm_union_desc.bSlaveInterface0 = status;
+ acm_call_mgmt_descriptor.bDataInterface = status;
+
+ status = -ENODEV;
+
+ /* allocate instance-specific endpoints */
+ ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc);
+ if (!ep)
+ goto fail;
+ acm->port.in = ep;
+ ep->driver_data = cdev; /* claim */
+
+ ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc);
+ if (!ep)
+ goto fail;
+ acm->port.out = ep;
+ ep->driver_data = cdev; /* claim */
+
+ ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc);
+ if (!ep)
+ goto fail;
+ acm->notify = ep;
+ ep->driver_data = cdev; /* claim */
+
+ /* allocate notification */
+ acm->notify_req = gs_alloc_req(ep,
+ sizeof(struct usb_cdc_notification) + 2,
+ GFP_KERNEL);
+ if (!acm->notify_req)
+ goto fail;
+
+ acm->notify_req->complete = acm_cdc_notify_complete;
+ acm->notify_req->context = acm;
+
+ /* copy descriptors, and track endpoint copies */
+ f->descriptors = usb_copy_descriptors(acm_fs_function);
+ if (!f->descriptors)
+ goto fail;
+
+ acm->fs.in = usb_find_endpoint(acm_fs_function,
+ f->descriptors, &acm_fs_in_desc);
+ acm->fs.out = usb_find_endpoint(acm_fs_function,
+ f->descriptors, &acm_fs_out_desc);
+ acm->fs.notify = usb_find_endpoint(acm_fs_function,
+ f->descriptors, &acm_fs_notify_desc);
+
+ /* support all relevant hardware speeds... we expect that when
+ * hardware is dual speed, all bulk-capable endpoints work at
+ * both speeds
+ */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ acm_hs_in_desc.bEndpointAddress =
+ acm_fs_in_desc.bEndpointAddress;
+ acm_hs_out_desc.bEndpointAddress =
+ acm_fs_out_desc.bEndpointAddress;
+ acm_hs_notify_desc.bEndpointAddress =
+ acm_fs_notify_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->hs_descriptors = usb_copy_descriptors(acm_hs_function);
+
+ acm->hs.in = usb_find_endpoint(acm_hs_function,
+ f->hs_descriptors, &acm_hs_in_desc);
+ acm->hs.out = usb_find_endpoint(acm_hs_function,
+ f->hs_descriptors, &acm_hs_out_desc);
+ acm->hs.notify = usb_find_endpoint(acm_hs_function,
+ f->hs_descriptors, &acm_hs_notify_desc);
+ }
+
+ DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+ acm->port_num,
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ acm->port.in->name, acm->port.out->name,
+ acm->notify->name);
+ return 0;
+
+fail:
+ if (acm->notify_req)
+ gs_free_req(acm->notify, acm->notify_req);
+
+ /* we might as well release our claims on endpoints */
+ if (acm->notify)
+ acm->notify->driver_data = NULL;
+ if (acm->port.out)
+ acm->port.out->driver_data = NULL;
+ if (acm->port.in)
+ acm->port.in->driver_data = NULL;
+
+ ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status);
+
+ return status;
+}
+
+static void
+acm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_acm *acm = func_to_acm(f);
+
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+ gs_free_req(acm->notify, acm->notify_req);
+ kfree(acm->port.func.name);
+ kfree(acm);
+}
+
+/* Some controllers can't support CDC ACM ... */
+static inline bool can_support_cdc(struct usb_configuration *c)
+{
+ /* everything else is *probably* fine ... */
+ return true;
+}
+
+/**
+ * acm_bind_config - add a CDC ACM function to a configuration
+ * @c: the configuration to support the CDC ACM instance
+ * @port_num: /dev/ttyGS* port this interface will use
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gserial_setup() with enough ports to
+ * handle all the ones it binds. Caller is also responsible
+ * for calling @gserial_cleanup() before module unload.
+ */
+int acm_bind_config(struct usb_configuration *c, u8 port_num)
+{
+ struct f_acm *acm;
+ int status;
+
+ if (!can_support_cdc(c))
+ return -EINVAL;
+
+ /* REVISIT might want instance-specific strings to help
+ * distinguish instances ...
+ */
+
+ /* maybe allocate device-global string IDs, and patch descriptors */
+ if (acm_string_defs[ACM_CTRL_IDX].id == 0) {
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ acm_string_defs[ACM_CTRL_IDX].id = status;
+
+ acm_control_interface_desc.iInterface = status;
+
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ acm_string_defs[ACM_DATA_IDX].id = status;
+
+ acm_data_interface_desc.iInterface = status;
+
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ acm_string_defs[ACM_IAD_IDX].id = status;
+
+ acm_iad_descriptor.iFunction = status;
+ }
+
+ /* allocate and initialize one new instance */
+ acm = kzalloc(sizeof *acm, GFP_KERNEL);
+ if (!acm)
+ return -ENOMEM;
+
+ spin_lock_init(&acm->lock);
+
+ acm->port_num = port_num;
+
+ acm->port.connect = acm_connect;
+ acm->port.disconnect = acm_disconnect;
+ acm->port.send_break = acm_send_break;
+
+ acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num);
+ if (!acm->port.func.name) {
+ kfree(acm);
+ return -ENOMEM;
+ }
+ acm->port.func.strings = acm_strings;
+ /* descriptors are per-instance copies */
+ acm->port.func.bind = acm_bind;
+ acm->port.func.unbind = acm_unbind;
+ acm->port.func.set_alt = acm_set_alt;
+ acm->port.func.setup = acm_setup;
+ acm->port.func.disable = acm_disable;
+
+ status = usb_add_function(c, &acm->port.func);
+ if (status)
+ kfree(acm);
+ return status;
+}
+
+#ifdef CONFIG_USB_ANDROID_ACM
+#include <linux/platform_device.h>
+
+static struct acm_platform_data *acm_pdata;
+
+static int acm_probe(struct platform_device *pdev)
+{
+ acm_pdata = pdev->dev.platform_data;
+ return 0;
+}
+
+static struct platform_driver acm_platform_driver = {
+ .driver = { .name = "acm", },
+ .probe = acm_probe,
+};
+
+int acm_function_bind_config(struct usb_configuration *c)
+{
+ int i;
+ u8 num_inst = acm_pdata ? acm_pdata->num_inst : 1;
+ int ret = gserial_setup(c->cdev->gadget, num_inst);
+
+ if (ret)
+ return ret;
+
+ for (i = 0; i < num_inst; i++) {
+ ret = acm_bind_config(c, i);
+ if (ret) {
+ pr_err("Could not bind acm%u config\n", i);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static struct android_usb_function acm_function = {
+ .name = "acm",
+ .bind_config = acm_function_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_acm init\n");
+ platform_driver_register(&acm_platform_driver);
+ android_register_function(&acm_function);
+ return 0;
+}
+module_init(init);
+
+#endif /* CONFIG_USB_ANDROID_ACM */
diff --git a/drivers/usb/gadget/gadget_gbhc/f_adb.c b/drivers/usb/gadget/gadget_gbhc/f_adb.c
new file mode 100644
index 0000000..a8d7b7d
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/f_adb.c
@@ -0,0 +1,661 @@
+/*
+ * Gadget Driver for Android ADB
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb/android_composite.h>
+
+#define BULK_BUFFER_SIZE 4096
+
+/* number of tx requests to allocate */
+#define TX_REQ_MAX 4
+
+static const char shortname[] = "android_adb";
+
+struct adb_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+
+ int online;
+ int error;
+
+ atomic_t read_excl;
+ atomic_t write_excl;
+ atomic_t open_excl;
+
+ struct list_head tx_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ struct usb_request *rx_req;
+ int rx_done;
+};
+
+static struct usb_interface_descriptor adb_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = 0xFF,
+ .bInterfaceSubClass = 0x42,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor adb_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor adb_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor adb_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor adb_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *fs_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_fullspeed_out_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_adb_descs[] = {
+ (struct usb_descriptor_header *) &adb_interface_desc,
+ (struct usb_descriptor_header *) &adb_highspeed_in_desc,
+ (struct usb_descriptor_header *) &adb_highspeed_out_desc,
+ NULL,
+};
+
+
+/* temporary variable used between adb_open() and adb_gadget_bind() */
+static struct adb_dev *_adb_dev;
+
+static atomic_t adb_enable_excl;
+
+static inline struct adb_dev *func_to_dev(struct usb_function *f)
+{
+ return container_of(f, struct adb_dev, function);
+}
+
+
+static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void adb_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int _lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void _unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+void req_put(struct adb_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+struct usb_request *req_get(struct adb_dev *dev, struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void adb_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ if (req->status != 0)
+ dev->error = 1;
+
+ req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void adb_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct adb_dev *dev = _adb_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->error = 1;
+
+ wake_up(&dev->read_wq);
+}
+
+static int __init create_bulk_endpoints(struct adb_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ /* now allocate requests for our endpoints */
+ req = adb_request_new(dev->ep_out, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = adb_complete_out;
+ dev->rx_req = req;
+
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = adb_request_new(dev->ep_in, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = adb_complete_in;
+ req_put(dev, &dev->tx_idle, req);
+ }
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "adb_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t adb_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct adb_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret;
+
+ DBG(cdev, "adb_read(%d)\n", count);
+
+ if (count > BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ if (_lock(&dev->read_excl))
+ return -EBUSY;
+
+ /* we will block until we're online */
+ while (!(dev->online || dev->error)) {
+ DBG(cdev, "adb_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ (dev->online || dev->error));
+ if (ret < 0) {
+ _unlock(&dev->read_excl);
+ return ret;
+ }
+ }
+ if (dev->error) {
+ r = -EIO;
+ goto done;
+ }
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req;
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC);
+ if (ret < 0) {
+ DBG(cdev, "adb_read: failed to queue req %p (%d)\n", req, ret);
+ r = -EIO;
+ dev->error = 1;
+ goto done;
+ } else {
+ DBG(cdev, "rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ /* Try to dequeue request since adb daemon
+ * will queue this request again later.
+ * If it fails we can do nothing just report. */
+ if (usb_ep_dequeue(dev->ep_out, req))
+ printk(KERN_WARNING "adb: can not dequeue request\n");
+
+ dev->error = 1;
+ r = ret;
+ goto done;
+ }
+ if (!dev->error) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ DBG(cdev, "rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+ } else
+ r = -EIO;
+
+done:
+ _unlock(&dev->read_excl);
+ DBG(cdev, "adb_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t adb_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct adb_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int ret;
+
+ DBG(cdev, "adb_write(%d)\n", count);
+
+ if (_lock(&dev->write_excl))
+ return -EBUSY;
+
+ while (count > 0) {
+ if (dev->error) {
+ DBG(cdev, "adb_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = req_get(dev, &dev->tx_idle)) || dev->error));
+
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+
+ if (req != 0) {
+ if (count > BULK_BUFFER_SIZE)
+ xfer = BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ if (copy_from_user(req->buf, buf, xfer)) {
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC);
+ if (ret < 0) {
+ DBG(cdev, "adb_write: xfer error %d\n", ret);
+ dev->error = 1;
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+ }
+
+ if (req)
+ req_put(dev, &dev->tx_idle, req);
+
+ _unlock(&dev->write_excl);
+ DBG(cdev, "adb_write returning %d\n", r);
+ return r;
+}
+
+static int adb_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "adb_open\n");
+ if (_lock(&_adb_dev->open_excl))
+ return -EBUSY;
+
+ fp->private_data = _adb_dev;
+
+ /* clear the error latch */
+ _adb_dev->error = 0;
+
+ return 0;
+}
+
+static int adb_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "adb_release\n");
+ _unlock(&_adb_dev->open_excl);
+ return 0;
+}
+
+/* file operations for ADB device /dev/android_adb */
+static struct file_operations adb_fops = {
+ .owner = THIS_MODULE,
+ .read = adb_read,
+ .write = adb_write,
+ .open = adb_open,
+ .release = adb_release,
+};
+
+static struct miscdevice adb_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = shortname,
+ .fops = &adb_fops,
+};
+
+static int adb_enable_open(struct inode *ip, struct file *fp)
+{
+ if (atomic_inc_return(&adb_enable_excl) != 1) {
+ atomic_dec(&adb_enable_excl);
+ return -EBUSY;
+ }
+
+ printk(KERN_INFO "enabling adb\n");
+ android_enable_function(&_adb_dev->function, 1);
+
+ return 0;
+}
+
+static int adb_enable_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "disabling adb\n");
+ android_enable_function(&_adb_dev->function, 0);
+ atomic_dec(&adb_enable_excl);
+ return 0;
+}
+
+static const struct file_operations adb_enable_fops = {
+ .owner = THIS_MODULE,
+ .open = adb_enable_open,
+ .release = adb_enable_release,
+};
+
+static struct miscdevice adb_enable_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "android_adb_enable",
+ .fops = &adb_enable_fops,
+};
+
+static int
+adb_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct adb_dev *dev = func_to_dev(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "adb_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ adb_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = create_bulk_endpoints(dev, &adb_fullspeed_in_desc,
+ &adb_fullspeed_out_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ adb_highspeed_in_desc.bEndpointAddress =
+ adb_fullspeed_in_desc.bEndpointAddress;
+ adb_highspeed_out_desc.bEndpointAddress =
+ adb_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+adb_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct adb_dev *dev = func_to_dev(f);
+ struct usb_request *req;
+
+ spin_lock_irq(&dev->lock);
+
+ adb_request_free(dev->rx_req, dev->ep_out);
+ while ((req = req_get(dev, &dev->tx_idle)))
+ adb_request_free(req, dev->ep_in);
+
+ dev->online = 0;
+ dev->error = 1;
+ spin_unlock_irq(&dev->lock);
+
+ misc_deregister(&adb_device);
+ misc_deregister(&adb_enable_device);
+ kfree(_adb_dev);
+ _adb_dev = NULL;
+}
+
+static int adb_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct adb_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt);
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &adb_highspeed_in_desc,
+ &adb_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &adb_highspeed_out_desc,
+ &adb_fullspeed_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ dev->online = 1;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void adb_function_disable(struct usb_function *f)
+{
+ struct adb_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "adb_function_disable\n");
+ dev->online = 0;
+ dev->error = 1;
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int adb_bind_config(struct usb_configuration *c)
+{
+ struct adb_dev *dev;
+ int ret;
+
+ printk(KERN_INFO "adb_bind_config\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->lock);
+
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+
+ atomic_set(&dev->open_excl, 0);
+ atomic_set(&dev->read_excl, 0);
+ atomic_set(&dev->write_excl, 0);
+
+ INIT_LIST_HEAD(&dev->tx_idle);
+
+ dev->cdev = c->cdev;
+ dev->function.name = "adb";
+ dev->function.descriptors = fs_adb_descs;
+ dev->function.hs_descriptors = hs_adb_descs;
+ dev->function.bind = adb_function_bind;
+ dev->function.unbind = adb_function_unbind;
+ dev->function.set_alt = adb_function_set_alt;
+ dev->function.disable = adb_function_disable;
+
+ /* start disabled */
+ dev->function.disabled = 1;
+
+ /* _adb_dev must be set before calling usb_gadget_register_driver */
+ _adb_dev = dev;
+
+ ret = misc_register(&adb_device);
+ if (ret)
+ goto err1;
+ ret = misc_register(&adb_enable_device);
+ if (ret)
+ goto err2;
+
+ ret = usb_add_function(c, &dev->function);
+ if (ret)
+ goto err3;
+
+ return 0;
+
+err3:
+ misc_deregister(&adb_enable_device);
+err2:
+ misc_deregister(&adb_device);
+err1:
+ kfree(dev);
+ printk(KERN_ERR "adb gadget driver failed to initialize\n");
+ return ret;
+}
+
+static struct android_usb_function adb_function = {
+ .name = "adb",
+ .bind_config = adb_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_adb init\n");
+ android_register_function(&adb_function);
+ return 0;
+}
+module_init(init);
diff --git a/drivers/usb/gadget/gadget_gbhc/f_mass_storage.c b/drivers/usb/gadget/gadget_gbhc/f_mass_storage.c
new file mode 100644
index 0000000..a296cd8
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/f_mass_storage.c
@@ -0,0 +1,3260 @@
+/*
+ * f_mass_storage.c -- Mass Storage USB Composite Function
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * Copyright (C) 2009 Samsung Electronics
+ * Author: Michal Nazarewicz <m.nazarewicz@samsung.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") as published by the Free Software
+ * Foundation, either version 2 of that License or (at your option) any
+ * later version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * The Mass Storage Function acts as a USB Mass Storage device,
+ * appearing to the host as a disk drive or as a CD-ROM drive. In
+ * addition to providing an example of a genuinely useful composite
+ * function for a USB device, it also illustrates a technique of
+ * double-buffering for increased throughput.
+ *
+ * Function supports multiple logical units (LUNs). Backing storage
+ * for each LUN is provided by a regular file or a block device.
+ * Access for each LUN can be limited to read-only. Moreover, the
+ * function can indicate that LUN is removable and/or CD-ROM. (The
+ * later implies read-only access.)
+ *
+ * MSF is configured by specifying a fsg_config structure. It has the
+ * following fields:
+ *
+ * nluns Number of LUNs function have (anywhere from 1
+ * to FSG_MAX_LUNS which is 8).
+ * luns An array of LUN configuration values. This
+ * should be filled for each LUN that
+ * function will include (ie. for "nluns"
+ * LUNs). Each element of the array has
+ * the following fields:
+ * ->filename The path to the backing file for the LUN.
+ * Required if LUN is not marked as
+ * removable.
+ * ->ro Flag specifying access to the LUN shall be
+ * read-only. This is implied if CD-ROM
+ * emulation is enabled as well as when
+ * it was impossible to open "filename"
+ * in R/W mode.
+ * ->removable Flag specifying that LUN shall be indicated as
+ * being removable.
+ * ->cdrom Flag specifying that LUN shall be reported as
+ * being a CD-ROM.
+ * ->nofua Flag specifying that FUA flag in SCSI WRITE(10,12)
+ * commands for this LUN shall be ignored.
+ *
+ * lun_name_format A printf-like format for names of the LUN
+ * devices. This determines how the
+ * directory in sysfs will be named.
+ * Unless you are using several MSFs in
+ * a single gadget (as opposed to single
+ * MSF in many configurations) you may
+ * leave it as NULL (in which case
+ * "lun%d" will be used). In the format
+ * you can use "%d" to index LUNs for
+ * MSF's with more than one LUN. (Beware
+ * that there is only one integer given
+ * as an argument for the format and
+ * specifying invalid format may cause
+ * unspecified behaviour.)
+ * thread_name Name of the kernel thread process used by the
+ * MSF. You can safely set it to NULL
+ * (in which case default "file-storage"
+ * will be used).
+ *
+ * vendor_name
+ * product_name
+ * release Information used as a reply to INQUIRY
+ * request. To use default set to NULL,
+ * NULL, 0xffff respectively. The first
+ * field should be 8 and the second 16
+ * characters or less.
+ *
+ * can_stall Set to permit function to halt bulk endpoints.
+ * Disabled on some USB devices known not
+ * to work correctly. You should set it
+ * to true.
+ *
+ * If "removable" is not set for a LUN then a backing file must be
+ * specified. If it is set, then NULL filename means the LUN's medium
+ * is not loaded (an empty string as "filename" in the fsg_config
+ * structure causes error). The CD-ROM emulation includes a single
+ * data track and no audio tracks; hence there need be only one
+ * backing file per LUN. Note also that the CD-ROM block length is
+ * set to 512 rather than the more common value 2048.
+ *
+ *
+ * MSF includes support for module parameters. If gadget using it
+ * decides to use it, the following module parameters will be
+ * available:
+ *
+ * file=filename[,filename...]
+ * Names of the files or block devices used for
+ * backing storage.
+ * ro=b[,b...] Default false, boolean for read-only access.
+ * removable=b[,b...]
+ * Default true, boolean for removable media.
+ * cdrom=b[,b...] Default false, boolean for whether to emulate
+ * a CD-ROM drive.
+ * nofua=b[,b...] Default false, booleans for ignore FUA flag
+ * in SCSI WRITE(10,12) commands
+ * luns=N Default N = number of filenames, number of
+ * LUNs to support.
+ * stall Default determined according to the type of
+ * USB device controller (usually true),
+ * boolean to permit the driver to halt
+ * bulk endpoints.
+ *
+ * The module parameters may be prefixed with some string. You need
+ * to consult gadget's documentation or source to verify whether it is
+ * using those module parameters and if it does what are the prefixes
+ * (look for FSG_MODULE_PARAMETERS() macro usage, what's inside it is
+ * the prefix).
+ *
+ *
+ * Requirements are modest; only a bulk-in and a bulk-out endpoint are
+ * needed. The memory requirement amounts to two 16K buffers, size
+ * configurable by a parameter. Support is included for both
+ * full-speed and high-speed operation.
+ *
+ * Note that the driver is slightly non-portable in that it assumes a
+ * single memory/DMA buffer will be useable for bulk-in, bulk-out, and
+ * interrupt-in endpoints. With most device controllers this isn't an
+ * issue, but there may be some with hardware restrictions that prevent
+ * a buffer from being used by more than one endpoint.
+ *
+ *
+ * The pathnames of the backing files and the ro settings are
+ * available in the attribute files "file" and "ro" in the lun<n> (or
+ * to be more precise in a directory which name comes from
+ * "lun_name_format" option!) subdirectory of the gadget's sysfs
+ * directory. If the "removable" option is set, writing to these
+ * files will simulate ejecting/loading the medium (writing an empty
+ * line means eject) and adjusting a write-enable tab. Changes to the
+ * ro setting are not allowed when the medium is loaded or if CD-ROM
+ * emulation is being used.
+ *
+ * When a LUN receive an "eject" SCSI request (Start/Stop Unit),
+ * if the LUN is removable, the backing file is released to simulate
+ * ejection.
+ *
+ *
+ * This function is heavily based on "File-backed Storage Gadget" by
+ * Alan Stern which in turn is heavily based on "Gadget Zero" by David
+ * Brownell. The driver's SCSI command interface was based on the
+ * "Information technology - Small Computer System Interface - 2"
+ * document from X3T9.2 Project 375D, Revision 10L, 7-SEP-93,
+ * available at <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>.
+ * The single exception is opcode 0x23 (READ FORMAT CAPACITIES), which
+ * was based on the "Universal Serial Bus Mass Storage Class UFI
+ * Command Specification" document, Revision 1.0, December 14, 1998,
+ * available at
+ * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>.
+ */
+
+/*
+ * Driver Design
+ *
+ * The MSF is fairly straightforward. There is a main kernel
+ * thread that handles most of the work. Interrupt routines field
+ * callbacks from the controller driver: bulk- and interrupt-request
+ * completion notifications, endpoint-0 events, and disconnect events.
+ * Completion events are passed to the main thread by wakeup calls. Many
+ * ep0 requests are handled at interrupt time, but SetInterface,
+ * SetConfiguration, and device reset requests are forwarded to the
+ * thread in the form of "exceptions" using SIGUSR1 signals (since they
+ * should interrupt any ongoing file I/O operations).
+ *
+ * The thread's main routine implements the standard command/data/status
+ * parts of a SCSI interaction. It and its subroutines are full of tests
+ * for pending signals/exceptions -- all this polling is necessary since
+ * the kernel has no setjmp/longjmp equivalents. (Maybe this is an
+ * indication that the driver really wants to be running in userspace.)
+ * An important point is that so long as the thread is alive it keeps an
+ * open reference to the backing file. This will prevent unmounting
+ * the backing file's underlying filesystem and could cause problems
+ * during system shutdown, for example. To prevent such problems, the
+ * thread catches INT, TERM, and KILL signals and converts them into
+ * an EXIT exception.
+ *
+ * In normal operation the main thread is started during the gadget's
+ * fsg_bind() callback and stopped during fsg_unbind(). But it can
+ * also exit when it receives a signal, and there's no point leaving
+ * the gadget running when the thread is dead. At of this moment, MSF
+ * provides no way to deregister the gadget when thread dies -- maybe
+ * a callback functions is needed.
+ *
+ * To provide maximum throughput, the driver uses a circular pipeline of
+ * buffer heads (struct fsg_buffhd). In principle the pipeline can be
+ * arbitrarily long; in practice the benefits don't justify having more
+ * than 2 stages (i.e., double buffering). But it helps to think of the
+ * pipeline as being a long one. Each buffer head contains a bulk-in and
+ * a bulk-out request pointer (since the buffer can be used for both
+ * output and input -- directions always are given from the host's
+ * point of view) as well as a pointer to the buffer and various state
+ * variables.
+ *
+ * Use of the pipeline follows a simple protocol. There is a variable
+ * (fsg->next_buffhd_to_fill) that points to the next buffer head to use.
+ * At any time that buffer head may still be in use from an earlier
+ * request, so each buffer head has a state variable indicating whether
+ * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the
+ * buffer head to be EMPTY, filling the buffer either by file I/O or by
+ * USB I/O (during which the buffer head is BUSY), and marking the buffer
+ * head FULL when the I/O is complete. Then the buffer will be emptied
+ * (again possibly by USB I/O, during which it is marked BUSY) and
+ * finally marked EMPTY again (possibly by a completion routine).
+ *
+ * A module parameter tells the driver to avoid stalling the bulk
+ * endpoints wherever the transport specification allows. This is
+ * necessary for some UDCs like the SuperH, which cannot reliably clear a
+ * halt on a bulk endpoint. However, under certain circumstances the
+ * Bulk-only specification requires a stall. In such cases the driver
+ * will halt the endpoint and set a flag indicating that it should clear
+ * the halt in software during the next device reset. Hopefully this
+ * will permit everything to work correctly. Furthermore, although the
+ * specification allows the bulk-out endpoint to halt when the host sends
+ * too much data, implementing this would cause an unavoidable race.
+ * The driver will always use the "no-stall" approach for OUT transfers.
+ *
+ * One subtle point concerns sending status-stage responses for ep0
+ * requests. Some of these requests, such as device reset, can involve
+ * interrupting an ongoing file I/O operation, which might take an
+ * arbitrarily long time. During that delay the host might give up on
+ * the original ep0 request and issue a new one. When that happens the
+ * driver should not notify the host about completion of the original
+ * request, as the host will no longer be waiting for it. So the driver
+ * assigns to each ep0 request a unique tag, and it keeps track of the
+ * tag value of the request associated with a long-running exception
+ * (device-reset, interface-change, or configuration-change). When the
+ * exception handler is finished, the status-stage response is submitted
+ * only if the current ep0 request tag is equal to the exception request
+ * tag. Thus only the most recently received ep0 request will get a
+ * status-stage response.
+ *
+ * Warning: This driver source file is too long. It ought to be split up
+ * into a header file plus about 3 separate .c files, to handle the details
+ * of the Gadget, USB Mass Storage, and SCSI protocols.
+ */
+
+
+/* #define VERBOSE_DEBUG */
+/* #define DUMP_MSGS */
+
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/dcache.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/limits.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/freezer.h>
+#include <linux/utsname.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+
+#include "gadget_chips.h"
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+#include <linux/usb/android_composite.h>
+#include <linux/platform_device.h>
+
+#define FUNCTION_NAME "usb_mass_storage"
+#endif
+
+/*------------------------------------------------------------------------*/
+
+#define FSG_DRIVER_DESC "Mass Storage Function"
+#define FSG_DRIVER_VERSION "2009/09/11"
+
+static const char fsg_string_interface[] = "Mass Storage";
+
+#define FSG_NO_INTR_EP 1
+#define FSG_NO_DEVICE_STRINGS 1
+#define FSG_NO_OTG 1
+#define FSG_NO_INTR_EP 1
+
+#include "storage_common.c"
+
+
+/*-------------------------------------------------------------------------*/
+
+struct fsg_dev;
+struct fsg_common;
+
+/* FSF callback functions */
+struct fsg_operations {
+ /*
+ * Callback function to call when thread exits. If no
+ * callback is set or it returns value lower then zero MSF
+ * will force eject all LUNs it operates on (including those
+ * marked as non-removable or with prevent_medium_removal flag
+ * set).
+ */
+ int (*thread_exits)(struct fsg_common *common);
+
+ /*
+ * Called prior to ejection. Negative return means error,
+ * zero means to continue with ejection, positive means not to
+ * eject.
+ */
+ int (*pre_eject)(struct fsg_common *common,
+ struct fsg_lun *lun, int num);
+ /*
+ * Called after ejection. Negative return means error, zero
+ * or positive is just a success.
+ */
+ int (*post_eject)(struct fsg_common *common,
+ struct fsg_lun *lun, int num);
+};
+
+/* Data shared by all the FSG instances. */
+struct fsg_common {
+ struct usb_gadget *gadget;
+ struct usb_composite_dev *cdev;
+ struct fsg_dev *fsg, *new_fsg;
+ wait_queue_head_t fsg_wait;
+
+ /* filesem protects: backing files in use */
+ struct rw_semaphore filesem;
+
+ /* lock protects: state, all the req_busy's */
+ spinlock_t lock;
+
+ struct usb_ep *ep0; /* Copy of gadget->ep0 */
+ struct usb_request *ep0req; /* Copy of cdev->req */
+ unsigned int ep0_req_tag;
+
+ struct fsg_buffhd *next_buffhd_to_fill;
+ struct fsg_buffhd *next_buffhd_to_drain;
+ struct fsg_buffhd buffhds[FSG_NUM_BUFFERS];
+
+ int cmnd_size;
+ u8 cmnd[MAX_COMMAND_SIZE];
+
+ unsigned int nluns;
+ unsigned int lun;
+ struct fsg_lun *luns;
+ struct fsg_lun *curlun;
+
+ unsigned int bulk_out_maxpacket;
+ enum fsg_state state; /* For exception handling */
+ unsigned int exception_req_tag;
+
+ enum data_direction data_dir;
+ u32 data_size;
+ u32 data_size_from_cmnd;
+ u32 tag;
+ u32 residue;
+ u32 usb_amount_left;
+
+ unsigned int can_stall:1;
+ unsigned int free_storage_on_release:1;
+ unsigned int phase_error:1;
+ unsigned int short_packet_received:1;
+ unsigned int bad_lun_okay:1;
+ unsigned int running:1;
+
+ int thread_wakeup_needed;
+ struct completion thread_notifier;
+ struct task_struct *thread_task;
+
+ /* Callback functions. */
+ const struct fsg_operations *ops;
+ /* Gadget's private data. */
+ void *private_data;
+
+ /*
+ * Vendor (8 chars), product (16 chars), release (4
+ * hexadecimal digits) and NUL byte
+ */
+ char inquiry_string[8 + 16 + 4 + 1];
+
+ struct kref ref;
+};
+
+struct fsg_config {
+ unsigned nluns;
+ struct fsg_lun_config {
+ const char *filename;
+ char ro;
+ char removable;
+ char cdrom;
+ char nofua;
+ } luns[FSG_MAX_LUNS];
+
+ const char *lun_name_format;
+ const char *thread_name;
+
+ /* Callback functions. */
+ const struct fsg_operations *ops;
+ /* Gadget's private data. */
+ void *private_data;
+
+ const char *vendor_name; /* 8 characters or less */
+ const char *product_name; /* 16 characters or less */
+ u16 release;
+
+ char can_stall;
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+ struct platform_device *pdev;
+#endif
+};
+
+struct fsg_dev {
+ struct usb_function function;
+ struct usb_gadget *gadget; /* Copy of cdev->gadget */
+ struct fsg_common *common;
+
+ u16 interface_number;
+
+ unsigned int bulk_in_enabled:1;
+ unsigned int bulk_out_enabled:1;
+
+ unsigned long atomic_bitflags;
+#define IGNORE_BULK_OUT 0
+
+ struct usb_ep *bulk_in;
+ struct usb_ep *bulk_out;
+};
+
+static inline int __fsg_is_set(struct fsg_common *common,
+ const char *func, unsigned line)
+{
+ if (common->fsg)
+ return 1;
+ ERROR(common, "common->fsg is NULL in %s at %u\n", func, line);
+ WARN_ON(1);
+ return 0;
+}
+
+#define fsg_is_set(common) likely(__fsg_is_set(common, __func__, __LINE__))
+
+static inline struct fsg_dev *fsg_from_func(struct usb_function *f)
+{
+ return container_of(f, struct fsg_dev, function);
+}
+
+typedef void (*fsg_routine_t)(struct fsg_dev *);
+
+static int exception_in_progress(struct fsg_common *common)
+{
+ return common->state > FSG_STATE_IDLE;
+}
+
+/* Make bulk-out requests be divisible by the maxpacket size */
+static void set_bulk_out_req_length(struct fsg_common *common,
+ struct fsg_buffhd *bh, unsigned int length)
+{
+ unsigned int rem;
+
+ bh->bulk_out_intended_length = length;
+ rem = length % common->bulk_out_maxpacket;
+ if (rem > 0)
+ length += common->bulk_out_maxpacket - rem;
+ bh->outreq->length = length;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
+{
+ const char *name;
+
+ if (ep == fsg->bulk_in)
+ name = "bulk-in";
+ else if (ep == fsg->bulk_out)
+ name = "bulk-out";
+ else
+ name = ep->name;
+ DBG(fsg, "%s set halt\n", name);
+ return usb_ep_set_halt(ep);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* These routines may be called in process context or in_irq */
+
+/* Caller must hold fsg->lock */
+static void wakeup_thread(struct fsg_common *common)
+{
+ /* Tell the main thread that something has happened */
+ common->thread_wakeup_needed = 1;
+ if (common->thread_task)
+ wake_up_process(common->thread_task);
+}
+
+static void raise_exception(struct fsg_common *common, enum fsg_state new_state)
+{
+ unsigned long flags;
+
+ /*
+ * Do nothing if a higher-priority exception is already in progress.
+ * If a lower-or-equal priority exception is in progress, preempt it
+ * and notify the main thread by sending it a signal.
+ */
+ spin_lock_irqsave(&common->lock, flags);
+ if (common->state <= new_state) {
+ common->exception_req_tag = common->ep0_req_tag;
+ common->state = new_state;
+ if (common->thread_task)
+ send_sig_info(SIGUSR1, SEND_SIG_FORCED,
+ common->thread_task);
+ }
+ spin_unlock_irqrestore(&common->lock, flags);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int ep0_queue(struct fsg_common *common)
+{
+ int rc;
+
+ rc = usb_ep_queue(common->ep0, common->ep0req, GFP_ATOMIC);
+ common->ep0->driver_data = common;
+ if (rc != 0 && rc != -ESHUTDOWN) {
+ /* We can't do much more than wait for a reset */
+ WARNING(common, "error in submission: %s --> %d\n",
+ common->ep0->name, rc);
+ }
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Completion handlers. These always run in_irq. */
+
+static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct fsg_common *common = ep->driver_data;
+ struct fsg_buffhd *bh = req->context;
+
+ if (req->status || req->actual != req->length)
+ DBG(common, "%s --> %d, %u/%u\n", __func__,
+ req->status, req->actual, req->length);
+ if (req->status == -ECONNRESET) /* Request was cancelled */
+ usb_ep_fifo_flush(ep);
+
+ /* Hold the lock while we update the request and buffer states */
+ smp_wmb();
+ spin_lock(&common->lock);
+ bh->inreq_busy = 0;
+ bh->state = BUF_STATE_EMPTY;
+ wakeup_thread(common);
+ spin_unlock(&common->lock);
+}
+
+static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct fsg_common *common = ep->driver_data;
+ struct fsg_buffhd *bh = req->context;
+
+ dump_msg(common, "bulk-out", req->buf, req->actual);
+ if (req->status || req->actual != bh->bulk_out_intended_length)
+ DBG(common, "%s --> %d, %u/%u\n", __func__,
+ req->status, req->actual, bh->bulk_out_intended_length);
+ if (req->status == -ECONNRESET) /* Request was cancelled */
+ usb_ep_fifo_flush(ep);
+
+ /* Hold the lock while we update the request and buffer states */
+ smp_wmb();
+ spin_lock(&common->lock);
+ bh->outreq_busy = 0;
+ bh->state = BUF_STATE_FULL;
+ wakeup_thread(common);
+ spin_unlock(&common->lock);
+}
+
+static int fsg_setup(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct fsg_dev *fsg = fsg_from_func(f);
+ struct usb_request *req = fsg->common->ep0req;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ if (!fsg_is_set(fsg->common))
+ return -EOPNOTSUPP;
+
+ ++fsg->common->ep0_req_tag; /* Record arrival of a new request */
+ req->context = NULL;
+ req->length = 0;
+ dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl));
+
+ switch (ctrl->bRequest) {
+
+ case USB_BULK_RESET_REQUEST:
+ if (ctrl->bRequestType !=
+ (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+ break;
+ if (w_index != fsg->interface_number || w_value != 0)
+ return -EDOM;
+
+ /*
+ * Raise an exception to stop the current operation
+ * and reinitialize our state.
+ */
+ DBG(fsg, "bulk reset request\n");
+ raise_exception(fsg->common, FSG_STATE_RESET);
+ return DELAYED_STATUS;
+
+ case USB_BULK_GET_MAX_LUN_REQUEST:
+ if (ctrl->bRequestType !=
+ (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+ break;
+ if (w_index != fsg->interface_number || w_value != 0)
+ return -EDOM;
+ VDBG(fsg, "get max LUN\n");
+ *(u8 *)req->buf = fsg->common->nluns - 1;
+
+ /* Respond with data/status */
+ req->length = min((u16)1, w_length);
+ return ep0_queue(fsg->common);
+ }
+
+ VDBG(fsg,
+ "unknown class-specific control req %02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ le16_to_cpu(ctrl->wValue), w_index, w_length);
+ return -EOPNOTSUPP;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* All the following routines run in process context */
+
+/* Use this for bulk or interrupt transfers, not ep0 */
+static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
+ struct usb_request *req, int *pbusy,
+ enum fsg_buffer_state *state)
+{
+ int rc;
+
+ if (ep == fsg->bulk_in)
+ dump_msg(fsg, "bulk-in", req->buf, req->length);
+
+ spin_lock_irq(&fsg->common->lock);
+ *pbusy = 1;
+ *state = BUF_STATE_BUSY;
+ spin_unlock_irq(&fsg->common->lock);
+ rc = usb_ep_queue(ep, req, GFP_KERNEL);
+ if (rc != 0) {
+ *pbusy = 0;
+ *state = BUF_STATE_EMPTY;
+
+ /* We can't do much more than wait for a reset */
+
+ /*
+ * Note: currently the net2280 driver fails zero-length
+ * submissions if DMA is enabled.
+ */
+ if (rc != -ESHUTDOWN &&
+ !(rc == -EOPNOTSUPP && req->length == 0))
+ WARNING(fsg, "error in submission: %s --> %d\n",
+ ep->name, rc);
+ }
+}
+
+static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ if (!fsg_is_set(common))
+ return false;
+ start_transfer(common->fsg, common->fsg->bulk_in,
+ bh->inreq, &bh->inreq_busy, &bh->state);
+ return true;
+}
+
+static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ if (!fsg_is_set(common))
+ return false;
+ start_transfer(common->fsg, common->fsg->bulk_out,
+ bh->outreq, &bh->outreq_busy, &bh->state);
+ return true;
+}
+
+static int sleep_thread(struct fsg_common *common)
+{
+ int rc = 0;
+
+ /* Wait until a signal arrives or we are woken up */
+ for (;;) {
+ try_to_freeze();
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (signal_pending(current)) {
+ rc = -EINTR;
+ break;
+ }
+ if (common->thread_wakeup_needed)
+ break;
+ schedule();
+ }
+ __set_current_state(TASK_RUNNING);
+ common->thread_wakeup_needed = 0;
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_read(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u32 lba;
+ struct fsg_buffhd *bh;
+ int rc;
+ u32 amount_left;
+ loff_t file_offset, file_offset_tmp;
+ unsigned int amount;
+ unsigned int partial_page;
+ ssize_t nread;
+
+ /*
+ * Get the starting Logical Block Address and check that it's
+ * not too big.
+ */
+ if (common->cmnd[0] == READ_6)
+ lba = get_unaligned_be24(&common->cmnd[1]);
+ else {
+ lba = get_unaligned_be32(&common->cmnd[2]);
+
+ /*
+ * We allow DPO (Disable Page Out = don't save data in the
+ * cache) and FUA (Force Unit Access = don't read from the
+ * cache), but we don't implement them.
+ */
+ if ((common->cmnd[1] & ~0x18) != 0) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ }
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ file_offset = ((loff_t) lba) << 9;
+
+ /* Carry out the file reads */
+ amount_left = common->data_size_from_cmnd;
+ if (unlikely(amount_left == 0))
+ return -EIO; /* No default reply */
+
+ for (;;) {
+ /*
+ * Figure out how much we need to read:
+ * Try to read the remaining amount.
+ * But don't read more than the buffer size.
+ * And don't try to read past the end of the file.
+ * Finally, if we're not at a page boundary, don't read past
+ * the next page.
+ * If this means reading 0 then we were asked to read past
+ * the end of file.
+ */
+ amount = min(amount_left, FSG_BUFLEN);
+ amount = min((loff_t)amount,
+ curlun->file_length - file_offset);
+ partial_page = file_offset & (PAGE_CACHE_SIZE - 1);
+ if (partial_page > 0)
+ amount = min(amount, (unsigned int)PAGE_CACHE_SIZE -
+ partial_page);
+
+ /* Wait for the next buffer to become available */
+ bh = common->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+
+ /*
+ * If we were asked to read past the end of file,
+ * end with an empty buffer.
+ */
+ if (amount == 0) {
+ curlun->sense_data =
+ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ bh->inreq->length = 0;
+ bh->state = BUF_STATE_FULL;
+ break;
+ }
+
+ /* Perform the read */
+ file_offset_tmp = file_offset;
+ nread = vfs_read(curlun->filp,
+ (char __user *)bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+ (unsigned long long)file_offset, (int)nread);
+ if (signal_pending(current))
+ return -EINTR;
+
+ if (nread < 0) {
+ LDBG(curlun, "error in file read: %d\n", (int)nread);
+ nread = 0;
+ } else if (nread < amount) {
+ LDBG(curlun, "partial file read: %d/%u\n",
+ (int)nread, amount);
+ nread -= (nread & 511); /* Round down to a block */
+ }
+ file_offset += nread;
+ amount_left -= nread;
+ common->residue -= nread;
+ bh->inreq->length = nread;
+ bh->state = BUF_STATE_FULL;
+
+ /* If an error occurred, report it and its position */
+ if (nread < amount) {
+ curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ if (amount_left == 0)
+ break; /* No more left to read */
+
+ /* Send this buffer and go read some more */
+ bh->inreq->zero = 0;
+ if (!start_in_transfer(common, bh))
+ /* Don't know what to do if common->fsg is NULL */
+ return -EIO;
+ common->next_buffhd_to_fill = bh->next;
+ }
+
+ return -EIO; /* No default reply */
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_write(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u32 lba;
+ struct fsg_buffhd *bh;
+ int get_some_more;
+ u32 amount_left_to_req, amount_left_to_write;
+ loff_t usb_offset, file_offset, file_offset_tmp;
+ unsigned int amount;
+ unsigned int partial_page;
+ ssize_t nwritten;
+ int rc;
+
+ if (curlun->ro) {
+ curlun->sense_data = SS_WRITE_PROTECTED;
+ return -EINVAL;
+ }
+ spin_lock(&curlun->filp->f_lock);
+ curlun->filp->f_flags &= ~O_SYNC; /* Default is not to wait */
+ spin_unlock(&curlun->filp->f_lock);
+
+ /*
+ * Get the starting Logical Block Address and check that it's
+ * not too big
+ */
+ if (common->cmnd[0] == WRITE_6)
+ lba = get_unaligned_be24(&common->cmnd[1]);
+ else {
+ lba = get_unaligned_be32(&common->cmnd[2]);
+
+ /*
+ * We allow DPO (Disable Page Out = don't save data in the
+ * cache) and FUA (Force Unit Access = write directly to the
+ * medium). We don't implement DPO; we implement FUA by
+ * performing synchronous output.
+ */
+ if (common->cmnd[1] & ~0x18) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ if (!curlun->nofua && (common->cmnd[1] & 0x08)) { /* FUA */
+ spin_lock(&curlun->filp->f_lock);
+ curlun->filp->f_flags |= O_SYNC;
+ spin_unlock(&curlun->filp->f_lock);
+ }
+ }
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ /* Carry out the file writes */
+ get_some_more = 1;
+ file_offset = usb_offset = ((loff_t) lba) << 9;
+ amount_left_to_req = common->data_size_from_cmnd;
+ amount_left_to_write = common->data_size_from_cmnd;
+
+ while (amount_left_to_write > 0) {
+
+ /* Queue a request for more data from the host */
+ bh = common->next_buffhd_to_fill;
+ if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+
+ /*
+ * Figure out how much we want to get:
+ * Try to get the remaining amount.
+ * But don't get more than the buffer size.
+ * And don't try to go past the end of the file.
+ * If we're not at a page boundary,
+ * don't go past the next page.
+ * If this means getting 0, then we were asked
+ * to write past the end of file.
+ * Finally, round down to a block boundary.
+ */
+ amount = min(amount_left_to_req, FSG_BUFLEN);
+ amount = min((loff_t)amount,
+ curlun->file_length - usb_offset);
+ partial_page = usb_offset & (PAGE_CACHE_SIZE - 1);
+ if (partial_page > 0)
+ amount = min(amount,
+ (unsigned int)PAGE_CACHE_SIZE - partial_page);
+
+ if (amount == 0) {
+ get_some_more = 0;
+ curlun->sense_data =
+ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ curlun->sense_data_info = usb_offset >> 9;
+ curlun->info_valid = 1;
+ continue;
+ }
+ amount -= amount & 511;
+ if (amount == 0) {
+
+ /*
+ * Why were we were asked to transfer a
+ * partial block?
+ */
+ get_some_more = 0;
+ continue;
+ }
+
+ /* Get the next buffer */
+ usb_offset += amount;
+ common->usb_amount_left -= amount;
+ amount_left_to_req -= amount;
+ if (amount_left_to_req == 0)
+ get_some_more = 0;
+
+ /*
+ * amount is always divisible by 512, hence by
+ * the bulk-out maxpacket size
+ */
+ bh->outreq->length = amount;
+ bh->bulk_out_intended_length = amount;
+ bh->outreq->short_not_ok = 1;
+ if (!start_out_transfer(common, bh))
+ /* Dunno what to do if common->fsg is NULL */
+ return -EIO;
+ common->next_buffhd_to_fill = bh->next;
+ continue;
+ }
+
+ /* Write the received data to the backing file */
+ bh = common->next_buffhd_to_drain;
+ if (bh->state == BUF_STATE_EMPTY && !get_some_more)
+ break; /* We stopped early */
+ if (bh->state == BUF_STATE_FULL) {
+ smp_rmb();
+ common->next_buffhd_to_drain = bh->next;
+ bh->state = BUF_STATE_EMPTY;
+
+ /* Did something go wrong with the transfer? */
+ if (bh->outreq->status != 0) {
+ curlun->sense_data = SS_COMMUNICATION_FAILURE;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ amount = bh->outreq->actual;
+ if (curlun->file_length - file_offset < amount) {
+ LERROR(curlun,
+ "write %u @ %llu beyond end %llu\n",
+ amount, (unsigned long long)file_offset,
+ (unsigned long long)curlun->file_length);
+ amount = curlun->file_length - file_offset;
+ }
+
+ /* Perform the write */
+ file_offset_tmp = file_offset;
+ nwritten = vfs_write(curlun->filp,
+ (char __user *)bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
+ (unsigned long long)file_offset, (int)nwritten);
+ if (signal_pending(current))
+ return -EINTR; /* Interrupted! */
+
+ if (nwritten < 0) {
+ LDBG(curlun, "error in file write: %d\n",
+ (int)nwritten);
+ nwritten = 0;
+ } else if (nwritten < amount) {
+ LDBG(curlun, "partial file write: %d/%u\n",
+ (int)nwritten, amount);
+ nwritten -= (nwritten & 511);
+ /* Round down to a block */
+ }
+ file_offset += nwritten;
+ amount_left_to_write -= nwritten;
+ common->residue -= nwritten;
+
+ /* If an error occurred, report it and its position */
+ if (nwritten < amount) {
+ curlun->sense_data = SS_WRITE_ERROR;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ /* Did the host decide to stop early? */
+ if (bh->outreq->actual != bh->outreq->length) {
+ common->short_packet_received = 1;
+ break;
+ }
+ continue;
+ }
+
+ /* Wait for something to happen */
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+
+ return -EIO; /* No default reply */
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_synchronize_cache(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ int rc;
+
+ /* We ignore the requested LBA and write out all file's
+ * dirty data buffers. */
+ rc = fsg_lun_fsync_sub(curlun);
+ if (rc)
+ curlun->sense_data = SS_WRITE_ERROR;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void invalidate_sub(struct fsg_lun *curlun)
+{
+ struct file *filp = curlun->filp;
+ struct inode *inode = filp->f_path.dentry->d_inode;
+ unsigned long rc;
+
+ rc = invalidate_mapping_pages(inode->i_mapping, 0, -1);
+ VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc);
+}
+
+static int do_verify(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u32 lba;
+ u32 verification_length;
+ struct fsg_buffhd *bh = common->next_buffhd_to_fill;
+ loff_t file_offset, file_offset_tmp;
+ u32 amount_left;
+ unsigned int amount;
+ ssize_t nread;
+
+ /*
+ * Get the starting Logical Block Address and check that it's
+ * not too big.
+ */
+ lba = get_unaligned_be32(&common->cmnd[2]);
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ /*
+ * We allow DPO (Disable Page Out = don't save data in the
+ * cache) but we don't implement it.
+ */
+ if (common->cmnd[1] & ~0x10) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ verification_length = get_unaligned_be16(&common->cmnd[7]);
+ if (unlikely(verification_length == 0))
+ return -EIO; /* No default reply */
+
+ /* Prepare to carry out the file verify */
+ amount_left = verification_length << 9;
+ file_offset = ((loff_t) lba) << 9;
+
+ /* Write out all the dirty buffers before invalidating them */
+ fsg_lun_fsync_sub(curlun);
+ if (signal_pending(current))
+ return -EINTR;
+
+ invalidate_sub(curlun);
+ if (signal_pending(current))
+ return -EINTR;
+
+ /* Just try to read the requested blocks */
+ while (amount_left > 0) {
+ /*
+ * Figure out how much we need to read:
+ * Try to read the remaining amount, but not more than
+ * the buffer size.
+ * And don't try to read past the end of the file.
+ * If this means reading 0 then we were asked to read
+ * past the end of file.
+ */
+ amount = min(amount_left, FSG_BUFLEN);
+ amount = min((loff_t)amount,
+ curlun->file_length - file_offset);
+ if (amount == 0) {
+ curlun->sense_data =
+ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ /* Perform the read */
+ file_offset_tmp = file_offset;
+ nread = vfs_read(curlun->filp,
+ (char __user *) bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+ (unsigned long long) file_offset,
+ (int) nread);
+ if (signal_pending(current))
+ return -EINTR;
+
+ if (nread < 0) {
+ LDBG(curlun, "error in file verify: %d\n", (int)nread);
+ nread = 0;
+ } else if (nread < amount) {
+ LDBG(curlun, "partial file verify: %d/%u\n",
+ (int)nread, amount);
+ nread -= nread & 511; /* Round down to a sector */
+ }
+ if (nread == 0) {
+ curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+ file_offset += nread;
+ amount_left -= nread;
+ }
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u8 *buf = (u8 *) bh->buf;
+
+ if (!curlun) { /* Unsupported LUNs are okay */
+ common->bad_lun_okay = 1;
+ memset(buf, 0, 36);
+ buf[0] = 0x7f; /* Unsupported, no device-type */
+ buf[4] = 31; /* Additional length */
+ return 36;
+ }
+
+ buf[0] = curlun->cdrom ? TYPE_ROM : TYPE_DISK;
+ buf[1] = curlun->removable ? 0x80 : 0;
+ buf[2] = 2; /* ANSI SCSI level 2 */
+ buf[3] = 2; /* SCSI-2 INQUIRY data format */
+ buf[4] = 31; /* Additional length */
+ buf[5] = 0; /* No special options */
+ buf[6] = 0;
+ buf[7] = 0;
+ memcpy(buf + 8, common->inquiry_string, sizeof common->inquiry_string);
+ return 36;
+}
+
+static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u8 *buf = (u8 *) bh->buf;
+ u32 sd, sdinfo;
+ int valid;
+
+ /*
+ * From the SCSI-2 spec., section 7.9 (Unit attention condition):
+ *
+ * If a REQUEST SENSE command is received from an initiator
+ * with a pending unit attention condition (before the target
+ * generates the contingent allegiance condition), then the
+ * target shall either:
+ * a) report any pending sense data and preserve the unit
+ * attention condition on the logical unit, or,
+ * b) report the unit attention condition, may discard any
+ * pending sense data, and clear the unit attention
+ * condition on the logical unit for that initiator.
+ *
+ * FSG normally uses option a); enable this code to use option b).
+ */
+#if 0
+ if (curlun && curlun->unit_attention_data != SS_NO_SENSE) {
+ curlun->sense_data = curlun->unit_attention_data;
+ curlun->unit_attention_data = SS_NO_SENSE;
+ }
+#endif
+
+ if (!curlun) { /* Unsupported LUNs are okay */
+ common->bad_lun_okay = 1;
+ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+ sdinfo = 0;
+ valid = 0;
+ } else {
+ sd = curlun->sense_data;
+ sdinfo = curlun->sense_data_info;
+ valid = curlun->info_valid << 7;
+ curlun->sense_data = SS_NO_SENSE;
+ curlun->sense_data_info = 0;
+ curlun->info_valid = 0;
+ }
+
+ memset(buf, 0, 18);
+ buf[0] = valid | 0x70; /* Valid, current error */
+ buf[2] = SK(sd);
+ put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */
+ buf[7] = 18 - 8; /* Additional sense length */
+ buf[12] = ASC(sd);
+ buf[13] = ASCQ(sd);
+ return 18;
+}
+
+static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u32 lba = get_unaligned_be32(&common->cmnd[2]);
+ int pmi = common->cmnd[8];
+ u8 *buf = (u8 *)bh->buf;
+
+ /* Check the PMI and LBA fields */
+ if (pmi > 1 || (pmi == 0 && lba != 0)) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ put_unaligned_be32(curlun->num_sectors - 1, &buf[0]);
+ /* Max logical block */
+ put_unaligned_be32(512, &buf[4]); /* Block length */
+ return 8;
+}
+
+static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ int msf = common->cmnd[1] & 0x02;
+ u32 lba = get_unaligned_be32(&common->cmnd[2]);
+ u8 *buf = (u8 *)bh->buf;
+
+ if (common->cmnd[1] & ~0x02) { /* Mask away MSF */
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ memset(buf, 0, 8);
+ buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */
+ store_cdrom_address(&buf[4], msf, lba);
+ return 8;
+}
+
+static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ int msf = common->cmnd[1] & 0x02;
+ int start_track = common->cmnd[6];
+ u8 *buf = (u8 *)bh->buf;
+
+ if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */
+ start_track > 1) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ memset(buf, 0, 20);
+ buf[1] = (20-2); /* TOC data length */
+ buf[2] = 1; /* First track number */
+ buf[3] = 1; /* Last track number */
+ buf[5] = 0x16; /* Data track, copying allowed */
+ buf[6] = 0x01; /* Only track is number 1 */
+ store_cdrom_address(&buf[8], msf, 0);
+
+ buf[13] = 0x16; /* Lead-out track is data */
+ buf[14] = 0xAA; /* Lead-out track number */
+ store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+ return 20;
+}
+
+static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ int mscmnd = common->cmnd[0];
+ u8 *buf = (u8 *) bh->buf;
+ u8 *buf0 = buf;
+ int pc, page_code;
+ int changeable_values, all_pages;
+ int valid_page = 0;
+ int len, limit;
+
+ if ((common->cmnd[1] & ~0x08) != 0) { /* Mask away DBD */
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ pc = common->cmnd[2] >> 6;
+ page_code = common->cmnd[2] & 0x3f;
+ if (pc == 3) {
+ curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED;
+ return -EINVAL;
+ }
+ changeable_values = (pc == 1);
+ all_pages = (page_code == 0x3f);
+
+ /*
+ * Write the mode parameter header. Fixed values are: default
+ * medium type, no cache control (DPOFUA), and no block descriptors.
+ * The only variable value is the WriteProtect bit. We will fill in
+ * the mode data length later.
+ */
+ memset(buf, 0, 8);
+ if (mscmnd == MODE_SENSE) {
+ buf[2] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */
+ buf += 4;
+ limit = 255;
+ } else { /* MODE_SENSE_10 */
+ buf[3] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */
+ buf += 8;
+ limit = 65535; /* Should really be FSG_BUFLEN */
+ }
+
+ /* No block descriptors */
+
+ /*
+ * The mode pages, in numerical order. The only page we support
+ * is the Caching page.
+ */
+ if (page_code == 0x08 || all_pages) {
+ valid_page = 1;
+ buf[0] = 0x08; /* Page code */
+ buf[1] = 10; /* Page length */
+ memset(buf+2, 0, 10); /* None of the fields are changeable */
+
+ if (!changeable_values) {
+ buf[2] = 0x04; /* Write cache enable, */
+ /* Read cache not disabled */
+ /* No cache retention priorities */
+ put_unaligned_be16(0xffff, &buf[4]);
+ /* Don't disable prefetch */
+ /* Minimum prefetch = 0 */
+ put_unaligned_be16(0xffff, &buf[8]);
+ /* Maximum prefetch */
+ put_unaligned_be16(0xffff, &buf[10]);
+ /* Maximum prefetch ceiling */
+ }
+ buf += 12;
+ }
+
+ /*
+ * Check that a valid page was requested and the mode data length
+ * isn't too long.
+ */
+ len = buf - buf0;
+ if (!valid_page || len > limit) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ /* Store the mode data length */
+ if (mscmnd == MODE_SENSE)
+ buf0[0] = len - 1;
+ else
+ put_unaligned_be16(len - 2, buf0);
+ return len;
+}
+
+static int do_start_stop(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ int loej, start;
+
+ if (!curlun) {
+ return -EINVAL;
+ } else if (!curlun->removable) {
+ curlun->sense_data = SS_INVALID_COMMAND;
+ return -EINVAL;
+ } else if ((common->cmnd[1] & ~0x01) != 0 || /* Mask away Immed */
+ (common->cmnd[4] & ~0x03) != 0) { /* Mask LoEj, Start */
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ loej = common->cmnd[4] & 0x02;
+ start = common->cmnd[4] & 0x01;
+
+ /*
+ * Our emulation doesn't support mounting; the medium is
+ * available for use as soon as it is loaded.
+ */
+ if (start) {
+ if (!fsg_lun_is_open(curlun)) {
+ curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ /* Are we allowed to unload the media? */
+ if (curlun->prevent_medium_removal) {
+ LDBG(curlun, "unload attempt prevented\n");
+ curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED;
+ return -EINVAL;
+ }
+
+ if (!loej)
+ return 0;
+
+ /* Simulate an unload/eject */
+ if (common->ops && common->ops->pre_eject) {
+ int r = common->ops->pre_eject(common, curlun,
+ curlun - common->luns);
+ if (unlikely(r < 0))
+ return r;
+ else if (r)
+ return 0;
+ }
+
+ up_read(&common->filesem);
+ down_write(&common->filesem);
+ fsg_lun_close(curlun);
+ up_write(&common->filesem);
+ down_read(&common->filesem);
+
+ return common->ops && common->ops->post_eject
+ ? min(0, common->ops->post_eject(common, curlun,
+ curlun - common->luns))
+ : 0;
+}
+
+static int do_prevent_allow(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ int prevent;
+
+ if (!common->curlun) {
+ return -EINVAL;
+ } else if (!common->curlun->removable) {
+ common->curlun->sense_data = SS_INVALID_COMMAND;
+ return -EINVAL;
+ }
+
+ prevent = common->cmnd[4] & 0x01;
+ if ((common->cmnd[4] & ~0x01) != 0) { /* Mask away Prevent */
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ if (curlun->prevent_medium_removal && !prevent)
+ fsg_lun_fsync_sub(curlun);
+ curlun->prevent_medium_removal = prevent;
+ return 0;
+}
+
+static int do_read_format_capacities(struct fsg_common *common,
+ struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+ u8 *buf = (u8 *) bh->buf;
+
+ buf[0] = buf[1] = buf[2] = 0;
+ buf[3] = 8; /* Only the Current/Maximum Capacity Descriptor */
+ buf += 4;
+
+ put_unaligned_be32(curlun->num_sectors, &buf[0]);
+ /* Number of blocks */
+ put_unaligned_be32(512, &buf[4]); /* Block length */
+ buf[4] = 0x02; /* Current capacity */
+ return 12;
+}
+
+static int do_mode_select(struct fsg_common *common, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = common->curlun;
+
+ /* We don't support MODE SELECT */
+ if (curlun)
+ curlun->sense_data = SS_INVALID_COMMAND;
+ return -EINVAL;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int halt_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+ int rc;
+
+ rc = fsg_set_halt(fsg, fsg->bulk_in);
+ if (rc == -EAGAIN)
+ VDBG(fsg, "delayed bulk-in endpoint halt\n");
+ while (rc != 0) {
+ if (rc != -EAGAIN) {
+ WARNING(fsg, "usb_ep_set_halt -> %d\n", rc);
+ rc = 0;
+ break;
+ }
+
+ /* Wait for a short time and then try again */
+ if (msleep_interruptible(100) != 0)
+ return -EINTR;
+ rc = usb_ep_set_halt(fsg->bulk_in);
+ }
+ return rc;
+}
+
+static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+ int rc;
+
+ DBG(fsg, "bulk-in set wedge\n");
+ rc = usb_ep_set_wedge(fsg->bulk_in);
+ if (rc == -EAGAIN)
+ VDBG(fsg, "delayed bulk-in endpoint wedge\n");
+ while (rc != 0) {
+ if (rc != -EAGAIN) {
+ WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc);
+ rc = 0;
+ break;
+ }
+
+ /* Wait for a short time and then try again */
+ if (msleep_interruptible(100) != 0)
+ return -EINTR;
+ rc = usb_ep_set_wedge(fsg->bulk_in);
+ }
+ return rc;
+}
+
+static int throw_away_data(struct fsg_common *common)
+{
+ struct fsg_buffhd *bh;
+ u32 amount;
+ int rc;
+
+ for (bh = common->next_buffhd_to_drain;
+ bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0;
+ bh = common->next_buffhd_to_drain) {
+
+ /* Throw away the data in a filled buffer */
+ if (bh->state == BUF_STATE_FULL) {
+ smp_rmb();
+ bh->state = BUF_STATE_EMPTY;
+ common->next_buffhd_to_drain = bh->next;
+
+ /* A short packet or an error ends everything */
+ if (bh->outreq->actual != bh->outreq->length ||
+ bh->outreq->status != 0) {
+ raise_exception(common,
+ FSG_STATE_ABORT_BULK_OUT);
+ return -EINTR;
+ }
+ continue;
+ }
+
+ /* Try to submit another request if we need one */
+ bh = common->next_buffhd_to_fill;
+ if (bh->state == BUF_STATE_EMPTY
+ && common->usb_amount_left > 0) {
+ amount = min(common->usb_amount_left, FSG_BUFLEN);
+
+ /*
+ * amount is always divisible by 512, hence by
+ * the bulk-out maxpacket size.
+ */
+ bh->outreq->length = amount;
+ bh->bulk_out_intended_length = amount;
+ bh->outreq->short_not_ok = 1;
+ if (!start_out_transfer(common, bh))
+ /* Dunno what to do if common->fsg is NULL */
+ return -EIO;
+ common->next_buffhd_to_fill = bh->next;
+ common->usb_amount_left -= amount;
+ continue;
+ }
+
+ /* Otherwise wait for something to happen */
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int finish_reply(struct fsg_common *common)
+{
+ struct fsg_buffhd *bh = common->next_buffhd_to_fill;
+ int rc = 0;
+
+ switch (common->data_dir) {
+ case DATA_DIR_NONE:
+ break; /* Nothing to send */
+
+ /*
+ * If we don't know whether the host wants to read or write,
+ * this must be CB or CBI with an unknown command. We mustn't
+ * try to send or receive any data. So stall both bulk pipes
+ * if we can and wait for a reset.
+ */
+ case DATA_DIR_UNKNOWN:
+ if (!common->can_stall) {
+ /* Nothing */
+ } else if (fsg_is_set(common)) {
+ fsg_set_halt(common->fsg, common->fsg->bulk_out);
+ rc = halt_bulk_in_endpoint(common->fsg);
+ } else {
+ /* Don't know what to do if common->fsg is NULL */
+ rc = -EIO;
+ }
+ break;
+
+ /* All but the last buffer of data must have already been sent */
+ case DATA_DIR_TO_HOST:
+ if (common->data_size == 0) {
+ /* Nothing to send */
+
+ /* Don't know what to do if common->fsg is NULL */
+ } else if (!fsg_is_set(common)) {
+ rc = -EIO;
+
+ /* If there's no residue, simply send the last buffer */
+ } else if (common->residue == 0) {
+ bh->inreq->zero = 0;
+ if (!start_in_transfer(common, bh))
+ return -EIO;
+ common->next_buffhd_to_fill = bh->next;
+
+ /*
+ * For Bulk-only, mark the end of the data with a short
+ * packet. If we are allowed to stall, halt the bulk-in
+ * endpoint. (Note: This violates the Bulk-Only Transport
+ * specification, which requires us to pad the data if we
+ * don't halt the endpoint. Presumably nobody will mind.)
+ */
+ } else {
+ bh->inreq->zero = 1;
+ if (!start_in_transfer(common, bh))
+ rc = -EIO;
+ common->next_buffhd_to_fill = bh->next;
+ if (common->can_stall)
+ rc = halt_bulk_in_endpoint(common->fsg);
+ }
+ break;
+
+ /*
+ * We have processed all we want from the data the host has sent.
+ * There may still be outstanding bulk-out requests.
+ */
+ case DATA_DIR_FROM_HOST:
+ if (common->residue == 0) {
+ /* Nothing to receive */
+
+ /* Did the host stop sending unexpectedly early? */
+ } else if (common->short_packet_received) {
+ raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+ rc = -EINTR;
+
+ /*
+ * We haven't processed all the incoming data. Even though
+ * we may be allowed to stall, doing so would cause a race.
+ * The controller may already have ACK'ed all the remaining
+ * bulk-out packets, in which case the host wouldn't see a
+ * STALL. Not realizing the endpoint was halted, it wouldn't
+ * clear the halt -- leading to problems later on.
+ */
+#if 0
+ } else if (common->can_stall) {
+ if (fsg_is_set(common))
+ fsg_set_halt(common->fsg,
+ common->fsg->bulk_out);
+ raise_exception(common, FSG_STATE_ABORT_BULK_OUT);
+ rc = -EINTR;
+#endif
+
+ /*
+ * We can't stall. Read in the excess data and throw it
+ * all away.
+ */
+ } else {
+ rc = throw_away_data(common);
+ }
+ break;
+ }
+ return rc;
+}
+
+static int send_status(struct fsg_common *common)
+{
+ struct fsg_lun *curlun = common->curlun;
+ struct fsg_buffhd *bh;
+ struct bulk_cs_wrap *csw;
+ int rc;
+ u8 status = USB_STATUS_PASS;
+ u32 sd, sdinfo = 0;
+
+ /* Wait for the next buffer to become available */
+ bh = common->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+
+ if (curlun) {
+ sd = curlun->sense_data;
+ sdinfo = curlun->sense_data_info;
+ } else if (common->bad_lun_okay)
+ sd = SS_NO_SENSE;
+ else
+ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+
+ if (common->phase_error) {
+ DBG(common, "sending phase-error status\n");
+ status = USB_STATUS_PHASE_ERROR;
+ sd = SS_INVALID_COMMAND;
+ } else if (sd != SS_NO_SENSE) {
+ DBG(common, "sending command-failure status\n");
+ status = USB_STATUS_FAIL;
+ VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;"
+ " info x%x\n",
+ SK(sd), ASC(sd), ASCQ(sd), sdinfo);
+ }
+
+ /* Store and send the Bulk-only CSW */
+ csw = (void *)bh->buf;
+
+ csw->Signature = cpu_to_le32(USB_BULK_CS_SIG);
+ csw->Tag = common->tag;
+ csw->Residue = cpu_to_le32(common->residue);
+ csw->Status = status;
+
+ bh->inreq->length = USB_BULK_CS_WRAP_LEN;
+ bh->inreq->zero = 0;
+ if (!start_in_transfer(common, bh))
+ /* Don't know what to do if common->fsg is NULL */
+ return -EIO;
+
+ common->next_buffhd_to_fill = bh->next;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Check whether the command is properly formed and whether its data size
+ * and direction agree with the values we already have.
+ */
+static int check_command(struct fsg_common *common, int cmnd_size,
+ enum data_direction data_dir, unsigned int mask,
+ int needs_medium, const char *name)
+{
+ int i;
+ int lun = common->cmnd[1] >> 5;
+ static const char dirletter[4] = {'u', 'o', 'i', 'n'};
+ char hdlen[20];
+ struct fsg_lun *curlun;
+
+ hdlen[0] = 0;
+ if (common->data_dir != DATA_DIR_UNKNOWN)
+ sprintf(hdlen, ", H%c=%u", dirletter[(int) common->data_dir],
+ common->data_size);
+ VDBG(common, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n",
+ name, cmnd_size, dirletter[(int) data_dir],
+ common->data_size_from_cmnd, common->cmnd_size, hdlen);
+
+ /*
+ * We can't reply at all until we know the correct data direction
+ * and size.
+ */
+ if (common->data_size_from_cmnd == 0)
+ data_dir = DATA_DIR_NONE;
+ if (common->data_size < common->data_size_from_cmnd) {
+ /*
+ * Host data size < Device data size is a phase error.
+ * Carry out the command, but only transfer as much as
+ * we are allowed.
+ */
+ common->data_size_from_cmnd = common->data_size;
+ common->phase_error = 1;
+ }
+ common->residue = common->data_size;
+ common->usb_amount_left = common->data_size;
+
+ /* Conflicting data directions is a phase error */
+ if (common->data_dir != data_dir && common->data_size_from_cmnd > 0) {
+ common->phase_error = 1;
+ return -EINVAL;
+ }
+
+ /* Verify the length of the command itself */
+ if (cmnd_size != common->cmnd_size) {
+
+ /*
+ * Special case workaround: There are plenty of buggy SCSI
+ * implementations. Many have issues with cbw->Length
+ * field passing a wrong command size. For those cases we
+ * always try to work around the problem by using the length
+ * sent by the host side provided it is at least as large
+ * as the correct command length.
+ * Examples of such cases would be MS-Windows, which issues
+ * REQUEST SENSE with cbw->Length == 12 where it should
+ * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and
+ * REQUEST SENSE with cbw->Length == 10 where it should
+ * be 6 as well.
+ */
+ if (cmnd_size <= common->cmnd_size) {
+ DBG(common, "%s is buggy! Expected length %d "
+ "but we got %d\n", name,
+ cmnd_size, common->cmnd_size);
+ cmnd_size = common->cmnd_size;
+ } else {
+ common->phase_error = 1;
+ return -EINVAL;
+ }
+ }
+
+ /* Check that the LUN values are consistent */
+ if (common->lun != lun)
+ DBG(common, "using LUN %d from CBW, not LUN %d from CDB\n",
+ common->lun, lun);
+
+ /* Check the LUN */
+ if (common->lun < common->nluns) {
+ curlun = &common->luns[common->lun];
+ common->curlun = curlun;
+ if (common->cmnd[0] != REQUEST_SENSE) {
+ curlun->sense_data = SS_NO_SENSE;
+ curlun->sense_data_info = 0;
+ curlun->info_valid = 0;
+ }
+ } else {
+ common->curlun = NULL;
+ curlun = NULL;
+ common->bad_lun_okay = 0;
+
+ /*
+ * INQUIRY and REQUEST SENSE commands are explicitly allowed
+ * to use unsupported LUNs; all others may not.
+ */
+ if (common->cmnd[0] != INQUIRY &&
+ common->cmnd[0] != REQUEST_SENSE) {
+ DBG(common, "unsupported LUN %d\n", common->lun);
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * If a unit attention condition exists, only INQUIRY and
+ * REQUEST SENSE commands are allowed; anything else must fail.
+ */
+ if (curlun && curlun->unit_attention_data != SS_NO_SENSE &&
+ common->cmnd[0] != INQUIRY &&
+ common->cmnd[0] != REQUEST_SENSE) {
+ curlun->sense_data = curlun->unit_attention_data;
+ curlun->unit_attention_data = SS_NO_SENSE;
+ return -EINVAL;
+ }
+
+ /* Check that only command bytes listed in the mask are non-zero */
+ common->cmnd[1] &= 0x1f; /* Mask away the LUN */
+ for (i = 1; i < cmnd_size; ++i) {
+ if (common->cmnd[i] && !(mask & (1 << i))) {
+ if (curlun)
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ }
+
+ /* If the medium isn't mounted and the command needs to access
+ * it, return an error. */
+ if (curlun && !fsg_lun_is_open(curlun) && needs_medium) {
+ curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int do_scsi_command(struct fsg_common *common)
+{
+ struct fsg_buffhd *bh;
+ int rc;
+ int reply = -EINVAL;
+ int i;
+ static char unknown[16];
+
+ dump_cdb(common);
+
+ /* Wait for the next buffer to become available for data or status */
+ bh = common->next_buffhd_to_fill;
+ common->next_buffhd_to_drain = bh;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+ common->phase_error = 0;
+ common->short_packet_received = 0;
+
+ down_read(&common->filesem); /* We're using the backing file */
+ switch (common->cmnd[0]) {
+
+ case INQUIRY:
+ common->data_size_from_cmnd = common->cmnd[4];
+ reply = check_command(common, 6, DATA_DIR_TO_HOST,
+ (1<<4), 0,
+ "INQUIRY");
+ if (reply == 0)
+ reply = do_inquiry(common, bh);
+ break;
+
+ case MODE_SELECT:
+ common->data_size_from_cmnd = common->cmnd[4];
+ reply = check_command(common, 6, DATA_DIR_FROM_HOST,
+ (1<<1) | (1<<4), 0,
+ "MODE SELECT(6)");
+ if (reply == 0)
+ reply = do_mode_select(common, bh);
+ break;
+
+ case MODE_SELECT_10:
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]);
+ reply = check_command(common, 10, DATA_DIR_FROM_HOST,
+ (1<<1) | (3<<7), 0,
+ "MODE SELECT(10)");
+ if (reply == 0)
+ reply = do_mode_select(common, bh);
+ break;
+
+ case MODE_SENSE:
+ common->data_size_from_cmnd = common->cmnd[4];
+ reply = check_command(common, 6, DATA_DIR_TO_HOST,
+ (1<<1) | (1<<2) | (1<<4), 0,
+ "MODE SENSE(6)");
+ if (reply == 0)
+ reply = do_mode_sense(common, bh);
+ break;
+
+ case MODE_SENSE_10:
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]);
+ reply = check_command(common, 10, DATA_DIR_TO_HOST,
+ (1<<1) | (1<<2) | (3<<7), 0,
+ "MODE SENSE(10)");
+ if (reply == 0)
+ reply = do_mode_sense(common, bh);
+ break;
+
+ case ALLOW_MEDIUM_REMOVAL:
+ common->data_size_from_cmnd = 0;
+ reply = check_command(common, 6, DATA_DIR_NONE,
+ (1<<4), 0,
+ "PREVENT-ALLOW MEDIUM REMOVAL");
+ if (reply == 0)
+ reply = do_prevent_allow(common);
+ break;
+
+ case READ_6:
+ i = common->cmnd[4];
+ common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9;
+ reply = check_command(common, 6, DATA_DIR_TO_HOST,
+ (7<<1) | (1<<4), 1,
+ "READ(6)");
+ if (reply == 0)
+ reply = do_read(common);
+ break;
+
+ case READ_10:
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]) << 9;
+ reply = check_command(common, 10, DATA_DIR_TO_HOST,
+ (1<<1) | (0xf<<2) | (3<<7), 1,
+ "READ(10)");
+ if (reply == 0)
+ reply = do_read(common);
+ break;
+
+ case READ_12:
+ common->data_size_from_cmnd =
+ get_unaligned_be32(&common->cmnd[6]) << 9;
+ reply = check_command(common, 12, DATA_DIR_TO_HOST,
+ (1<<1) | (0xf<<2) | (0xf<<6), 1,
+ "READ(12)");
+ if (reply == 0)
+ reply = do_read(common);
+ break;
+
+ case READ_CAPACITY:
+ common->data_size_from_cmnd = 8;
+ reply = check_command(common, 10, DATA_DIR_TO_HOST,
+ (0xf<<2) | (1<<8), 1,
+ "READ CAPACITY");
+ if (reply == 0)
+ reply = do_read_capacity(common, bh);
+ break;
+
+ case READ_HEADER:
+ if (!common->curlun || !common->curlun->cdrom)
+ goto unknown_cmnd;
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]);
+ reply = check_command(common, 10, DATA_DIR_TO_HOST,
+ (3<<7) | (0x1f<<1), 1,
+ "READ HEADER");
+ if (reply == 0)
+ reply = do_read_header(common, bh);
+ break;
+
+ case READ_TOC:
+ if (!common->curlun || !common->curlun->cdrom)
+ goto unknown_cmnd;
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]);
+ reply = check_command(common, 10, DATA_DIR_TO_HOST,
+ (7<<6) | (1<<1), 1,
+ "READ TOC");
+ if (reply == 0)
+ reply = do_read_toc(common, bh);
+ break;
+
+ case READ_FORMAT_CAPACITIES:
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]);
+ reply = check_command(common, 10, DATA_DIR_TO_HOST,
+ (3<<7), 1,
+ "READ FORMAT CAPACITIES");
+ if (reply == 0)
+ reply = do_read_format_capacities(common, bh);
+ break;
+
+ case REQUEST_SENSE:
+ common->data_size_from_cmnd = common->cmnd[4];
+ reply = check_command(common, 6, DATA_DIR_TO_HOST,
+ (1<<4), 0,
+ "REQUEST SENSE");
+ if (reply == 0)
+ reply = do_request_sense(common, bh);
+ break;
+
+ case START_STOP:
+ common->data_size_from_cmnd = 0;
+ reply = check_command(common, 6, DATA_DIR_NONE,
+ (1<<1) | (1<<4), 0,
+ "START-STOP UNIT");
+ if (reply == 0)
+ reply = do_start_stop(common);
+ break;
+
+ case SYNCHRONIZE_CACHE:
+ common->data_size_from_cmnd = 0;
+ reply = check_command(common, 10, DATA_DIR_NONE,
+ (0xf<<2) | (3<<7), 1,
+ "SYNCHRONIZE CACHE");
+ if (reply == 0)
+ reply = do_synchronize_cache(common);
+ break;
+
+ case TEST_UNIT_READY:
+ common->data_size_from_cmnd = 0;
+ reply = check_command(common, 6, DATA_DIR_NONE,
+ 0, 1,
+ "TEST UNIT READY");
+ break;
+
+ /*
+ * Although optional, this command is used by MS-Windows. We
+ * support a minimal version: BytChk must be 0.
+ */
+ case VERIFY:
+ common->data_size_from_cmnd = 0;
+ reply = check_command(common, 10, DATA_DIR_NONE,
+ (1<<1) | (0xf<<2) | (3<<7), 1,
+ "VERIFY");
+ if (reply == 0)
+ reply = do_verify(common);
+ break;
+
+ case WRITE_6:
+ i = common->cmnd[4];
+ common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9;
+ reply = check_command(common, 6, DATA_DIR_FROM_HOST,
+ (7<<1) | (1<<4), 1,
+ "WRITE(6)");
+ if (reply == 0)
+ reply = do_write(common);
+ break;
+
+ case WRITE_10:
+ common->data_size_from_cmnd =
+ get_unaligned_be16(&common->cmnd[7]) << 9;
+ reply = check_command(common, 10, DATA_DIR_FROM_HOST,
+ (1<<1) | (0xf<<2) | (3<<7), 1,
+ "WRITE(10)");
+ if (reply == 0)
+ reply = do_write(common);
+ break;
+
+ case WRITE_12:
+ common->data_size_from_cmnd =
+ get_unaligned_be32(&common->cmnd[6]) << 9;
+ reply = check_command(common, 12, DATA_DIR_FROM_HOST,
+ (1<<1) | (0xf<<2) | (0xf<<6), 1,
+ "WRITE(12)");
+ if (reply == 0)
+ reply = do_write(common);
+ break;
+
+ /*
+ * Some mandatory commands that we recognize but don't implement.
+ * They don't mean much in this setting. It's left as an exercise
+ * for anyone interested to implement RESERVE and RELEASE in terms
+ * of Posix locks.
+ */
+ case FORMAT_UNIT:
+ case RELEASE:
+ case RESERVE:
+ case SEND_DIAGNOSTIC:
+ /* Fall through */
+
+ default:
+unknown_cmnd:
+ common->data_size_from_cmnd = 0;
+ sprintf(unknown, "Unknown x%02x", common->cmnd[0]);
+ reply = check_command(common, common->cmnd_size,
+ DATA_DIR_UNKNOWN, 0xff, 0, unknown);
+ if (reply == 0) {
+ common->curlun->sense_data = SS_INVALID_COMMAND;
+ reply = -EINVAL;
+ }
+ break;
+ }
+ up_read(&common->filesem);
+
+ if (reply == -EINTR || signal_pending(current))
+ return -EINTR;
+
+ /* Set up the single reply buffer for finish_reply() */
+ if (reply == -EINVAL)
+ reply = 0; /* Error reply length */
+ if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) {
+ reply = min((u32)reply, common->data_size_from_cmnd);
+ bh->inreq->length = reply;
+ bh->state = BUF_STATE_FULL;
+ common->residue -= reply;
+ } /* Otherwise it's already set */
+
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct usb_request *req = bh->outreq;
+ struct fsg_bulk_cb_wrap *cbw = req->buf;
+ struct fsg_common *common = fsg->common;
+
+ /* Was this a real packet? Should it be ignored? */
+ if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags))
+ return -EINVAL;
+
+ /* Is the CBW valid? */
+ if (req->actual != USB_BULK_CB_WRAP_LEN ||
+ cbw->Signature != cpu_to_le32(
+ USB_BULK_CB_SIG)) {
+ DBG(fsg, "invalid CBW: len %u sig 0x%x\n",
+ req->actual,
+ le32_to_cpu(cbw->Signature));
+
+ /*
+ * The Bulk-only spec says we MUST stall the IN endpoint
+ * (6.6.1), so it's unavoidable. It also says we must
+ * retain this state until the next reset, but there's
+ * no way to tell the controller driver it should ignore
+ * Clear-Feature(HALT) requests.
+ *
+ * We aren't required to halt the OUT endpoint; instead
+ * we can simply accept and discard any data received
+ * until the next reset.
+ */
+ wedge_bulk_in_endpoint(fsg);
+ set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+ return -EINVAL;
+ }
+
+ /* Is the CBW meaningful? */
+ if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG ||
+ cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) {
+ DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, "
+ "cmdlen %u\n",
+ cbw->Lun, cbw->Flags, cbw->Length);
+
+ /*
+ * We can do anything we want here, so let's stall the
+ * bulk pipes if we are allowed to.
+ */
+ if (common->can_stall) {
+ fsg_set_halt(fsg, fsg->bulk_out);
+ halt_bulk_in_endpoint(fsg);
+ }
+ return -EINVAL;
+ }
+
+ /* Save the command for later */
+ common->cmnd_size = cbw->Length;
+ memcpy(common->cmnd, cbw->CDB, common->cmnd_size);
+ if (cbw->Flags & USB_BULK_IN_FLAG)
+ common->data_dir = DATA_DIR_TO_HOST;
+ else
+ common->data_dir = DATA_DIR_FROM_HOST;
+ common->data_size = le32_to_cpu(cbw->DataTransferLength);
+ if (common->data_size == 0)
+ common->data_dir = DATA_DIR_NONE;
+ common->lun = cbw->Lun;
+ common->tag = cbw->Tag;
+ return 0;
+}
+
+static int get_next_command(struct fsg_common *common)
+{
+ struct fsg_buffhd *bh;
+ int rc = 0;
+
+ /* Wait for the next buffer to become available */
+ bh = common->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+
+ /* Queue a request to read a Bulk-only CBW */
+ set_bulk_out_req_length(common, bh, USB_BULK_CB_WRAP_LEN);
+ bh->outreq->short_not_ok = 1;
+ if (!start_out_transfer(common, bh))
+ /* Don't know what to do if common->fsg is NULL */
+ return -EIO;
+
+ /*
+ * We will drain the buffer in software, which means we
+ * can reuse it for the next filling. No need to advance
+ * next_buffhd_to_fill.
+ */
+
+ /* Wait for the CBW to arrive */
+ while (bh->state != BUF_STATE_FULL) {
+ rc = sleep_thread(common);
+ if (rc)
+ return rc;
+ }
+ smp_rmb();
+ rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO;
+ bh->state = BUF_STATE_EMPTY;
+
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int enable_endpoint(struct fsg_common *common, struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *d)
+{
+ int rc;
+
+ ep->driver_data = common;
+ rc = usb_ep_enable(ep, d);
+ if (rc)
+ ERROR(common, "can't enable %s, result %d\n", ep->name, rc);
+ return rc;
+}
+
+static int alloc_request(struct fsg_common *common, struct usb_ep *ep,
+ struct usb_request **preq)
+{
+ *preq = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (*preq)
+ return 0;
+ ERROR(common, "can't allocate request for %s\n", ep->name);
+ return -ENOMEM;
+}
+
+/* Reset interface setting and re-init endpoint state (toggle etc). */
+static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg)
+{
+ const struct usb_endpoint_descriptor *d;
+ struct fsg_dev *fsg;
+ int i, rc = 0;
+
+ if (common->running)
+ DBG(common, "reset interface\n");
+
+reset:
+ /* Deallocate the requests */
+ if (common->fsg) {
+ fsg = common->fsg;
+
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ struct fsg_buffhd *bh = &common->buffhds[i];
+
+ if (bh->inreq) {
+ usb_ep_free_request(fsg->bulk_in, bh->inreq);
+ bh->inreq = NULL;
+ }
+ if (bh->outreq) {
+ usb_ep_free_request(fsg->bulk_out, bh->outreq);
+ bh->outreq = NULL;
+ }
+ }
+
+ /* Disable the endpoints */
+ if (fsg->bulk_in_enabled) {
+ usb_ep_disable(fsg->bulk_in);
+ fsg->bulk_in_enabled = 0;
+ }
+ if (fsg->bulk_out_enabled) {
+ usb_ep_disable(fsg->bulk_out);
+ fsg->bulk_out_enabled = 0;
+ }
+
+ common->fsg = NULL;
+ wake_up(&common->fsg_wait);
+ }
+
+ common->running = 0;
+ if (!new_fsg || rc)
+ return rc;
+
+ common->fsg = new_fsg;
+ fsg = common->fsg;
+
+ /* Enable the endpoints */
+ d = fsg_ep_desc(common->gadget,
+ &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc);
+ rc = enable_endpoint(common, fsg->bulk_in, d);
+ if (rc)
+ goto reset;
+ fsg->bulk_in_enabled = 1;
+
+ d = fsg_ep_desc(common->gadget,
+ &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc);
+ rc = enable_endpoint(common, fsg->bulk_out, d);
+ if (rc)
+ goto reset;
+ fsg->bulk_out_enabled = 1;
+ common->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize);
+ clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+
+ /* Allocate the requests */
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ struct fsg_buffhd *bh = &common->buffhds[i];
+
+ rc = alloc_request(common, fsg->bulk_in, &bh->inreq);
+ if (rc)
+ goto reset;
+ rc = alloc_request(common, fsg->bulk_out, &bh->outreq);
+ if (rc)
+ goto reset;
+ bh->inreq->buf = bh->outreq->buf = bh->buf;
+ bh->inreq->context = bh->outreq->context = bh;
+ bh->inreq->complete = bulk_in_complete;
+ bh->outreq->complete = bulk_out_complete;
+ }
+
+ common->running = 1;
+ for (i = 0; i < common->nluns; ++i)
+ common->luns[i].unit_attention_data = SS_RESET_OCCURRED;
+ return rc;
+}
+
+
+/****************************** ALT CONFIGS ******************************/
+
+static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct fsg_dev *fsg = fsg_from_func(f);
+ fsg->common->new_fsg = fsg;
+ raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE);
+ return USB_GADGET_DELAYED_STATUS;
+}
+
+static void fsg_disable(struct usb_function *f)
+{
+ struct fsg_dev *fsg = fsg_from_func(f);
+ fsg->common->new_fsg = NULL;
+ raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void handle_exception(struct fsg_common *common)
+{
+ siginfo_t info;
+ int i;
+ struct fsg_buffhd *bh;
+ enum fsg_state old_state;
+ struct fsg_lun *curlun;
+ unsigned int exception_req_tag;
+
+ /*
+ * Clear the existing signals. Anything but SIGUSR1 is converted
+ * into a high-priority EXIT exception.
+ */
+ for (;;) {
+ int sig =
+ dequeue_signal_lock(current, &current->blocked, &info);
+ if (!sig)
+ break;
+ if (sig != SIGUSR1) {
+ if (common->state < FSG_STATE_EXIT)
+ DBG(common, "Main thread exiting on signal\n");
+ raise_exception(common, FSG_STATE_EXIT);
+ }
+ }
+
+ /* Cancel all the pending transfers */
+ if (likely(common->fsg)) {
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ bh = &common->buffhds[i];
+ if (bh->inreq_busy)
+ usb_ep_dequeue(common->fsg->bulk_in, bh->inreq);
+ if (bh->outreq_busy)
+ usb_ep_dequeue(common->fsg->bulk_out,
+ bh->outreq);
+ }
+
+ /* Wait until everything is idle */
+ for (;;) {
+ int num_active = 0;
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ bh = &common->buffhds[i];
+ num_active += bh->inreq_busy + bh->outreq_busy;
+ }
+ if (num_active == 0)
+ break;
+ if (sleep_thread(common))
+ return;
+ }
+
+ /* Clear out the controller's fifos */
+ if (common->fsg->bulk_in_enabled)
+ usb_ep_fifo_flush(common->fsg->bulk_in);
+ if (common->fsg->bulk_out_enabled)
+ usb_ep_fifo_flush(common->fsg->bulk_out);
+ }
+
+ /*
+ * Reset the I/O buffer states and pointers, the SCSI
+ * state, and the exception. Then invoke the handler.
+ */
+ spin_lock_irq(&common->lock);
+
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ bh = &common->buffhds[i];
+ bh->state = BUF_STATE_EMPTY;
+ }
+ common->next_buffhd_to_fill = &common->buffhds[0];
+ common->next_buffhd_to_drain = &common->buffhds[0];
+ exception_req_tag = common->exception_req_tag;
+ old_state = common->state;
+
+ if (old_state == FSG_STATE_ABORT_BULK_OUT)
+ common->state = FSG_STATE_STATUS_PHASE;
+ else {
+ for (i = 0; i < common->nluns; ++i) {
+ curlun = &common->luns[i];
+ curlun->prevent_medium_removal = 0;
+ curlun->sense_data = SS_NO_SENSE;
+ curlun->unit_attention_data = SS_NO_SENSE;
+ curlun->sense_data_info = 0;
+ curlun->info_valid = 0;
+ }
+ common->state = FSG_STATE_IDLE;
+ }
+ spin_unlock_irq(&common->lock);
+
+ /* Carry out any extra actions required for the exception */
+ switch (old_state) {
+ case FSG_STATE_ABORT_BULK_OUT:
+ send_status(common);
+ spin_lock_irq(&common->lock);
+ if (common->state == FSG_STATE_STATUS_PHASE)
+ common->state = FSG_STATE_IDLE;
+ spin_unlock_irq(&common->lock);
+ break;
+
+ case FSG_STATE_RESET:
+ /*
+ * In case we were forced against our will to halt a
+ * bulk endpoint, clear the halt now. (The SuperH UDC
+ * requires this.)
+ */
+ if (!fsg_is_set(common))
+ break;
+ if (test_and_clear_bit(IGNORE_BULK_OUT,
+ &common->fsg->atomic_bitflags))
+ usb_ep_clear_halt(common->fsg->bulk_in);
+
+ if (common->ep0_req_tag == exception_req_tag)
+ ep0_queue(common); /* Complete the status stage */
+
+ /*
+ * Technically this should go here, but it would only be
+ * a waste of time. Ditto for the INTERFACE_CHANGE and
+ * CONFIG_CHANGE cases.
+ */
+ /* for (i = 0; i < common->nluns; ++i) */
+ /* common->luns[i].unit_attention_data = */
+ /* SS_RESET_OCCURRED; */
+ break;
+
+ case FSG_STATE_CONFIG_CHANGE:
+ do_set_interface(common, common->new_fsg);
+ if (common->new_fsg)
+ usb_composite_setup_continue(common->cdev);
+ break;
+
+ case FSG_STATE_EXIT:
+ case FSG_STATE_TERMINATED:
+ do_set_interface(common, NULL); /* Free resources */
+ spin_lock_irq(&common->lock);
+ common->state = FSG_STATE_TERMINATED; /* Stop the thread */
+ spin_unlock_irq(&common->lock);
+ break;
+
+ case FSG_STATE_INTERFACE_CHANGE:
+ case FSG_STATE_DISCONNECT:
+ case FSG_STATE_COMMAND_PHASE:
+ case FSG_STATE_DATA_PHASE:
+ case FSG_STATE_STATUS_PHASE:
+ case FSG_STATE_IDLE:
+ break;
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_main_thread(void *common_)
+{
+ struct fsg_common *common = common_;
+
+ /*
+ * Allow the thread to be killed by a signal, but set the signal mask
+ * to block everything but INT, TERM, KILL, and USR1.
+ */
+ allow_signal(SIGINT);
+ allow_signal(SIGTERM);
+ allow_signal(SIGKILL);
+ allow_signal(SIGUSR1);
+
+ /* Allow the thread to be frozen */
+ set_freezable();
+
+ /*
+ * Arrange for userspace references to be interpreted as kernel
+ * pointers. That way we can pass a kernel pointer to a routine
+ * that expects a __user pointer and it will work okay.
+ */
+ set_fs(get_ds());
+
+ /* The main loop */
+ while (common->state != FSG_STATE_TERMINATED) {
+ if (exception_in_progress(common) || signal_pending(current)) {
+ handle_exception(common);
+ continue;
+ }
+
+ if (!common->running) {
+ sleep_thread(common);
+ continue;
+ }
+
+ if (get_next_command(common))
+ continue;
+
+ spin_lock_irq(&common->lock);
+ if (!exception_in_progress(common))
+ common->state = FSG_STATE_DATA_PHASE;
+ spin_unlock_irq(&common->lock);
+
+ if (do_scsi_command(common) || finish_reply(common))
+ continue;
+
+ spin_lock_irq(&common->lock);
+ if (!exception_in_progress(common))
+ common->state = FSG_STATE_STATUS_PHASE;
+ spin_unlock_irq(&common->lock);
+
+ if (send_status(common))
+ continue;
+
+ spin_lock_irq(&common->lock);
+ if (!exception_in_progress(common))
+ common->state = FSG_STATE_IDLE;
+ spin_unlock_irq(&common->lock);
+ }
+
+ spin_lock_irq(&common->lock);
+ common->thread_task = NULL;
+ spin_unlock_irq(&common->lock);
+
+ if (!common->ops || !common->ops->thread_exits
+ || common->ops->thread_exits(common) < 0) {
+ struct fsg_lun *curlun = common->luns;
+ unsigned i = common->nluns;
+
+ down_write(&common->filesem);
+ for (; i--; ++curlun) {
+ if (!fsg_lun_is_open(curlun))
+ continue;
+
+ fsg_lun_close(curlun);
+ curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
+ }
+ up_write(&common->filesem);
+ }
+
+ /* Let fsg_unbind() know the thread has exited */
+ complete_and_exit(&common->thread_notifier, 0);
+}
+
+
+/*************************** DEVICE ATTRIBUTES ***************************/
+
+/* Write permission is checked per LUN in store_*() functions. */
+static DEVICE_ATTR(ro, 0644, fsg_show_ro, fsg_store_ro);
+static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, fsg_store_nofua);
+static DEVICE_ATTR(file, 0644, fsg_show_file, fsg_store_file);
+
+
+/****************************** FSG COMMON ******************************/
+
+static void fsg_common_release(struct kref *ref);
+
+static void fsg_lun_release(struct device *dev)
+{
+ /* Nothing needs to be done */
+}
+
+static inline void fsg_common_get(struct fsg_common *common)
+{
+ kref_get(&common->ref);
+}
+
+static inline void fsg_common_put(struct fsg_common *common)
+{
+ kref_put(&common->ref, fsg_common_release);
+}
+
+static struct fsg_common *fsg_common_init(struct fsg_common *common,
+ struct usb_composite_dev *cdev,
+ struct fsg_config *cfg)
+{
+ struct usb_gadget *gadget = cdev->gadget;
+ struct fsg_buffhd *bh;
+ struct fsg_lun *curlun;
+ struct fsg_lun_config *lcfg;
+ int nluns, i, rc;
+ char *pathbuf;
+
+ /* Find out how many LUNs there should be */
+ nluns = cfg->nluns;
+ if (nluns < 1 || nluns > FSG_MAX_LUNS) {
+ dev_err(&gadget->dev, "invalid number of LUNs: %u\n", nluns);
+ return ERR_PTR(-EINVAL);
+ }
+
+ /* Allocate? */
+ if (!common) {
+ common = kzalloc(sizeof *common, GFP_KERNEL);
+ if (!common)
+ return ERR_PTR(-ENOMEM);
+ common->free_storage_on_release = 1;
+ } else {
+ memset(common, 0, sizeof *common);
+ common->free_storage_on_release = 0;
+ }
+
+ common->ops = cfg->ops;
+ common->private_data = cfg->private_data;
+
+ common->gadget = gadget;
+ common->ep0 = gadget->ep0;
+ common->ep0req = cdev->req;
+ common->cdev = cdev;
+
+ /* Maybe allocate device-global string IDs, and patch descriptors */
+ if (fsg_strings[FSG_STRING_INTERFACE].id == 0) {
+ rc = usb_string_id(cdev);
+ if (unlikely(rc < 0))
+ goto error_release;
+ fsg_strings[FSG_STRING_INTERFACE].id = rc;
+ fsg_intf_desc.iInterface = rc;
+ }
+
+ /*
+ * Create the LUNs, open their backing files, and register the
+ * LUN devices in sysfs.
+ */
+ curlun = kzalloc(nluns * sizeof *curlun, GFP_KERNEL);
+ if (unlikely(!curlun)) {
+ rc = -ENOMEM;
+ goto error_release;
+ }
+ common->luns = curlun;
+
+ init_rwsem(&common->filesem);
+
+ for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun, ++lcfg) {
+ curlun->cdrom = !!lcfg->cdrom;
+ curlun->ro = lcfg->cdrom || lcfg->ro;
+ curlun->initially_ro = curlun->ro;
+ curlun->removable = lcfg->removable;
+ curlun->dev.release = fsg_lun_release;
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+ /* use "usb_mass_storage" platform device as parent */
+ curlun->dev.parent = &cfg->pdev->dev;
+#else
+ curlun->dev.parent = &gadget->dev;
+#endif
+ /* curlun->dev.driver = &fsg_driver.driver; XXX */
+ dev_set_drvdata(&curlun->dev, &common->filesem);
+ dev_set_name(&curlun->dev,
+ cfg->lun_name_format
+ ? cfg->lun_name_format
+ : "lun%d",
+ i);
+
+ rc = device_register(&curlun->dev);
+ if (rc) {
+ INFO(common, "failed to register LUN%d: %d\n", i, rc);
+ common->nluns = i;
+ put_device(&curlun->dev);
+ goto error_release;
+ }
+
+ rc = device_create_file(&curlun->dev, &dev_attr_ro);
+ if (rc)
+ goto error_luns;
+ rc = device_create_file(&curlun->dev, &dev_attr_file);
+ if (rc)
+ goto error_luns;
+ rc = device_create_file(&curlun->dev, &dev_attr_nofua);
+ if (rc)
+ goto error_luns;
+
+ if (lcfg->filename) {
+ rc = fsg_lun_open(curlun, lcfg->filename);
+ if (rc)
+ goto error_luns;
+ } else if (!curlun->removable) {
+ ERROR(common, "no file given for LUN%d\n", i);
+ rc = -EINVAL;
+ goto error_luns;
+ }
+ }
+ common->nluns = nluns;
+
+ /* Data buffers cyclic list */
+ bh = common->buffhds;
+ i = FSG_NUM_BUFFERS;
+ goto buffhds_first_it;
+ do {
+ bh->next = bh + 1;
+ ++bh;
+buffhds_first_it:
+ bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL);
+ if (unlikely(!bh->buf)) {
+ rc = -ENOMEM;
+ goto error_release;
+ }
+ } while (--i);
+ bh->next = common->buffhds;
+
+ /* Prepare inquiryString */
+ if (cfg->release != 0xffff) {
+ i = cfg->release;
+ } else {
+ i = usb_gadget_controller_number(gadget);
+ if (i >= 0) {
+ i = 0x0300 + i;
+ } else {
+ WARNING(common, "controller '%s' not recognized\n",
+ gadget->name);
+ i = 0x0399;
+ }
+ }
+ snprintf(common->inquiry_string, sizeof common->inquiry_string,
+ "%-8s%-16s%04x", cfg->vendor_name ?: "Linux",
+ /* Assume product name dependent on the first LUN */
+ cfg->product_name ?: (common->luns->cdrom
+ ? "File-Stor Gadget"
+ : "File-CD Gadget"),
+ i);
+
+ /*
+ * Some peripheral controllers are known not to be able to
+ * halt bulk endpoints correctly. If one of them is present,
+ * disable stalls.
+ */
+ common->can_stall = cfg->can_stall &&
+ !(gadget_is_at91(common->gadget));
+
+ spin_lock_init(&common->lock);
+ kref_init(&common->ref);
+
+ /* Tell the thread to start working */
+ common->thread_task =
+ kthread_create(fsg_main_thread, common,
+ cfg->thread_name ?: "file-storage");
+ if (IS_ERR(common->thread_task)) {
+ rc = PTR_ERR(common->thread_task);
+ goto error_release;
+ }
+ init_completion(&common->thread_notifier);
+ init_waitqueue_head(&common->fsg_wait);
+
+ /* Information */
+ INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n");
+ INFO(common, "Number of LUNs=%d\n", common->nluns);
+
+ pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+ for (i = 0, nluns = common->nluns, curlun = common->luns;
+ i < nluns;
+ ++curlun, ++i) {
+ char *p = "(no medium)";
+ if (fsg_lun_is_open(curlun)) {
+ p = "(error)";
+ if (pathbuf) {
+ p = d_path(&curlun->filp->f_path,
+ pathbuf, PATH_MAX);
+ if (IS_ERR(p))
+ p = "(error)";
+ }
+ }
+ LINFO(curlun, "LUN: %s%s%sfile: %s\n",
+ curlun->removable ? "removable " : "",
+ curlun->ro ? "read only " : "",
+ curlun->cdrom ? "CD-ROM " : "",
+ p);
+ }
+ kfree(pathbuf);
+
+ DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task));
+
+ wake_up_process(common->thread_task);
+
+ return common;
+
+error_luns:
+ common->nluns = i + 1;
+error_release:
+ common->state = FSG_STATE_TERMINATED; /* The thread is dead */
+ /* Call fsg_common_release() directly, ref might be not initialised. */
+ fsg_common_release(&common->ref);
+ return ERR_PTR(rc);
+}
+
+static void fsg_common_release(struct kref *ref)
+{
+ struct fsg_common *common = container_of(ref, struct fsg_common, ref);
+
+ /* If the thread isn't already dead, tell it to exit now */
+ if (common->state != FSG_STATE_TERMINATED) {
+ raise_exception(common, FSG_STATE_EXIT);
+ wait_for_completion(&common->thread_notifier);
+ }
+
+ if (likely(common->luns)) {
+ struct fsg_lun *lun = common->luns;
+ unsigned i = common->nluns;
+
+ /* In error recovery common->nluns may be zero. */
+ for (; i; --i, ++lun) {
+ device_remove_file(&lun->dev, &dev_attr_nofua);
+ device_remove_file(&lun->dev, &dev_attr_ro);
+ device_remove_file(&lun->dev, &dev_attr_file);
+ fsg_lun_close(lun);
+ device_unregister(&lun->dev);
+ }
+
+ kfree(common->luns);
+ }
+
+ {
+ struct fsg_buffhd *bh = common->buffhds;
+ unsigned i = FSG_NUM_BUFFERS;
+ do {
+ kfree(bh->buf);
+ } while (++bh, --i);
+ }
+
+ if (common->free_storage_on_release)
+ kfree(common);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void fsg_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct fsg_dev *fsg = fsg_from_func(f);
+ struct fsg_common *common = fsg->common;
+
+ DBG(fsg, "unbind\n");
+ if (fsg->common->fsg == fsg) {
+ fsg->common->new_fsg = NULL;
+ raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE);
+ /* FIXME: make interruptible or killable somehow? */
+ wait_event(common->fsg_wait, common->fsg != fsg);
+ }
+
+ fsg_common_put(common);
+ usb_free_descriptors(fsg->function.descriptors);
+ usb_free_descriptors(fsg->function.hs_descriptors);
+ kfree(fsg);
+}
+
+static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct fsg_dev *fsg = fsg_from_func(f);
+ struct usb_gadget *gadget = c->cdev->gadget;
+ int i;
+ struct usb_ep *ep;
+
+ fsg->gadget = gadget;
+
+ /* New interface */
+ i = usb_interface_id(c, f);
+ if (i < 0)
+ return i;
+ fsg_intf_desc.bInterfaceNumber = i;
+ fsg->interface_number = i;
+
+ /* Find all the endpoints we will use */
+ ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
+ if (!ep)
+ goto autoconf_fail;
+ ep->driver_data = fsg->common; /* claim the endpoint */
+ fsg->bulk_in = ep;
+
+ ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
+ if (!ep)
+ goto autoconf_fail;
+ ep->driver_data = fsg->common; /* claim the endpoint */
+ fsg->bulk_out = ep;
+
+ /* Copy descriptors */
+ f->descriptors = usb_copy_descriptors(fsg_fs_function);
+ if (unlikely(!f->descriptors))
+ return -ENOMEM;
+
+ if (gadget_is_dualspeed(gadget)) {
+ /* Assume endpoint addresses are the same for both speeds */
+ fsg_hs_bulk_in_desc.bEndpointAddress =
+ fsg_fs_bulk_in_desc.bEndpointAddress;
+ fsg_hs_bulk_out_desc.bEndpointAddress =
+ fsg_fs_bulk_out_desc.bEndpointAddress;
+ f->hs_descriptors = usb_copy_descriptors(fsg_hs_function);
+ if (unlikely(!f->hs_descriptors)) {
+ usb_free_descriptors(f->descriptors);
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+
+autoconf_fail:
+ ERROR(fsg, "unable to autoconfigure all endpoints\n");
+ return -ENOTSUPP;
+}
+
+
+/****************************** ADD FUNCTION ******************************/
+
+static struct usb_gadget_strings *fsg_strings_array[] = {
+ &fsg_stringtab,
+ NULL,
+};
+
+static int fsg_bind_config(struct usb_composite_dev *cdev,
+ struct usb_configuration *c,
+ struct fsg_common *common)
+{
+ struct fsg_dev *fsg;
+ int rc;
+
+ fsg = kzalloc(sizeof *fsg, GFP_KERNEL);
+ if (unlikely(!fsg))
+ return -ENOMEM;
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+ fsg->function.name = FUNCTION_NAME;
+#else
+ fsg->function.name = FSG_DRIVER_DESC;
+#endif
+ fsg->function.strings = fsg_strings_array;
+ fsg->function.bind = fsg_bind;
+ fsg->function.unbind = fsg_unbind;
+ fsg->function.setup = fsg_setup;
+ fsg->function.set_alt = fsg_set_alt;
+ fsg->function.disable = fsg_disable;
+
+ fsg->common = common;
+ /*
+ * Our caller holds a reference to common structure so we
+ * don't have to be worry about it being freed until we return
+ * from this function. So instead of incrementing counter now
+ * and decrement in error recovery we increment it only when
+ * call to usb_add_function() was successful.
+ */
+
+ rc = usb_add_function(c, &fsg->function);
+ if (unlikely(rc))
+ kfree(fsg);
+ else
+ fsg_common_get(fsg->common);
+ return rc;
+}
+
+static inline int __deprecated __maybe_unused
+fsg_add(struct usb_composite_dev *cdev, struct usb_configuration *c,
+ struct fsg_common *common)
+{
+ return fsg_bind_config(cdev, c, common);
+}
+
+
+/************************* Module parameters *************************/
+
+struct fsg_module_parameters {
+ char *file[FSG_MAX_LUNS];
+ int ro[FSG_MAX_LUNS];
+ int removable[FSG_MAX_LUNS];
+ int cdrom[FSG_MAX_LUNS];
+ int nofua[FSG_MAX_LUNS];
+
+ unsigned int file_count, ro_count, removable_count, cdrom_count;
+ unsigned int nofua_count;
+ unsigned int luns; /* nluns */
+ int stall; /* can_stall */
+};
+
+#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc) \
+ module_param_array_named(prefix ## name, params.name, type, \
+ &prefix ## params.name ## _count, \
+ S_IRUGO); \
+ MODULE_PARM_DESC(prefix ## name, desc)
+
+#define _FSG_MODULE_PARAM(prefix, params, name, type, desc) \
+ module_param_named(prefix ## name, params.name, type, \
+ S_IRUGO); \
+ MODULE_PARM_DESC(prefix ## name, desc)
+
+#define FSG_MODULE_PARAMETERS(prefix, params) \
+ _FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp, \
+ "names of backing files or devices"); \
+ _FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool, \
+ "true to force read-only"); \
+ _FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool, \
+ "true to simulate removable media"); \
+ _FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool, \
+ "true to simulate CD-ROM instead of disk"); \
+ _FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool, \
+ "true to ignore SCSI WRITE(10,12) FUA bit"); \
+ _FSG_MODULE_PARAM(prefix, params, luns, uint, \
+ "number of LUNs"); \
+ _FSG_MODULE_PARAM(prefix, params, stall, bool, \
+ "false to prevent bulk stalls")
+
+static void
+fsg_config_from_params(struct fsg_config *cfg,
+ const struct fsg_module_parameters *params)
+{
+ struct fsg_lun_config *lun;
+ unsigned i;
+
+ /* Configure LUNs */
+ cfg->nluns =
+ min(params->luns ?: (params->file_count ?: 1u),
+ (unsigned)FSG_MAX_LUNS);
+ for (i = 0, lun = cfg->luns; i < cfg->nluns; ++i, ++lun) {
+ lun->ro = !!params->ro[i];
+ lun->cdrom = !!params->cdrom[i];
+ lun->removable = /* Removable by default */
+ params->removable_count <= i || params->removable[i];
+ lun->filename =
+ params->file_count > i && params->file[i][0]
+ ? params->file[i]
+ : 0;
+ }
+
+ /* Let MSF use defaults */
+ cfg->lun_name_format = 0;
+ cfg->thread_name = 0;
+ cfg->vendor_name = 0;
+ cfg->product_name = 0;
+ cfg->release = 0xffff;
+
+ cfg->ops = NULL;
+ cfg->private_data = NULL;
+
+ /* Finalise */
+ cfg->can_stall = params->stall;
+}
+
+static inline struct fsg_common *
+fsg_common_from_params(struct fsg_common *common,
+ struct usb_composite_dev *cdev,
+ const struct fsg_module_parameters *params)
+ __attribute__((unused));
+static inline struct fsg_common *
+fsg_common_from_params(struct fsg_common *common,
+ struct usb_composite_dev *cdev,
+ const struct fsg_module_parameters *params)
+{
+ struct fsg_config cfg;
+ fsg_config_from_params(&cfg, params);
+ return fsg_common_init(common, cdev, &cfg);
+}
+
+#ifdef CONFIG_USB_ANDROID_MASS_STORAGE
+
+static struct fsg_config fsg_cfg;
+
+static int fsg_probe(struct platform_device *pdev)
+{
+ struct usb_mass_storage_platform_data *pdata = pdev->dev.platform_data;
+ int i, nluns;
+
+ printk(KERN_INFO "fsg_probe pdev: %p, pdata: %p\n", pdev, pdata);
+ if (!pdata)
+ return -1;
+
+ nluns = pdata->nluns;
+ if (nluns > FSG_MAX_LUNS)
+ nluns = FSG_MAX_LUNS;
+ fsg_cfg.nluns = nluns;
+ for (i = 0; i < nluns; i++)
+ fsg_cfg.luns[i].removable = 1;
+
+ fsg_cfg.vendor_name = pdata->vendor;
+ fsg_cfg.product_name = pdata->product;
+ fsg_cfg.release = pdata->release;
+ fsg_cfg.can_stall = 0;
+ fsg_cfg.pdev = pdev;
+
+ return 0;
+}
+
+static struct platform_driver fsg_platform_driver = {
+ .driver = { .name = FUNCTION_NAME, },
+ .probe = fsg_probe,
+};
+
+int mass_storage_bind_config(struct usb_configuration *c)
+{
+ struct fsg_common *common = fsg_common_init(NULL, c->cdev, &fsg_cfg);
+ if (IS_ERR(common))
+ return -1;
+ return fsg_add(c->cdev, c, common);
+}
+
+static struct android_usb_function mass_storage_function = {
+ .name = FUNCTION_NAME,
+ .bind_config = mass_storage_bind_config,
+};
+
+static int __init init(void)
+{
+ int rc;
+ printk(KERN_INFO "f_mass_storage init\n");
+ rc = platform_driver_register(&fsg_platform_driver);
+ if (rc != 0)
+ return rc;
+ android_register_function(&mass_storage_function);
+ return 0;
+}module_init(init);
+
+#endif /* CONFIG_USB_ANDROID_MASS_STORAGE */
+
diff --git a/drivers/usb/gadget/gadget_gbhc/f_mtp.c b/drivers/usb/gadget/gadget_gbhc/f_mtp.c
new file mode 100644
index 0000000..d124e29
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/f_mtp.c
@@ -0,0 +1,1277 @@
+/*
+ * Gadget Function Driver for MTP
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb.h>
+#include <linux/usb_usual.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/android_composite.h>
+#include <linux/usb/f_mtp.h>
+
+#ifdef CONFIG_USB_ANDROID
+/* Constants for MTP_SET_INTERFACE_MODE */
+#define MTP_INTERFACE_MODE_MTP 0
+#define MTP_INTERFACE_MODE_PTP 1
+
+/* Sets the driver mode to either MTP or PTP */
+#define MTP_SET_INTERFACE_MODE _IOW('M', 2, int)
+#endif
+
+#define BULK_BUFFER_SIZE 16384
+#define INTR_BUFFER_SIZE 28
+
+/* String IDs */
+#define INTERFACE_STRING_INDEX 0
+
+/* values for mtp_dev.state */
+#define STATE_OFFLINE 0 /* initial state, disconnected */
+#define STATE_READY 1 /* ready for userspace calls */
+#define STATE_BUSY 2 /* processing userspace calls */
+#define STATE_CANCELED 3 /* transaction canceled by host */
+#define STATE_ERROR 4 /* error from completion routine */
+
+/* number of tx and rx requests to allocate */
+#define TX_REQ_MAX 4
+#define RX_REQ_MAX 2
+
+/* ID for Microsoft MTP OS String */
+#define MTP_OS_STRING_ID 0xEE
+
+/* MTP class reqeusts */
+#define MTP_REQ_CANCEL 0x64
+#define MTP_REQ_GET_EXT_EVENT_DATA 0x65
+#define MTP_REQ_RESET 0x66
+#define MTP_REQ_GET_DEVICE_STATUS 0x67
+
+/* constants for device status */
+#define MTP_RESPONSE_OK 0x2001
+#define MTP_RESPONSE_DEVICE_BUSY 0x2019
+
+static const char shortname[] = "mtp_usb";
+
+struct mtp_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ /* appear as MTP or PTP when enumerating */
+ int interface_mode;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+ struct usb_ep *ep_intr;
+
+ int state;
+
+ /* synchronize access to our device file */
+ atomic_t open_excl;
+ /* to enforce only one ioctl at a time */
+ atomic_t ioctl_excl;
+
+ struct list_head tx_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ struct usb_request *rx_req[RX_REQ_MAX];
+ struct usb_request *intr_req;
+ int rx_done;
+ /* true if interrupt endpoint is busy */
+ int intr_busy;
+
+ /* for processing MTP_SEND_FILE and MTP_RECEIVE_FILE
+ * ioctls on a work queue
+ */
+ struct workqueue_struct *wq;
+ struct work_struct send_file_work;
+ struct work_struct receive_file_work;
+ struct file *xfer_file;
+ loff_t xfer_file_offset;
+ int64_t xfer_file_length;
+ int xfer_result;
+};
+
+static struct usb_interface_descriptor mtp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = 0,
+};
+
+static struct usb_interface_descriptor ptp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_STILL_IMAGE,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 1,
+};
+
+static struct usb_endpoint_descriptor mtp_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mtp_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = __constant_cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_intr_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE),
+ .bInterval = 6,
+};
+
+static struct usb_descriptor_header *fs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *fs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_ptp_descs[] = {
+ (struct usb_descriptor_header *) &ptp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_string mtp_string_defs[] = {
+ /* Naming interface "MTP" so libmtp will recognize us */
+ [INTERFACE_STRING_INDEX].s = "MTP",
+ { }, /* end of list */
+};
+
+static struct usb_gadget_strings mtp_string_table = {
+ .language = 0x0409, /* en-US */
+ .strings = mtp_string_defs,
+};
+
+static struct usb_gadget_strings *mtp_strings[] = {
+ &mtp_string_table,
+ NULL,
+};
+
+/* Microsoft MTP OS String */
+static u8 mtp_os_string[] = {
+ 18, /* sizeof(mtp_os_string) */
+ USB_DT_STRING,
+ /* Signature field: "MSFT100" */
+ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+ /* vendor code */
+ 1,
+ /* padding */
+ 0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mtp_ext_config_desc_header {
+ __le32 dwLength;
+ __u16 bcdVersion;
+ __le16 wIndex;
+ __u8 bCount;
+ __u8 reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mtp_ext_config_desc_function {
+ __u8 bFirstInterfaceNumber;
+ __u8 bInterfaceCount;
+ __u8 compatibleID[8];
+ __u8 subCompatibleID[8];
+ __u8 reserved[6];
+};
+
+/* MTP Extended Configuration Descriptor */
+struct {
+ struct mtp_ext_config_desc_header header;
+ struct mtp_ext_config_desc_function function;
+} mtp_ext_config_desc = {
+ .header = {
+ .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)),
+ .bcdVersion = __constant_cpu_to_le16(0x0100),
+ .wIndex = __constant_cpu_to_le16(4),
+ .bCount = __constant_cpu_to_le16(1),
+ },
+ .function = {
+ .bFirstInterfaceNumber = 0,
+ .bInterfaceCount = 1,
+ .compatibleID = { 'M', 'T', 'P' },
+ },
+};
+
+struct mtp_device_status {
+ __le16 wLength;
+ __le16 wCode;
+};
+
+/* temporary variable used between mtp_open() and mtp_gadget_bind() */
+static struct mtp_dev *_mtp_dev;
+
+static inline struct mtp_dev *func_to_dev(struct usb_function *f)
+{
+ return container_of(f, struct mtp_dev, function);
+}
+
+static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void mtp_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int _lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void _unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+static void req_put(struct mtp_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request *req_get(struct mtp_dev *dev, struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ wake_up(&dev->read_wq);
+}
+
+static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ DBG(dev->cdev, "mtp_complete_intr status: %d actual: %d\n",
+ req->status, req->actual);
+ dev->intr_busy = 0;
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+}
+
+static int __init create_bulk_endpoints(struct mtp_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc,
+ struct usb_endpoint_descriptor *intr_desc)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, intr_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_intr = ep;
+
+ /* now allocate requests for our endpoints */
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_in, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_in;
+ req_put(dev, &dev->tx_idle, req);
+ }
+ for (i = 0; i < RX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_out, BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_out;
+ dev->rx_req[i] = req;
+ }
+ req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_intr;
+ dev->intr_req = req;
+
+ return 0;
+
+fail:
+ printk(KERN_ERR "mtp_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t mtp_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret = 0;
+
+ DBG(cdev, "mtp_read(%d)\n", count);
+
+ if (count > BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ /* we will block until we're online */
+ DBG(cdev, "mtp_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->state != STATE_OFFLINE);
+ if (ret < 0) {
+ r = ret;
+ goto done;
+ }
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req[0];
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ goto done;
+ } else {
+ DBG(cdev, "rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ r = ret;
+ usb_ep_dequeue(dev->ep_out, req);
+ goto done;
+ }
+ if (dev->state == STATE_BUSY) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ DBG(cdev, "rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ r = xfer;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+ } else
+ r = -EIO;
+
+done:
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_read returning %d\n", r);
+ return r;
+}
+
+static ssize_t mtp_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ int r = count, xfer;
+ int sendZLP = 0;
+ int ret;
+
+ DBG(cdev, "mtp_write(%d)\n", count);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ return -ENODEV;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ /* we need to send a zero length packet to signal the end of transfer
+ * if the transfer size is aligned to a packet boundary.
+ */
+ if ((count & (dev->ep_in->maxpacket - 1)) == 0) {
+ sendZLP = 1;
+ }
+
+ while (count > 0 || sendZLP) {
+ /* so we exit after sending ZLP */
+ if (count == 0)
+ sendZLP = 0;
+
+ if (dev->state != STATE_BUSY) {
+ DBG(cdev, "mtp_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY));
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > BULK_BUFFER_SIZE)
+ xfer = BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ if (xfer && copy_from_user(req->buf, buf, xfer)) {
+ r = -EFAULT;
+ break;
+ }
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "mtp_write: xfer error %d\n", ret);
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ req_put(dev, &dev->tx_idle, req);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_write returning %d\n", r);
+ return r;
+}
+
+/* read from a local file and write to USB */
+static void send_file_work(struct work_struct *data) {
+ struct mtp_dev *dev = container_of(data, struct mtp_dev, send_file_work);
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ struct file *filp;
+ loff_t offset;
+ int64_t count;
+ int xfer, ret;
+ int r = 0;
+ int sendZLP = 0;
+
+ /* read our parameters */
+ smp_rmb();
+ filp = dev->xfer_file;
+ offset = dev->xfer_file_offset;
+ count = dev->xfer_file_length;
+
+ DBG(cdev, "send_file_work(%lld %lld)\n", offset, count);
+
+ /* we need to send a zero length packet to signal the end of transfer
+ * if the transfer size is aligned to a packet boundary.
+ */
+ if ((dev->xfer_file_length & (dev->ep_in->maxpacket - 1)) == 0) {
+ sendZLP = 1;
+ }
+
+ while (count > 0 || sendZLP) {
+ /* so we exit after sending ZLP */
+ if (count == 0)
+ sendZLP = 0;
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ (req = req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY);
+ if (dev->state == STATE_CANCELED) {
+ r = -ECANCELED;
+ break;
+ }
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > BULK_BUFFER_SIZE)
+ xfer = BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ ret = vfs_read(filp, req->buf, xfer, &offset);
+ if (ret < 0) {
+ r = ret;
+ break;
+ }
+ xfer = ret;
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "send_file_work: xfer error %d\n", ret);
+ dev->state = STATE_ERROR;
+ r = -EIO;
+ break;
+ }
+
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ req_put(dev, &dev->tx_idle, req);
+
+ DBG(cdev, "send_file_work returning %d\n", r);
+ /* write the result */
+ dev->xfer_result = r;
+ smp_wmb();
+}
+
+/* read from USB and write to a local file */
+static void receive_file_work(struct work_struct *data)
+{
+ struct mtp_dev *dev = container_of(data, struct mtp_dev, receive_file_work);
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *read_req = NULL, *write_req = NULL;
+ struct file *filp;
+ loff_t offset;
+ int64_t count;
+ int ret, cur_buf = 0;
+ int r = 0;
+
+ /* read our parameters */
+ smp_rmb();
+ filp = dev->xfer_file;
+ offset = dev->xfer_file_offset;
+ count = dev->xfer_file_length;
+
+ DBG(cdev, "receive_file_work(%lld)\n", count);
+
+ while (count > 0 || write_req) {
+ if (count > 0) {
+ /* queue a request */
+ read_req = dev->rx_req[cur_buf];
+ cur_buf = (cur_buf + 1) % RX_REQ_MAX;
+
+ read_req->length = (count > BULK_BUFFER_SIZE
+ ? BULK_BUFFER_SIZE : count);
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ }
+
+ if (write_req) {
+ DBG(cdev, "rx %p %d\n", write_req, write_req->actual);
+ ret = vfs_write(filp, write_req->buf, write_req->actual,
+ &offset);
+ DBG(cdev, "vfs_write %d\n", ret);
+ if (ret != write_req->actual) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ write_req = NULL;
+ }
+
+ if (read_req) {
+ /* wait for our last read to complete */
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->rx_done || dev->state != STATE_BUSY);
+ if (dev->state == STATE_CANCELED) {
+ r = -ECANCELED;
+ if (!dev->rx_done)
+ usb_ep_dequeue(dev->ep_out, read_req);
+ break;
+ }
+ /* if xfer_file_length is 0xFFFFFFFF, then we read until
+ * we get a zero length packet
+ */
+ if (count != 0xFFFFFFFF)
+ count -= read_req->actual;
+ if (read_req->actual < read_req->length) {
+ /* short packet is used to signal EOF for sizes > 4 gig */
+ DBG(cdev, "got short packet\n");
+ count = 0;
+ }
+
+ write_req = read_req;
+ read_req = NULL;
+ }
+ }
+
+ DBG(cdev, "receive_file_work returning %d\n", r);
+ /* write the result */
+ dev->xfer_result = r;
+ smp_wmb();
+}
+
+static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event)
+{
+ struct usb_request *req;
+ int ret;
+ int length = event->length;
+
+ DBG(dev->cdev, "mtp_send_event(%d)\n", event->length);
+
+ if (length < 0 || length > INTR_BUFFER_SIZE)
+ return -EINVAL;
+ if (dev->state == STATE_OFFLINE)
+ return -ENODEV;
+ /* unfortunately an interrupt request might hang indefinitely if the host
+ * is not listening on the interrupt endpoint, so instead of waiting,
+ * we just fail if the endpoint is busy.
+ */
+ if (dev->intr_busy)
+ return -EBUSY;
+
+ req = dev->intr_req;
+ if (copy_from_user(req->buf, (void __user *)event->data, length))
+ return -EFAULT;
+ req->length = length;
+ dev->intr_busy = 1;
+ ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL);
+ if (ret)
+ dev->intr_busy = 0;
+
+ return ret;
+}
+
+static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct file *filp = NULL;
+ int ret = -EINVAL;
+
+ if (_lock(&dev->ioctl_excl))
+ return -EBUSY;
+
+ switch (code) {
+ case MTP_SEND_FILE:
+ case MTP_RECEIVE_FILE:
+ {
+ struct mtp_file_range mfr;
+ struct work_struct *work;
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancelation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ ret = -ECANCELED;
+ goto out;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ ret = -ENODEV;
+ goto out;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) {
+ ret = -EFAULT;
+ goto fail;
+ }
+ /* hold a reference to the file while we are working with it */
+ filp = fget(mfr.fd);
+ if (!filp) {
+ ret = -EBADF;
+ goto fail;
+ }
+
+ /* write the parameters */
+ dev->xfer_file = filp;
+ dev->xfer_file_offset = mfr.offset;
+ dev->xfer_file_length = mfr.length;
+ smp_wmb();
+
+ if (code == MTP_SEND_FILE)
+ work = &dev->send_file_work;
+ else
+ work = &dev->receive_file_work;
+
+ /* We do the file transfer on a work queue so it will run
+ * in kernel context, which is necessary for vfs_read and
+ * vfs_write to use our buffers in the kernel address space.
+ */
+ queue_work(dev->wq, work);
+ /* wait for operation to complete */
+ flush_workqueue(dev->wq);
+ fput(filp);
+
+ /* read the result */
+ smp_rmb();
+ ret = dev->xfer_result;
+ break;
+ }
+ case MTP_SET_INTERFACE_MODE:
+ if (value == MTP_INTERFACE_MODE_MTP ||
+ value == MTP_INTERFACE_MODE_PTP) {
+ dev->interface_mode = value;
+ if (value == MTP_INTERFACE_MODE_PTP) {
+ dev->function.descriptors = fs_ptp_descs;
+ dev->function.hs_descriptors = hs_ptp_descs;
+ } else {
+ dev->function.descriptors = fs_mtp_descs;
+ dev->function.hs_descriptors = hs_mtp_descs;
+ }
+ ret = 0;
+ }
+ break;
+ case MTP_SEND_EVENT:
+ {
+ struct mtp_event event;
+ /* return here so we don't change dev->state below,
+ * which would interfere with bulk transfer state.
+ */
+ if (copy_from_user(&event, (void __user *)value, sizeof(event)))
+ ret = -EFAULT;
+ else
+ ret = mtp_send_event(dev, &event);
+ goto out;
+ }
+ }
+
+fail:
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ ret = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+out:
+ _unlock(&dev->ioctl_excl);
+ DBG(dev->cdev, "ioctl returning %d\n", ret);
+ return ret;
+}
+
+static int mtp_open(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "mtp_open\n");
+ if (_lock(&_mtp_dev->open_excl))
+ return -EBUSY;
+
+ /* clear any error condition */
+ if (_mtp_dev->state != STATE_OFFLINE)
+ _mtp_dev->state = STATE_READY;
+
+ fp->private_data = _mtp_dev;
+ return 0;
+}
+
+static int mtp_release(struct inode *ip, struct file *fp)
+{
+ printk(KERN_INFO "mtp_release\n");
+
+ _unlock(&_mtp_dev->open_excl);
+ return 0;
+}
+
+/* file operations for /dev/mtp_usb */
+static const struct file_operations mtp_fops = {
+ .owner = THIS_MODULE,
+ .read = mtp_read,
+ .write = mtp_write,
+ .unlocked_ioctl = mtp_ioctl,
+ .open = mtp_open,
+ .release = mtp_release,
+};
+
+static struct miscdevice mtp_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = shortname,
+ .fops = &mtp_fops,
+};
+
+static int
+mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct mtp_dev *dev = func_to_dev(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "mtp_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ mtp_interface_desc.bInterfaceNumber = id;
+
+ /* allocate endpoints */
+ ret = create_bulk_endpoints(dev, &mtp_fullspeed_in_desc,
+ &mtp_fullspeed_out_desc, &mtp_intr_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ mtp_highspeed_in_desc.bEndpointAddress =
+ mtp_fullspeed_in_desc.bEndpointAddress;
+ mtp_highspeed_out_desc.bEndpointAddress =
+ mtp_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+mtp_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_request *req;
+ int i;
+
+ spin_lock_irq(&dev->lock);
+ while ((req = req_get(dev, &dev->tx_idle)))
+ mtp_request_free(req, dev->ep_in);
+ for (i = 0; i < RX_REQ_MAX; i++)
+ mtp_request_free(dev->rx_req[i], dev->ep_out);
+ mtp_request_free(dev->intr_req, dev->ep_intr);
+ dev->state = STATE_OFFLINE;
+ spin_unlock_irq(&dev->lock);
+
+ misc_deregister(&mtp_device);
+ kfree(_mtp_dev);
+ _mtp_dev = NULL;
+}
+
+static int mtp_function_setup(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+ unsigned long flags;
+
+ /* do nothing if we are disabled */
+ if (dev->function.disabled)
+ return value;
+
+ VDBG(cdev, "mtp_function_setup "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ /* Handle MTP OS string */
+ if (dev->interface_mode == MTP_INTERFACE_MODE_MTP
+ && ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+ && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == USB_DT_STRING
+ && (w_value & 0xFF) == MTP_OS_STRING_ID) {
+ value = (w_length < sizeof(mtp_os_string)
+ ? w_length : sizeof(mtp_os_string));
+ memcpy(cdev->req->buf, mtp_os_string, value);
+ /* return here since composite.c will send for us */
+ return value;
+ }
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
+ /* Handle MTP OS descriptor */
+ DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (dev->interface_mode == MTP_INTERFACE_MODE_MTP
+ && ctrl->bRequest == 1
+ && (ctrl->bRequestType & USB_DIR_IN)
+ && (w_index == 4 || w_index == 5)) {
+ value = (w_length < sizeof(mtp_ext_config_desc) ?
+ w_length : sizeof(mtp_ext_config_desc));
+ memcpy(cdev->req->buf, &mtp_ext_config_desc, value);
+ }
+ }
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
+ DBG(cdev, "class request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0
+ && w_value == 0) {
+ DBG(cdev, "MTP_REQ_CANCEL\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->state == STATE_BUSY) {
+ dev->state = STATE_CANCELED;
+ wake_up(&dev->read_wq);
+ wake_up(&dev->write_wq);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ /* We need to queue a request to read the remaining
+ * bytes, but we don't actually need to look at
+ * the contents.
+ */
+ value = w_length;
+ } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS
+ && w_index == 0 && w_value == 0) {
+ struct mtp_device_status *status = cdev->req->buf;
+ status->wLength =
+ __constant_cpu_to_le16(sizeof(*status));
+
+ DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n");
+ spin_lock_irqsave(&dev->lock, flags);
+ /* device status is "busy" until we report
+ * the cancelation to userspace
+ */
+ if (dev->state == STATE_CANCELED)
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY);
+ else
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_OK);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ value = sizeof(*status);
+ }
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ int rc;
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ ERROR(cdev, "%s setup response queue error\n", __func__);
+ }
+
+ if (value == -EOPNOTSUPP)
+ VDBG(cdev,
+ "unknown class-specific control req "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ return value;
+}
+
+static int mtp_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt);
+ ret = usb_ep_enable(dev->ep_in,
+ ep_choose(cdev->gadget,
+ &mtp_highspeed_in_desc,
+ &mtp_fullspeed_in_desc));
+ if (ret)
+ return ret;
+ ret = usb_ep_enable(dev->ep_out,
+ ep_choose(cdev->gadget,
+ &mtp_highspeed_out_desc,
+ &mtp_fullspeed_out_desc));
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ ret = usb_ep_enable(dev->ep_intr, &mtp_intr_desc);
+ if (ret) {
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ dev->state = STATE_READY;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void mtp_function_disable(struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_dev(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "mtp_function_disable\n");
+ dev->state = STATE_OFFLINE;
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_intr);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int mtp_bind_config(struct usb_configuration *c)
+{
+ struct mtp_dev *dev;
+ int ret = 0;
+
+ printk(KERN_INFO "mtp_bind_config\n");
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ /* allocate a string ID for our interface */
+ if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) {
+ ret = usb_string_id(c->cdev);
+ if (ret < 0)
+ return ret;
+ mtp_string_defs[INTERFACE_STRING_INDEX].id = ret;
+ mtp_interface_desc.iInterface = ret;
+ }
+
+ spin_lock_init(&dev->lock);
+ init_waitqueue_head(&dev->read_wq);
+ init_waitqueue_head(&dev->write_wq);
+ atomic_set(&dev->open_excl, 0);
+ atomic_set(&dev->ioctl_excl, 0);
+ INIT_LIST_HEAD(&dev->tx_idle);
+
+ dev->wq = create_singlethread_workqueue("f_mtp");
+ if (!dev->wq)
+ goto err1;
+ INIT_WORK(&dev->send_file_work, send_file_work);
+ INIT_WORK(&dev->receive_file_work, receive_file_work);
+
+ dev->cdev = c->cdev;
+ dev->function.name = "mtp";
+ dev->function.strings = mtp_strings,
+ dev->function.descriptors = fs_mtp_descs;
+ dev->function.hs_descriptors = hs_mtp_descs;
+ dev->function.bind = mtp_function_bind;
+ dev->function.unbind = mtp_function_unbind;
+ dev->function.setup = mtp_function_setup;
+ dev->function.set_alt = mtp_function_set_alt;
+ dev->function.disable = mtp_function_disable;
+
+ /* MTP mode by default */
+ dev->interface_mode = MTP_INTERFACE_MODE_MTP;
+
+ /* _mtp_dev must be set before calling usb_gadget_register_driver */
+ _mtp_dev = dev;
+
+ ret = misc_register(&mtp_device);
+ if (ret)
+ goto err1;
+
+ ret = usb_add_function(c, &dev->function);
+ if (ret)
+ goto err2;
+
+ return 0;
+
+err2:
+ misc_deregister(&mtp_device);
+err1:
+ if (dev->wq)
+ destroy_workqueue(dev->wq);
+ kfree(dev);
+ printk(KERN_ERR "mtp gadget driver failed to initialize\n");
+ return ret;
+}
+
+static struct android_usb_function mtp_function = {
+ .name = "mtp",
+ .bind_config = mtp_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_mtp init\n");
+ android_register_function(&mtp_function);
+ return 0;
+}
+module_init(init);
diff --git a/drivers/usb/gadget/gadget_gbhc/f_ncm.c b/drivers/usb/gadget/gadget_gbhc/f_ncm.c
new file mode 100644
index 0000000..86902a6
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/f_ncm.c
@@ -0,0 +1,1407 @@
+/*
+ * f_ncm.c -- USB CDC Network (NCM) link function driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Contact: Yauheni Kaliuta <yauheni.kaliuta@nokia.com>
+ *
+ * The driver borrows from f_ecm.c which is:
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/etherdevice.h>
+#include <linux/crc32.h>
+
+#include <linux/usb/cdc.h>
+
+#include "u_ether.h"
+
+/*
+ * This function is a "CDC Network Control Model" (CDC NCM) Ethernet link.
+ * NCM is intended to be used with high-speed network attachments.
+ *
+ * Note that NCM requires the use of "alternate settings" for its data
+ * interface. This means that the set_alt() method has real work to do,
+ * and also means that a get_alt() method is required.
+ */
+
+/* to trigger crc/non-crc ndp signature */
+
+#define NCM_NDP_HDR_CRC_MASK 0x01000000
+#define NCM_NDP_HDR_CRC 0x01000000
+#define NCM_NDP_HDR_NOCRC 0x00000000
+
+struct ncm_ep_descs {
+ struct usb_endpoint_descriptor *in;
+ struct usb_endpoint_descriptor *out;
+ struct usb_endpoint_descriptor *notify;
+};
+
+enum ncm_notify_state {
+ NCM_NOTIFY_NONE, /* don't notify */
+ NCM_NOTIFY_CONNECT, /* issue CONNECT next */
+ NCM_NOTIFY_SPEED, /* issue SPEED_CHANGE next */
+};
+
+struct f_ncm {
+ struct gether port;
+ u8 ctrl_id, data_id;
+
+ char ethaddr[14];
+
+ struct ncm_ep_descs fs;
+ struct ncm_ep_descs hs;
+
+ struct usb_ep *notify;
+ struct usb_endpoint_descriptor *notify_desc;
+ struct usb_request *notify_req;
+ u8 notify_state;
+ bool is_open;
+
+ struct ndp_parser_opts *parser_opts;
+ bool is_crc;
+
+ /*
+ * for notification, it is accessed from both
+ * callback and ethernet open/close
+ */
+ spinlock_t lock;
+};
+
+static inline struct f_ncm *func_to_ncm(struct usb_function *f)
+{
+ return container_of(f, struct f_ncm, port.func);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static inline unsigned ncm_bitrate(struct usb_gadget *g)
+{
+ if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+ return 13 * 512 * 8 * 1000 * 8;
+ else
+ return 19 * 64 * 1 * 1000 * 8;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * We cannot group frames so use just the minimal size which ok to put
+ * one max-size ethernet frame.
+ * If the host can group frames, allow it to do that, 16K is selected,
+ * because it's used by default by the current linux host driver
+ */
+#define NTB_DEFAULT_IN_SIZE USB_CDC_NCM_NTB_MIN_IN_SIZE
+#define NTB_OUT_SIZE 16384
+
+/*
+ * skbs of size less than that will not be aligned
+ * to NCM's dwNtbInMaxSize to save bus bandwidth
+ */
+
+#define MAX_TX_NONFIXED (512 * 3)
+
+#define FORMATS_SUPPORTED (USB_CDC_NCM_NTB16_SUPPORTED | \
+ USB_CDC_NCM_NTB32_SUPPORTED)
+
+static struct usb_cdc_ncm_ntb_parameters ntb_parameters = {
+ .wLength = sizeof ntb_parameters,
+ .bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED),
+ .dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE),
+ .wNdpInDivisor = cpu_to_le16(4),
+ .wNdpInPayloadRemainder = cpu_to_le16(0),
+ .wNdpInAlignment = cpu_to_le16(4),
+
+ .dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE),
+ .wNdpOutDivisor = cpu_to_le16(4),
+ .wNdpOutPayloadRemainder = cpu_to_le16(0),
+ .wNdpOutAlignment = cpu_to_le16(4),
+};
+
+/*
+ * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one
+ * packet, to simplify cancellation; and a big transfer interval, to
+ * waste less bandwidth.
+ */
+
+#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */
+#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */
+
+static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = {
+ .bLength = sizeof ncm_iad_desc,
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+ /* .bFirstInterface = DYNAMIC, */
+ .bInterfaceCount = 2, /* control + data */
+ .bFunctionClass = USB_CLASS_COMM,
+ .bFunctionSubClass = USB_CDC_SUBCLASS_NCM,
+ .bFunctionProtocol = USB_CDC_PROTO_NONE,
+ /* .iFunction = DYNAMIC */
+};
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor ncm_control_intf __initdata = {
+ .bLength = sizeof ncm_control_intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_COMM,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM,
+ .bInterfaceProtocol = USB_CDC_PROTO_NONE,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc ncm_header_desc __initdata = {
+ .bLength = sizeof ncm_header_desc,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_HEADER_TYPE,
+
+ .bcdCDC = cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_union_desc ncm_union_desc __initdata = {
+ .bLength = sizeof(ncm_union_desc),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_UNION_TYPE,
+ /* .bMasterInterface0 = DYNAMIC */
+ /* .bSlaveInterface0 = DYNAMIC */
+};
+
+static struct usb_cdc_ether_desc ecm_desc __initdata = {
+ .bLength = sizeof ecm_desc,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_ETHERNET_TYPE,
+
+ /* this descriptor actually adds value, surprise! */
+ /* .iMACAddress = DYNAMIC */
+ .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */
+ .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN),
+ .wNumberMCFilters = cpu_to_le16(0),
+ .bNumberPowerFilters = 0,
+};
+
+#define NCAPS (USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE)
+
+static struct usb_cdc_ncm_desc ncm_desc __initdata = {
+ .bLength = sizeof ncm_desc,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_NCM_TYPE,
+
+ .bcdNcmVersion = cpu_to_le16(0x0100),
+ /* can process SetEthernetPacketFilter */
+ .bmNetworkCapabilities = NCAPS,
+};
+
+/* the default data interface has no endpoints ... */
+
+static struct usb_interface_descriptor ncm_data_nop_intf __initdata = {
+ .bLength = sizeof ncm_data_nop_intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = USB_CDC_NCM_PROTO_NTB,
+ /* .iInterface = DYNAMIC */
+};
+
+/* ... but the "real" data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor ncm_data_intf __initdata = {
+ .bLength = sizeof ncm_data_intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+
+ .bInterfaceNumber = 1,
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = USB_CDC_NCM_PROTO_NTB,
+ /* .iInterface = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT),
+ .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *ncm_fs_function[] __initdata = {
+ (struct usb_descriptor_header *) &ncm_iad_desc,
+ /* CDC NCM control descriptors */
+ (struct usb_descriptor_header *) &ncm_control_intf,
+ (struct usb_descriptor_header *) &ncm_header_desc,
+ (struct usb_descriptor_header *) &ncm_union_desc,
+ (struct usb_descriptor_header *) &ecm_desc,
+ (struct usb_descriptor_header *) &ncm_desc,
+ (struct usb_descriptor_header *) &fs_ncm_notify_desc,
+ /* data interface, altsettings 0 and 1 */
+ (struct usb_descriptor_header *) &ncm_data_nop_intf,
+ (struct usb_descriptor_header *) &ncm_data_intf,
+ (struct usb_descriptor_header *) &fs_ncm_in_desc,
+ (struct usb_descriptor_header *) &fs_ncm_out_desc,
+ NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT),
+ .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *ncm_hs_function[] __initdata = {
+ (struct usb_descriptor_header *) &ncm_iad_desc,
+ /* CDC NCM control descriptors */
+ (struct usb_descriptor_header *) &ncm_control_intf,
+ (struct usb_descriptor_header *) &ncm_header_desc,
+ (struct usb_descriptor_header *) &ncm_union_desc,
+ (struct usb_descriptor_header *) &ecm_desc,
+ (struct usb_descriptor_header *) &ncm_desc,
+ (struct usb_descriptor_header *) &hs_ncm_notify_desc,
+ /* data interface, altsettings 0 and 1 */
+ (struct usb_descriptor_header *) &ncm_data_nop_intf,
+ (struct usb_descriptor_header *) &ncm_data_intf,
+ (struct usb_descriptor_header *) &hs_ncm_in_desc,
+ (struct usb_descriptor_header *) &hs_ncm_out_desc,
+ NULL,
+};
+
+/* string descriptors: */
+
+#define STRING_CTRL_IDX 0
+#define STRING_MAC_IDX 1
+#define STRING_DATA_IDX 2
+#define STRING_IAD_IDX 3
+
+static struct usb_string ncm_string_defs[] = {
+ [STRING_CTRL_IDX].s = "CDC Network Control Model (NCM)",
+ [STRING_MAC_IDX].s = NULL /* DYNAMIC */,
+ [STRING_DATA_IDX].s = "CDC Network Data",
+ [STRING_IAD_IDX].s = "CDC NCM",
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings ncm_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = ncm_string_defs,
+};
+
+static struct usb_gadget_strings *ncm_strings[] = {
+ &ncm_string_table,
+ NULL,
+};
+
+/*
+ * Here are options for NCM Datagram Pointer table (NDP) parser.
+ * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3),
+ * in NDP16 offsets and sizes fields are 1 16bit word wide,
+ * in NDP32 -- 2 16bit words wide. Also signatures are different.
+ * To make the parser code the same, put the differences in the structure,
+ * and switch pointers to the structures when the format is changed.
+ */
+
+struct ndp_parser_opts {
+ u32 nth_sign;
+ u32 ndp_sign;
+ unsigned nth_size;
+ unsigned ndp_size;
+ unsigned ndplen_align;
+ /* sizes in u16 units */
+ unsigned dgram_item_len; /* index or length */
+ unsigned block_length;
+ unsigned fp_index;
+ unsigned reserved1;
+ unsigned reserved2;
+ unsigned next_fp_index;
+};
+
+#define INIT_NDP16_OPTS { \
+ .nth_sign = USB_CDC_NCM_NTH16_SIGN, \
+ .ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \
+ .nth_size = sizeof(struct usb_cdc_ncm_nth16), \
+ .ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \
+ .ndplen_align = 4, \
+ .dgram_item_len = 1, \
+ .block_length = 1, \
+ .fp_index = 1, \
+ .reserved1 = 0, \
+ .reserved2 = 0, \
+ .next_fp_index = 1, \
+ }
+
+
+#define INIT_NDP32_OPTS { \
+ .nth_sign = USB_CDC_NCM_NTH32_SIGN, \
+ .ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \
+ .nth_size = sizeof(struct usb_cdc_ncm_nth32), \
+ .ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \
+ .ndplen_align = 8, \
+ .dgram_item_len = 2, \
+ .block_length = 2, \
+ .fp_index = 2, \
+ .reserved1 = 1, \
+ .reserved2 = 2, \
+ .next_fp_index = 2, \
+ }
+
+static struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS;
+static struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS;
+
+static inline void put_ncm(__le16 **p, unsigned size, unsigned val)
+{
+ switch (size) {
+ case 1:
+ put_unaligned_le16((u16)val, *p);
+ break;
+ case 2:
+ put_unaligned_le32((u32)val, *p);
+
+ break;
+ default:
+ BUG();
+ }
+
+ *p += size;
+}
+
+static inline unsigned get_ncm(__le16 **p, unsigned size)
+{
+ unsigned tmp;
+
+ switch (size) {
+ case 1:
+ tmp = get_unaligned_le16(*p);
+ break;
+ case 2:
+ tmp = get_unaligned_le32(*p);
+ break;
+ default:
+ BUG();
+ }
+
+ *p += size;
+ return tmp;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static inline void ncm_reset_values(struct f_ncm *ncm)
+{
+ ncm->parser_opts = &ndp16_opts;
+ ncm->is_crc = false;
+ ncm->port.cdc_filter = DEFAULT_FILTER;
+
+ /* doesn't make sense for ncm, fixed size used */
+ ncm->port.header_len = 0;
+
+ ncm->port.fixed_out_len = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+ ncm->port.fixed_in_len = NTB_DEFAULT_IN_SIZE;
+}
+
+/*
+ * Context: ncm->lock held
+ */
+static void ncm_do_notify(struct f_ncm *ncm)
+{
+ struct usb_request *req = ncm->notify_req;
+ struct usb_cdc_notification *event;
+ struct usb_composite_dev *cdev = ncm->port.func.config->cdev;
+ __le32 *data;
+ int status;
+
+ /* notification already in flight? */
+ if (!req)
+ return;
+
+ event = req->buf;
+ switch (ncm->notify_state) {
+ case NCM_NOTIFY_NONE:
+ return;
+
+ case NCM_NOTIFY_CONNECT:
+ event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION;
+ if (ncm->is_open)
+ event->wValue = cpu_to_le16(1);
+ else
+ event->wValue = cpu_to_le16(0);
+ event->wLength = 0;
+ req->length = sizeof *event;
+
+ DBG(cdev, "notify connect %s\n",
+ ncm->is_open ? "true" : "false");
+ ncm->notify_state = NCM_NOTIFY_NONE;
+ break;
+
+ case NCM_NOTIFY_SPEED:
+ event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE;
+ event->wValue = cpu_to_le16(0);
+ event->wLength = cpu_to_le16(8);
+ req->length = NCM_STATUS_BYTECOUNT;
+
+ /* SPEED_CHANGE data is up/down speeds in bits/sec */
+ data = req->buf + sizeof *event;
+ data[0] = cpu_to_le32(ncm_bitrate(cdev->gadget));
+ data[1] = data[0];
+
+ DBG(cdev, "notify speed %d\n", ncm_bitrate(cdev->gadget));
+ ncm->notify_state = NCM_NOTIFY_CONNECT;
+ break;
+ }
+ event->bmRequestType = 0xA1;
+ event->wIndex = cpu_to_le16(ncm->ctrl_id);
+
+ ncm->notify_req = NULL;
+ /*
+ * In double buffering if there is a space in FIFO,
+ * completion callback can be called right after the call,
+ * so unlocking
+ */
+ spin_unlock(&ncm->lock);
+ status = usb_ep_queue(ncm->notify, req, GFP_ATOMIC);
+ spin_lock(&ncm->lock);
+ if (status < 0) {
+ ncm->notify_req = req;
+ DBG(cdev, "notify --> %d\n", status);
+ }
+}
+
+/*
+ * Context: ncm->lock held
+ */
+static void ncm_notify(struct f_ncm *ncm)
+{
+ /*
+ * NOTE on most versions of Linux, host side cdc-ethernet
+ * won't listen for notifications until its netdevice opens.
+ * The first notification then sits in the FIFO for a long
+ * time, and the second one is queued.
+ *
+ * If ncm_notify() is called before the second (CONNECT)
+ * notification is sent, then it will reset to send the SPEED
+ * notificaion again (and again, and again), but it's not a problem
+ */
+ ncm->notify_state = NCM_NOTIFY_SPEED;
+ ncm_do_notify(ncm);
+}
+
+static void ncm_notify_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_ncm *ncm = req->context;
+ struct usb_composite_dev *cdev = ncm->port.func.config->cdev;
+ struct usb_cdc_notification *event = req->buf;
+
+ spin_lock(&ncm->lock);
+ switch (req->status) {
+ case 0:
+ VDBG(cdev, "Notification %02x sent\n",
+ event->bNotificationType);
+ break;
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ ncm->notify_state = NCM_NOTIFY_NONE;
+ break;
+ default:
+ DBG(cdev, "event %02x --> %d\n",
+ event->bNotificationType, req->status);
+ break;
+ }
+ ncm->notify_req = req;
+ ncm_do_notify(ncm);
+ spin_unlock(&ncm->lock);
+}
+
+static void ncm_ep0out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ /* now for SET_NTB_INPUT_SIZE only */
+ unsigned in_size;
+ struct usb_function *f = req->context;
+ struct f_ncm *ncm = func_to_ncm(f);
+ struct usb_composite_dev *cdev = ep->driver_data;
+
+ req->context = NULL;
+ if (req->status || req->actual != req->length) {
+ DBG(cdev, "Bad control-OUT transfer\n");
+ goto invalid;
+ }
+
+ in_size = get_unaligned_le32(req->buf);
+ if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE ||
+ in_size > le32_to_cpu(ntb_parameters.dwNtbInMaxSize)) {
+ DBG(cdev, "Got wrong INPUT SIZE (%d) from host\n", in_size);
+ goto invalid;
+ }
+
+ ncm->port.fixed_in_len = in_size;
+ VDBG(cdev, "Set NTB INPUT SIZE %d\n", in_size);
+ return;
+
+invalid:
+ usb_ep_set_halt(ep);
+ return;
+}
+
+static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_ncm *ncm = func_to_ncm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ /*
+ * composite driver infrastructure handles everything except
+ * CDC class messages; interface activation uses set_alt().
+ */
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SET_ETHERNET_PACKET_FILTER:
+ /*
+ * see 6.2.30: no data, wIndex = interface,
+ * wValue = packet filter bitmap
+ */
+ if (w_length != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ DBG(cdev, "packet filter %02x\n", w_value);
+ /*
+ * REVISIT locking of cdc_filter. This assumes the UDC
+ * driver won't have a concurrent packet TX irq running on
+ * another CPU; or that if it does, this write is atomic...
+ */
+ ncm->port.cdc_filter = w_value;
+ value = 0;
+ break;
+ /*
+ * and optionally:
+ * case USB_CDC_SEND_ENCAPSULATED_COMMAND:
+ * case USB_CDC_GET_ENCAPSULATED_RESPONSE:
+ * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS:
+ * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER:
+ * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER:
+ * case USB_CDC_GET_ETHERNET_STATISTIC:
+ */
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_NTB_PARAMETERS:
+
+ if (w_length == 0 || w_value != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ value = w_length > sizeof ntb_parameters ?
+ sizeof ntb_parameters : w_length;
+ memcpy(req->buf, &ntb_parameters, value);
+ VDBG(cdev, "Host asked NTB parameters\n");
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_NTB_INPUT_SIZE:
+
+ if (w_length < 4 || w_value != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ put_unaligned_le32(ncm->port.fixed_in_len, req->buf);
+ value = 4;
+ VDBG(cdev, "Host asked INPUT SIZE, sending %d\n",
+ ncm->port.fixed_in_len);
+ break;
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SET_NTB_INPUT_SIZE:
+ {
+ if (w_length != 4 || w_value != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ req->complete = ncm_ep0out_complete;
+ req->length = w_length;
+ req->context = f;
+
+ value = req->length;
+ break;
+ }
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_NTB_FORMAT:
+ {
+ uint16_t format;
+
+ if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ format = (ncm->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001;
+ put_unaligned_le16(format, req->buf);
+ value = 2;
+ VDBG(cdev, "Host asked NTB FORMAT, sending %d\n", format);
+ break;
+ }
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SET_NTB_FORMAT:
+ {
+ if (w_length != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ switch (w_value) {
+ case 0x0000:
+ ncm->parser_opts = &ndp16_opts;
+ DBG(cdev, "NCM16 selected\n");
+ break;
+ case 0x0001:
+ ncm->parser_opts = &ndp32_opts;
+ DBG(cdev, "NCM32 selected\n");
+ break;
+ default:
+ goto invalid;
+ }
+ value = 0;
+ break;
+ }
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_CRC_MODE:
+ {
+ uint16_t is_crc;
+
+ if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ is_crc = ncm->is_crc ? 0x0001 : 0x0000;
+ put_unaligned_le16(is_crc, req->buf);
+ value = 2;
+ VDBG(cdev, "Host asked CRC MODE, sending %d\n", is_crc);
+ break;
+ }
+
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SET_CRC_MODE:
+ {
+ int ndp_hdr_crc = 0;
+
+ if (w_length != 0 || w_index != ncm->ctrl_id)
+ goto invalid;
+ switch (w_value) {
+ case 0x0000:
+ ncm->is_crc = false;
+ ndp_hdr_crc = NCM_NDP_HDR_NOCRC;
+ DBG(cdev, "non-CRC mode selected\n");
+ break;
+ case 0x0001:
+ ncm->is_crc = true;
+ ndp_hdr_crc = NCM_NDP_HDR_CRC;
+ DBG(cdev, "CRC mode selected\n");
+ break;
+ default:
+ goto invalid;
+ }
+ ncm->parser_opts->ndp_sign &= ~NCM_NDP_HDR_CRC_MASK;
+ ncm->parser_opts->ndp_sign |= ndp_hdr_crc;
+ value = 0;
+ break;
+ }
+
+ /* and disabled in ncm descriptor: */
+ /* case USB_CDC_GET_NET_ADDRESS: */
+ /* case USB_CDC_SET_NET_ADDRESS: */
+ /* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */
+ /* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */
+
+ default:
+invalid:
+ DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ DBG(cdev, "ncm req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ req->zero = 0;
+ req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0)
+ ERROR(cdev, "ncm req %02x.%02x response err %d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ value);
+ }
+
+ /* device either stalls (value < 0) or reports success */
+ return value;
+}
+
+
+static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct f_ncm *ncm = func_to_ncm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ /* Control interface has only altsetting 0 */
+ if (intf == ncm->ctrl_id) {
+ if (alt != 0)
+ goto fail;
+
+ if (ncm->notify->driver_data) {
+ DBG(cdev, "reset ncm control %d\n", intf);
+ usb_ep_disable(ncm->notify);
+ } else {
+ DBG(cdev, "init ncm ctrl %d\n", intf);
+ ncm->notify_desc = ep_choose(cdev->gadget,
+ ncm->hs.notify,
+ ncm->fs.notify);
+ }
+ usb_ep_enable(ncm->notify, ncm->notify_desc);
+ ncm->notify->driver_data = ncm;
+
+ /* Data interface has two altsettings, 0 and 1 */
+ } else if (intf == ncm->data_id) {
+ if (alt > 1)
+ goto fail;
+
+ if (ncm->port.in_ep->driver_data) {
+ DBG(cdev, "reset ncm\n");
+ gether_disconnect(&ncm->port);
+ ncm_reset_values(ncm);
+ }
+
+ /*
+ * CDC Network only sends data in non-default altsettings.
+ * Changing altsettings resets filters, statistics, etc.
+ */
+ if (alt == 1) {
+ struct net_device *net;
+
+ if (!ncm->port.in) {
+ DBG(cdev, "init ncm\n");
+ ncm->port.in = ep_choose(cdev->gadget,
+ ncm->hs.in,
+ ncm->fs.in);
+ ncm->port.out = ep_choose(cdev->gadget,
+ ncm->hs.out,
+ ncm->fs.out);
+ }
+
+ /* TODO */
+ /* Enable zlps by default for NCM conformance;
+ * override for musb_hdrc (avoids txdma ovhead)
+ */
+ ncm->port.is_zlp_ok = !(
+ gadget_is_musbhdrc(cdev->gadget)
+ );
+ ncm->port.cdc_filter = DEFAULT_FILTER;
+ DBG(cdev, "activate ncm\n");
+ net = gether_connect(&ncm->port);
+ if (IS_ERR(net))
+ return PTR_ERR(net);
+ }
+
+ spin_lock(&ncm->lock);
+ ncm_notify(ncm);
+ spin_unlock(&ncm->lock);
+ } else
+ goto fail;
+
+ return 0;
+fail:
+ return -EINVAL;
+}
+
+/*
+ * Because the data interface supports multiple altsettings,
+ * this NCM function *MUST* implement a get_alt() method.
+ */
+static int ncm_get_alt(struct usb_function *f, unsigned intf)
+{
+ struct f_ncm *ncm = func_to_ncm(f);
+
+ if (intf == ncm->ctrl_id)
+ return 0;
+ return ncm->port.in_ep->driver_data ? 1 : 0;
+}
+
+static struct sk_buff *ncm_wrap_ntb(struct gether *port,
+ struct sk_buff *skb)
+{
+ struct f_ncm *ncm = func_to_ncm(&port->func);
+ struct sk_buff *skb2;
+ int ncb_len = 0;
+ __le16 *tmp;
+ int div = ntb_parameters.wNdpInDivisor;
+ int rem = ntb_parameters.wNdpInPayloadRemainder;
+ int pad;
+ int ndp_align = ntb_parameters.wNdpInAlignment;
+ int ndp_pad;
+ unsigned max_size = ncm->port.fixed_in_len;
+ struct ndp_parser_opts *opts = ncm->parser_opts;
+ unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0;
+
+ ncb_len += opts->nth_size;
+ ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len;
+ ncb_len += ndp_pad;
+ ncb_len += opts->ndp_size;
+ ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */
+ ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */
+ pad = ALIGN(ncb_len, div) + rem - ncb_len;
+ ncb_len += pad;
+
+ if (ncb_len + skb->len + crc_len > max_size) {
+ dev_kfree_skb_any(skb);
+ return NULL;
+ }
+
+ skb2 = skb_copy_expand(skb, ncb_len,
+ max_size - skb->len - ncb_len - crc_len,
+ GFP_ATOMIC);
+ dev_kfree_skb_any(skb);
+ if (!skb2)
+ return NULL;
+
+ skb = skb2;
+
+ tmp = (void *) skb_push(skb, ncb_len);
+ memset(tmp, 0, ncb_len);
+
+ put_unaligned_le32(opts->nth_sign, tmp); /* dwSignature */
+ tmp += 2;
+ /* wHeaderLength */
+ put_unaligned_le16(opts->nth_size, tmp++);
+ tmp++; /* skip wSequence */
+ put_ncm(&tmp, opts->block_length, skb->len); /* (d)wBlockLength */
+ /* (d)wFpIndex */
+ /* the first pointer is right after the NTH + align */
+ put_ncm(&tmp, opts->fp_index, opts->nth_size + ndp_pad);
+
+ tmp = (void *)tmp + ndp_pad;
+
+ /* NDP */
+ put_unaligned_le32(opts->ndp_sign, tmp); /* dwSignature */
+ tmp += 2;
+ /* wLength */
+ put_unaligned_le16(ncb_len - opts->nth_size - pad, tmp++);
+
+ tmp += opts->reserved1;
+ tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+ tmp += opts->reserved2;
+
+ if (ncm->is_crc) {
+ uint32_t crc;
+
+ crc = ~crc32_le(~0,
+ skb->data + ncb_len,
+ skb->len - ncb_len);
+ put_unaligned_le32(crc, skb->data + skb->len);
+ skb_put(skb, crc_len);
+ }
+
+ /* (d)wDatagramIndex[0] */
+ put_ncm(&tmp, opts->dgram_item_len, ncb_len);
+ /* (d)wDatagramLength[0] */
+ put_ncm(&tmp, opts->dgram_item_len, skb->len - ncb_len);
+ /* (d)wDatagramIndex[1] and (d)wDatagramLength[1] already zeroed */
+
+ if (skb->len > MAX_TX_NONFIXED)
+ memset(skb_put(skb, max_size - skb->len),
+ 0, max_size - skb->len);
+
+ return skb;
+}
+
+static int ncm_unwrap_ntb(struct gether *port,
+ struct sk_buff *skb,
+ struct sk_buff_head *list)
+{
+ struct f_ncm *ncm = func_to_ncm(&port->func);
+ __le16 *tmp = (void *) skb->data;
+ unsigned index, index2;
+ unsigned dg_len, dg_len2;
+ unsigned ndp_len;
+ struct sk_buff *skb2;
+ int ret = -EINVAL;
+ unsigned max_size = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize);
+ struct ndp_parser_opts *opts = ncm->parser_opts;
+ unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0;
+ int dgram_counter;
+
+ /* dwSignature */
+ if (get_unaligned_le32(tmp) != opts->nth_sign) {
+ INFO(port->func.config->cdev, "Wrong NTH SIGN, skblen %d\n",
+ skb->len);
+ print_hex_dump(KERN_INFO, "HEAD:", DUMP_PREFIX_ADDRESS, 32, 1,
+ skb->data, 32, false);
+
+ goto err;
+ }
+ tmp += 2;
+ /* wHeaderLength */
+ if (get_unaligned_le16(tmp++) != opts->nth_size) {
+ INFO(port->func.config->cdev, "Wrong NTB headersize\n");
+ goto err;
+ }
+ tmp++; /* skip wSequence */
+
+ /* (d)wBlockLength */
+ if (get_ncm(&tmp, opts->block_length) > max_size) {
+ INFO(port->func.config->cdev, "OUT size exceeded\n");
+ goto err;
+ }
+
+ index = get_ncm(&tmp, opts->fp_index);
+ /* NCM 3.2 */
+ if (((index % 4) != 0) && (index < opts->nth_size)) {
+ INFO(port->func.config->cdev, "Bad index: %x\n",
+ index);
+ goto err;
+ }
+
+ /* walk through NDP */
+ tmp = ((void *)skb->data) + index;
+ if (get_unaligned_le32(tmp) != opts->ndp_sign) {
+ INFO(port->func.config->cdev, "Wrong NDP SIGN\n");
+ goto err;
+ }
+ tmp += 2;
+
+ ndp_len = get_unaligned_le16(tmp++);
+ /*
+ * NCM 3.3.1
+ * entry is 2 items
+ * item size is 16/32 bits, opts->dgram_item_len * 2 bytes
+ * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry
+ */
+ if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2))
+ || (ndp_len % opts->ndplen_align != 0)) {
+ INFO(port->func.config->cdev, "Bad NDP length: %x\n", ndp_len);
+ goto err;
+ }
+ tmp += opts->reserved1;
+ tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */
+ tmp += opts->reserved2;
+
+ ndp_len -= opts->ndp_size;
+ index2 = get_ncm(&tmp, opts->dgram_item_len);
+ dg_len2 = get_ncm(&tmp, opts->dgram_item_len);
+ dgram_counter = 0;
+
+ do {
+ index = index2;
+ dg_len = dg_len2;
+ if (dg_len < 14 + crc_len) { /* ethernet header + crc */
+ INFO(port->func.config->cdev, "Bad dgram length: %x\n",
+ dg_len);
+ goto err;
+ }
+ if (ncm->is_crc) {
+ uint32_t crc, crc2;
+
+ crc = get_unaligned_le32(skb->data +
+ index + dg_len - crc_len);
+ crc2 = ~crc32_le(~0,
+ skb->data + index,
+ dg_len - crc_len);
+ if (crc != crc2) {
+ INFO(port->func.config->cdev, "Bad CRC\n");
+ goto err;
+ }
+ }
+
+ index2 = get_ncm(&tmp, opts->dgram_item_len);
+ dg_len2 = get_ncm(&tmp, opts->dgram_item_len);
+
+ if (index2 == 0 || dg_len2 == 0) {
+ skb2 = skb;
+ } else {
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 == NULL)
+ goto err;
+ }
+
+ if (!skb_pull(skb2, index)) {
+ ret = -EOVERFLOW;
+ goto err;
+ }
+
+ skb_trim(skb2, dg_len - crc_len);
+ skb_queue_tail(list, skb2);
+
+ ndp_len -= 2 * (opts->dgram_item_len * 2);
+
+ dgram_counter++;
+
+ if (index2 == 0 || dg_len2 == 0)
+ break;
+ } while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */
+
+ VDBG(port->func.config->cdev,
+ "Parsed NTB with %d frames\n", dgram_counter);
+ return 0;
+err:
+ skb_queue_purge(list);
+ dev_kfree_skb_any(skb);
+ return ret;
+}
+
+static void ncm_disable(struct usb_function *f)
+{
+ struct f_ncm *ncm = func_to_ncm(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ DBG(cdev, "ncm deactivated\n");
+
+ if (ncm->port.in_ep->driver_data)
+ gether_disconnect(&ncm->port);
+
+ if (ncm->notify->driver_data) {
+ usb_ep_disable(ncm->notify);
+ ncm->notify->driver_data = NULL;
+ ncm->notify_desc = NULL;
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Callbacks let us notify the host about connect/disconnect when the
+ * net device is opened or closed.
+ *
+ * For testing, note that link states on this side include both opened
+ * and closed variants of:
+ *
+ * - disconnected/unconfigured
+ * - configured but inactive (data alt 0)
+ * - configured and active (data alt 1)
+ *
+ * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and
+ * SET_INTERFACE (altsetting). Remember also that "configured" doesn't
+ * imply the host is actually polling the notification endpoint, and
+ * likewise that "active" doesn't imply it's actually using the data
+ * endpoints for traffic.
+ */
+
+static void ncm_open(struct gether *geth)
+{
+ struct f_ncm *ncm = func_to_ncm(&geth->func);
+
+ DBG(ncm->port.func.config->cdev, "%s\n", __func__);
+
+ spin_lock(&ncm->lock);
+ ncm->is_open = true;
+ ncm_notify(ncm);
+ spin_unlock(&ncm->lock);
+}
+
+static void ncm_close(struct gether *geth)
+{
+ struct f_ncm *ncm = func_to_ncm(&geth->func);
+
+ DBG(ncm->port.func.config->cdev, "%s\n", __func__);
+
+ spin_lock(&ncm->lock);
+ ncm->is_open = false;
+ ncm_notify(ncm);
+ spin_unlock(&ncm->lock);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ethernet function driver setup/binding */
+
+static int __init
+ncm_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_ncm *ncm = func_to_ncm(f);
+ int status;
+ struct usb_ep *ep;
+
+ /* allocate instance-specific interface IDs */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ ncm->ctrl_id = status;
+ ncm_iad_desc.bFirstInterface = status;
+
+ ncm_control_intf.bInterfaceNumber = status;
+ ncm_union_desc.bMasterInterface0 = status;
+
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ ncm->data_id = status;
+
+ ncm_data_nop_intf.bInterfaceNumber = status;
+ ncm_data_intf.bInterfaceNumber = status;
+ ncm_union_desc.bSlaveInterface0 = status;
+
+ status = -ENODEV;
+
+ /* allocate instance-specific endpoints */
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc);
+ if (!ep)
+ goto fail;
+ ncm->port.in_ep = ep;
+ ep->driver_data = cdev; /* claim */
+
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc);
+ if (!ep)
+ goto fail;
+ ncm->port.out_ep = ep;
+ ep->driver_data = cdev; /* claim */
+
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc);
+ if (!ep)
+ goto fail;
+ ncm->notify = ep;
+ ep->driver_data = cdev; /* claim */
+
+ status = -ENOMEM;
+
+ /* allocate notification request and buffer */
+ ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!ncm->notify_req)
+ goto fail;
+ ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL);
+ if (!ncm->notify_req->buf)
+ goto fail;
+ ncm->notify_req->context = ncm;
+ ncm->notify_req->complete = ncm_notify_complete;
+
+ /* copy descriptors, and track endpoint copies */
+ f->descriptors = usb_copy_descriptors(ncm_fs_function);
+ if (!f->descriptors)
+ goto fail;
+
+ ncm->fs.in = usb_find_endpoint(ncm_fs_function,
+ f->descriptors, &fs_ncm_in_desc);
+ ncm->fs.out = usb_find_endpoint(ncm_fs_function,
+ f->descriptors, &fs_ncm_out_desc);
+ ncm->fs.notify = usb_find_endpoint(ncm_fs_function,
+ f->descriptors, &fs_ncm_notify_desc);
+
+ /*
+ * support all relevant hardware speeds... we expect that when
+ * hardware is dual speed, all bulk-capable endpoints work at
+ * both speeds
+ */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ hs_ncm_in_desc.bEndpointAddress =
+ fs_ncm_in_desc.bEndpointAddress;
+ hs_ncm_out_desc.bEndpointAddress =
+ fs_ncm_out_desc.bEndpointAddress;
+ hs_ncm_notify_desc.bEndpointAddress =
+ fs_ncm_notify_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->hs_descriptors = usb_copy_descriptors(ncm_hs_function);
+ if (!f->hs_descriptors)
+ goto fail;
+
+ ncm->hs.in = usb_find_endpoint(ncm_hs_function,
+ f->hs_descriptors, &hs_ncm_in_desc);
+ ncm->hs.out = usb_find_endpoint(ncm_hs_function,
+ f->hs_descriptors, &hs_ncm_out_desc);
+ ncm->hs.notify = usb_find_endpoint(ncm_hs_function,
+ f->hs_descriptors, &hs_ncm_notify_desc);
+ }
+
+ /*
+ * NOTE: all that is done without knowing or caring about
+ * the network link ... which is unavailable to this code
+ * until we're activated via set_alt().
+ */
+
+ ncm->port.open = ncm_open;
+ ncm->port.close = ncm_close;
+
+ DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ ncm->port.in_ep->name, ncm->port.out_ep->name,
+ ncm->notify->name);
+ return 0;
+
+fail:
+ if (f->descriptors)
+ usb_free_descriptors(f->descriptors);
+
+ if (ncm->notify_req) {
+ kfree(ncm->notify_req->buf);
+ usb_ep_free_request(ncm->notify, ncm->notify_req);
+ }
+
+ /* we might as well release our claims on endpoints */
+ if (ncm->notify)
+ ncm->notify->driver_data = NULL;
+ if (ncm->port.out)
+ ncm->port.out_ep->driver_data = NULL;
+ if (ncm->port.in)
+ ncm->port.in_ep->driver_data = NULL;
+
+ ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+ return status;
+}
+
+static void
+ncm_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_ncm *ncm = func_to_ncm(f);
+
+ DBG(c->cdev, "ncm unbind\n");
+
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+
+ kfree(ncm->notify_req->buf);
+ usb_ep_free_request(ncm->notify, ncm->notify_req);
+
+ ncm_string_defs[1].s = NULL;
+ kfree(ncm);
+}
+
+/**
+ * ncm_bind_config - add CDC Network link to a configuration
+ * @c: the configuration to support the network link
+ * @ethaddr: a buffer in which the ethernet address of the host side
+ * side of the link was recorded
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gether_setup(). Caller is also responsible
+ * for calling @gether_cleanup() before module unload.
+ */
+int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
+{
+ struct f_ncm *ncm;
+ int status;
+
+ if (!can_support_ecm(c->cdev->gadget) || !ethaddr)
+ return -EINVAL;
+
+ /* maybe allocate device-global string IDs */
+ if (ncm_string_defs[0].id == 0) {
+
+ /* control interface label */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ ncm_string_defs[STRING_CTRL_IDX].id = status;
+ ncm_control_intf.iInterface = status;
+
+ /* data interface label */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ ncm_string_defs[STRING_DATA_IDX].id = status;
+ ncm_data_nop_intf.iInterface = status;
+ ncm_data_intf.iInterface = status;
+
+ /* MAC address */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ ncm_string_defs[STRING_MAC_IDX].id = status;
+ ecm_desc.iMACAddress = status;
+
+ /* IAD */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ ncm_string_defs[STRING_IAD_IDX].id = status;
+ ncm_iad_desc.iFunction = status;
+ }
+
+ /* allocate and initialize one new instance */
+ ncm = kzalloc(sizeof *ncm, GFP_KERNEL);
+ if (!ncm)
+ return -ENOMEM;
+
+ /* export host's Ethernet address in CDC format */
+ snprintf(ncm->ethaddr, sizeof ncm->ethaddr,
+ "%02X%02X%02X%02X%02X%02X",
+ ethaddr[0], ethaddr[1], ethaddr[2],
+ ethaddr[3], ethaddr[4], ethaddr[5]);
+ ncm_string_defs[1].s = ncm->ethaddr;
+
+ spin_lock_init(&ncm->lock);
+ ncm_reset_values(ncm);
+ ncm->port.is_fixed = true;
+
+ ncm->port.func.name = "cdc_network";
+ ncm->port.func.strings = ncm_strings;
+ /* descriptors are per-instance copies */
+ ncm->port.func.bind = ncm_bind;
+ ncm->port.func.unbind = ncm_unbind;
+ ncm->port.func.set_alt = ncm_set_alt;
+ ncm->port.func.get_alt = ncm_get_alt;
+ ncm->port.func.setup = ncm_setup;
+ ncm->port.func.disable = ncm_disable;
+
+ ncm->port.wrap = ncm_wrap_ntb;
+ ncm->port.unwrap = ncm_unwrap_ntb;
+
+ status = usb_add_function(c, &ncm->port.func);
+ if (status) {
+ ncm_string_defs[1].s = NULL;
+ kfree(ncm);
+ }
+ return status;
+}
diff --git a/drivers/usb/gadget/gadget_gbhc/f_rndis.c b/drivers/usb/gadget/gadget_gbhc/f_rndis.c
new file mode 100644
index 0000000..64faf65
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/f_rndis.c
@@ -0,0 +1,935 @@
+/*
+ * f_rndis.c -- RNDIS link function driver
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ * Copyright (C) 2009 Samsung Electronics
+ * Author: Michal Nazarewicz (m.nazarewicz@samsung.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/etherdevice.h>
+#include <linux/usb/android_composite.h>
+
+#include <asm/atomic.h>
+
+#include "u_ether.h"
+#include "rndis.h"
+
+
+/*
+ * This function is an RNDIS Ethernet port -- a Microsoft protocol that's
+ * been promoted instead of the standard CDC Ethernet. The published RNDIS
+ * spec is ambiguous, incomplete, and needlessly complex. Variants such as
+ * ActiveSync have even worse status in terms of specification.
+ *
+ * In short: it's a protocol controlled by (and for) Microsoft, not for an
+ * Open ecosystem or markets. Linux supports it *only* because Microsoft
+ * doesn't support the CDC Ethernet standard.
+ *
+ * The RNDIS data transfer model is complex, with multiple Ethernet packets
+ * per USB message, and out of band data. The control model is built around
+ * what's essentially an "RNDIS RPC" protocol. It's all wrapped in a CDC ACM
+ * (modem, not Ethernet) veneer, with those ACM descriptors being entirely
+ * useless (they're ignored). RNDIS expects to be the only function in its
+ * configuration, so it's no real help if you need composite devices; and
+ * it expects to be the first configuration too.
+ *
+ * There is a single technical advantage of RNDIS over CDC Ethernet, if you
+ * discount the fluff that its RPC can be made to deliver: it doesn't need
+ * a NOP altsetting for the data interface. That lets it work on some of the
+ * "so smart it's stupid" hardware which takes over configuration changes
+ * from the software, and adds restrictions like "no altsettings".
+ *
+ * Unfortunately MSFT's RNDIS drivers are buggy. They hang or oops, and
+ * have all sorts of contrary-to-specification oddities that can prevent
+ * them from working sanely. Since bugfixes (or accurate specs, letting
+ * Linux work around those bugs) are unlikely to ever come from MSFT, you
+ * may want to avoid using RNDIS on purely operational grounds.
+ *
+ * Omissions from the RNDIS 1.0 specification include:
+ *
+ * - Power management ... references data that's scattered around lots
+ * of other documentation, which is incorrect/incomplete there too.
+ *
+ * - There are various undocumented protocol requirements, like the need
+ * to send garbage in some control-OUT messages.
+ *
+ * - MS-Windows drivers sometimes emit undocumented requests.
+ */
+
+struct rndis_ep_descs {
+ struct usb_endpoint_descriptor *in;
+ struct usb_endpoint_descriptor *out;
+ struct usb_endpoint_descriptor *notify;
+};
+
+struct f_rndis {
+ struct gether port;
+ u8 ctrl_id, data_id;
+ u8 ethaddr[ETH_ALEN];
+ int config;
+
+ struct rndis_ep_descs fs;
+ struct rndis_ep_descs hs;
+
+ struct usb_ep *notify;
+ struct usb_endpoint_descriptor *notify_desc;
+ struct usb_request *notify_req;
+ atomic_t notify_count;
+};
+
+static inline struct f_rndis *func_to_rndis(struct usb_function *f)
+{
+ return container_of(f, struct f_rndis, port.func);
+}
+
+/* peak (theoretical) bulk transfer rate in bits-per-second */
+static unsigned int bitrate(struct usb_gadget *g)
+{
+ if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+ return 13 * 512 * 8 * 1000 * 8;
+ else
+ return 19 * 64 * 1 * 1000 * 8;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ */
+
+#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */
+#define STATUS_BYTECOUNT 8 /* 8 bytes data */
+
+
+/* interface descriptor: */
+
+static struct usb_interface_descriptor rndis_control_intf = {
+ .bLength = sizeof rndis_control_intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+
+ /* .bInterfaceNumber = DYNAMIC */
+ /* status endpoint is optional; this could be patched later */
+ .bNumEndpoints = 1,
+#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS
+ /* "Wireless" RNDIS; auto-detected by Windows */
+ .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER,
+ .bInterfaceSubClass = 0x01,
+ .bInterfaceProtocol = 0x03,
+#else
+ .bInterfaceClass = USB_CLASS_COMM,
+ .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM,
+ .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR,
+#endif
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_cdc_header_desc header_desc = {
+ .bLength = sizeof header_desc,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_HEADER_TYPE,
+
+ .bcdCDC = cpu_to_le16(0x0110),
+};
+
+static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = {
+ .bLength = sizeof call_mgmt_descriptor,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE,
+
+ .bmCapabilities = 0x00,
+ .bDataInterface = 0x01,
+};
+
+static struct usb_cdc_acm_descriptor rndis_acm_descriptor = {
+ .bLength = sizeof rndis_acm_descriptor,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_ACM_TYPE,
+
+ .bmCapabilities = 0x00,
+};
+
+static struct usb_cdc_union_desc rndis_union_desc = {
+ .bLength = sizeof(rndis_union_desc),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubType = USB_CDC_UNION_TYPE,
+ /* .bMasterInterface0 = DYNAMIC */
+ /* .bSlaveInterface0 = DYNAMIC */
+};
+
+/* the data interface has two bulk endpoints */
+
+static struct usb_interface_descriptor rndis_data_intf = {
+ .bLength = sizeof rndis_data_intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+
+ /* .bInterfaceNumber = DYNAMIC */
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_CDC_DATA,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+ /* .iInterface = DYNAMIC */
+};
+
+
+static struct usb_interface_assoc_descriptor
+rndis_iad_descriptor = {
+ .bLength = sizeof rndis_iad_descriptor,
+ .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
+
+ .bFirstInterface = 0, /* XXX, hardcoded */
+ .bInterfaceCount = 2, // control + data
+#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS
+ /* "Wireless" RNDIS; auto-detected by Windows */
+ .bFunctionClass = USB_CLASS_WIRELESS_CONTROLLER,
+ .bFunctionSubClass = 0x01,
+ .bFunctionProtocol = 0x03,
+#else
+ .bFunctionClass = USB_CLASS_COMM,
+ .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET,
+ .bFunctionProtocol = USB_CDC_ACM_PROTO_VENDOR,
+#endif
+ /* .iFunction = DYNAMIC */
+};
+
+/* full speed support: */
+
+static struct usb_endpoint_descriptor fs_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT),
+ .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC,
+};
+
+static struct usb_endpoint_descriptor fs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor fs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_descriptor_header *eth_fs_function[] = {
+ (struct usb_descriptor_header *) &rndis_iad_descriptor,
+ /* control interface matches ACM, not Ethernet */
+ (struct usb_descriptor_header *) &rndis_control_intf,
+ (struct usb_descriptor_header *) &header_desc,
+ (struct usb_descriptor_header *) &call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &rndis_acm_descriptor,
+ (struct usb_descriptor_header *) &rndis_union_desc,
+ (struct usb_descriptor_header *) &fs_notify_desc,
+ /* data interface has no altsetting */
+ (struct usb_descriptor_header *) &rndis_data_intf,
+ (struct usb_descriptor_header *) &fs_in_desc,
+ (struct usb_descriptor_header *) &fs_out_desc,
+ NULL,
+};
+
+/* high speed support: */
+
+static struct usb_endpoint_descriptor hs_notify_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT),
+ .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4,
+};
+static struct usb_endpoint_descriptor hs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor hs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *eth_hs_function[] = {
+ (struct usb_descriptor_header *) &rndis_iad_descriptor,
+ /* control interface matches ACM, not Ethernet */
+ (struct usb_descriptor_header *) &rndis_control_intf,
+ (struct usb_descriptor_header *) &header_desc,
+ (struct usb_descriptor_header *) &call_mgmt_descriptor,
+ (struct usb_descriptor_header *) &rndis_acm_descriptor,
+ (struct usb_descriptor_header *) &rndis_union_desc,
+ (struct usb_descriptor_header *) &hs_notify_desc,
+ /* data interface has no altsetting */
+ (struct usb_descriptor_header *) &rndis_data_intf,
+ (struct usb_descriptor_header *) &hs_in_desc,
+ (struct usb_descriptor_header *) &hs_out_desc,
+ NULL,
+};
+
+/* string descriptors: */
+
+static struct usb_string rndis_string_defs[] = {
+ [0].s = "RNDIS Communications Control",
+ [1].s = "RNDIS Ethernet Data",
+ [2].s = "RNDIS",
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings rndis_string_table = {
+ .language = 0x0409, /* en-us */
+ .strings = rndis_string_defs,
+};
+
+static struct usb_gadget_strings *rndis_strings[] = {
+ &rndis_string_table,
+ NULL,
+};
+
+#ifdef CONFIG_USB_ANDROID_RNDIS
+static struct usb_ether_platform_data *rndis_pdata;
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+static struct sk_buff *rndis_add_header(struct gether *port,
+ struct sk_buff *skb)
+{
+ struct sk_buff *skb2;
+
+ skb2 = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type));
+ if (skb2)
+ rndis_add_hdr(skb2);
+
+ dev_kfree_skb_any(skb);
+ return skb2;
+}
+
+static void rndis_response_available(void *_rndis)
+{
+ struct f_rndis *rndis = _rndis;
+ struct usb_request *req = rndis->notify_req;
+ struct usb_composite_dev *cdev = rndis->port.func.config->cdev;
+ __le32 *data = req->buf;
+ int status;
+
+ if (atomic_inc_return(&rndis->notify_count) != 1)
+ return;
+
+ /* Send RNDIS RESPONSE_AVAILABLE notification; a
+ * USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too
+ *
+ * This is the only notification defined by RNDIS.
+ */
+ data[0] = cpu_to_le32(1);
+ data[1] = cpu_to_le32(0);
+
+ status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
+ if (status) {
+ atomic_dec(&rndis->notify_count);
+ DBG(cdev, "notify/0 --> %d\n", status);
+ }
+}
+
+static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_rndis *rndis = req->context;
+ struct usb_composite_dev *cdev = rndis->port.func.config->cdev;
+ int status = req->status;
+
+ /* after TX:
+ * - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control)
+ * - RNDIS_RESPONSE_AVAILABLE (status/irq)
+ */
+ switch (status) {
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ /* connection gone */
+ atomic_set(&rndis->notify_count, 0);
+ break;
+ default:
+ DBG(cdev, "RNDIS %s response error %d, %d/%d\n",
+ ep->name, status,
+ req->actual, req->length);
+ /* FALLTHROUGH */
+ case 0:
+ if (ep != rndis->notify)
+ break;
+
+ /* handle multiple pending RNDIS_RESPONSE_AVAILABLE
+ * notifications by resending until we're done
+ */
+ if (atomic_dec_and_test(&rndis->notify_count))
+ break;
+ status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC);
+ if (status) {
+ atomic_dec(&rndis->notify_count);
+ DBG(cdev, "notify/1 --> %d\n", status);
+ }
+ break;
+ }
+}
+
+static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_rndis *rndis = req->context;
+ struct usb_composite_dev *cdev = rndis->port.func.config->cdev;
+ int status;
+
+ /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */
+// spin_lock(&dev->lock);
+ status = rndis_msg_parser(rndis->config, (u8 *) req->buf);
+ if (status < 0)
+ ERROR(cdev, "RNDIS command error %d, %d/%d\n",
+ status, req->actual, req->length);
+// spin_unlock(&dev->lock);
+}
+
+static int
+rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct f_rndis *rndis = func_to_rndis(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ /* composite driver infrastructure handles everything except
+ * CDC class messages; interface activation uses set_alt().
+ */
+ switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
+
+ /* RNDIS uses the CDC command encapsulation mechanism to implement
+ * an RPC scheme, with much getting/setting of attributes by OID.
+ */
+ case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_SEND_ENCAPSULATED_COMMAND:
+ if (w_value || w_index != rndis->ctrl_id)
+ goto invalid;
+ /* read the request; process it later */
+ value = w_length;
+ req->complete = rndis_command_complete;
+ req->context = rndis;
+ /* later, rndis_response_available() sends a notification */
+ break;
+
+ case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8)
+ | USB_CDC_GET_ENCAPSULATED_RESPONSE:
+ if (w_value || w_index != rndis->ctrl_id)
+ goto invalid;
+ else {
+ u8 *buf;
+ u32 n;
+
+ /* return the result */
+ buf = rndis_get_next_response(rndis->config, &n);
+ if (buf) {
+ memcpy(req->buf, buf, n);
+ req->complete = rndis_response_complete;
+ rndis_free_response(rndis->config, buf);
+ value = n;
+ }
+ /* else stalls ... spec says to avoid that */
+ }
+ break;
+
+ default:
+invalid:
+ VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ req->zero = (value < w_length);
+ req->length = value;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0)
+ ERROR(cdev, "rndis response on err %d\n", value);
+ }
+
+ /* device either stalls (value < 0) or reports success */
+ return value;
+}
+
+
+static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct f_rndis *rndis = func_to_rndis(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ /* we know alt == 0 */
+
+ if (intf == rndis->ctrl_id) {
+ if (rndis->notify->driver_data) {
+ VDBG(cdev, "reset rndis control %d\n", intf);
+ usb_ep_disable(rndis->notify);
+ } else {
+ VDBG(cdev, "init rndis ctrl %d\n", intf);
+ }
+ rndis->notify_desc = ep_choose(cdev->gadget,
+ rndis->hs.notify,
+ rndis->fs.notify);
+ usb_ep_enable(rndis->notify, rndis->notify_desc);
+ rndis->notify->driver_data = rndis;
+
+ } else if (intf == rndis->data_id) {
+ struct net_device *net;
+
+ if (rndis->port.in_ep->driver_data) {
+ DBG(cdev, "reset rndis\n");
+ gether_disconnect(&rndis->port);
+ }
+
+ if (!rndis->port.in) {
+ DBG(cdev, "init rndis\n");
+ }
+ rndis->port.in = ep_choose(cdev->gadget,
+ rndis->hs.in, rndis->fs.in);
+ rndis->port.out = ep_choose(cdev->gadget,
+ rndis->hs.out, rndis->fs.out);
+
+ /* Avoid ZLPs; they can be troublesome. */
+ rndis->port.is_zlp_ok = false;
+
+ /* RNDIS should be in the "RNDIS uninitialized" state,
+ * either never activated or after rndis_uninit().
+ *
+ * We don't want data to flow here until a nonzero packet
+ * filter is set, at which point it enters "RNDIS data
+ * initialized" state ... but we do want the endpoints
+ * to be activated. It's a strange little state.
+ *
+ * REVISIT the RNDIS gadget code has done this wrong for a
+ * very long time. We need another call to the link layer
+ * code -- gether_updown(...bool) maybe -- to do it right.
+ */
+ rndis->port.cdc_filter = 0;
+
+ DBG(cdev, "RNDIS RX/TX early activation ... \n");
+ net = gether_connect(&rndis->port);
+ if (IS_ERR(net))
+ return PTR_ERR(net);
+
+ rndis_set_param_dev(rndis->config, net,
+ &rndis->port.cdc_filter);
+ } else
+ goto fail;
+
+ return 0;
+fail:
+ return -EINVAL;
+}
+
+static void rndis_disable(struct usb_function *f)
+{
+ struct f_rndis *rndis = func_to_rndis(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+
+ if (!rndis->notify->driver_data)
+ return;
+
+ DBG(cdev, "rndis deactivated\n");
+
+ rndis_uninit(rndis->config);
+ gether_disconnect(&rndis->port);
+
+ usb_ep_disable(rndis->notify);
+ rndis->notify->driver_data = NULL;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * This isn't quite the same mechanism as CDC Ethernet, since the
+ * notification scheme passes less data, but the same set of link
+ * states must be tested. A key difference is that altsettings are
+ * not used to tell whether the link should send packets or not.
+ */
+
+static void rndis_open(struct gether *geth)
+{
+ struct f_rndis *rndis = func_to_rndis(&geth->func);
+ struct usb_composite_dev *cdev = geth->func.config->cdev;
+
+ DBG(cdev, "%s\n", __func__);
+
+ rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3,
+ bitrate(cdev->gadget) / 100);
+ rndis_signal_connect(rndis->config);
+}
+
+static void rndis_close(struct gether *geth)
+{
+ struct f_rndis *rndis = func_to_rndis(&geth->func);
+
+ DBG(geth->func.config->cdev, "%s\n", __func__);
+
+ rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0);
+ rndis_signal_disconnect(rndis->config);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ethernet function driver setup/binding */
+
+static int
+rndis_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_rndis *rndis = func_to_rndis(f);
+ int status;
+ struct usb_ep *ep;
+
+ /* allocate instance-specific interface IDs */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ rndis->ctrl_id = status;
+ rndis_iad_descriptor.bFirstInterface = status;
+
+ rndis_control_intf.bInterfaceNumber = status;
+ rndis_union_desc.bMasterInterface0 = status;
+
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ rndis->data_id = status;
+
+ rndis_data_intf.bInterfaceNumber = status;
+ rndis_union_desc.bSlaveInterface0 = status;
+
+ status = -ENODEV;
+
+ /* allocate instance-specific endpoints */
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc);
+ if (!ep)
+ goto fail;
+ rndis->port.in_ep = ep;
+ ep->driver_data = cdev; /* claim */
+
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc);
+ if (!ep)
+ goto fail;
+ rndis->port.out_ep = ep;
+ ep->driver_data = cdev; /* claim */
+
+ /* NOTE: a status/notification endpoint is, strictly speaking,
+ * optional. We don't treat it that way though! It's simpler,
+ * and some newer profiles don't treat it as optional.
+ */
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc);
+ if (!ep)
+ goto fail;
+ rndis->notify = ep;
+ ep->driver_data = cdev; /* claim */
+
+ status = -ENOMEM;
+
+ /* allocate notification request and buffer */
+ rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!rndis->notify_req)
+ goto fail;
+ rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL);
+ if (!rndis->notify_req->buf)
+ goto fail;
+ rndis->notify_req->length = STATUS_BYTECOUNT;
+ rndis->notify_req->context = rndis;
+ rndis->notify_req->complete = rndis_response_complete;
+
+ /* copy descriptors, and track endpoint copies */
+ f->descriptors = usb_copy_descriptors(eth_fs_function);
+ if (!f->descriptors)
+ goto fail;
+
+ rndis->fs.in = usb_find_endpoint(eth_fs_function,
+ f->descriptors, &fs_in_desc);
+ rndis->fs.out = usb_find_endpoint(eth_fs_function,
+ f->descriptors, &fs_out_desc);
+ rndis->fs.notify = usb_find_endpoint(eth_fs_function,
+ f->descriptors, &fs_notify_desc);
+
+ /* support all relevant hardware speeds... we expect that when
+ * hardware is dual speed, all bulk-capable endpoints work at
+ * both speeds
+ */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ hs_in_desc.bEndpointAddress =
+ fs_in_desc.bEndpointAddress;
+ hs_out_desc.bEndpointAddress =
+ fs_out_desc.bEndpointAddress;
+ hs_notify_desc.bEndpointAddress =
+ fs_notify_desc.bEndpointAddress;
+
+ /* copy descriptors, and track endpoint copies */
+ f->hs_descriptors = usb_copy_descriptors(eth_hs_function);
+
+ if (!f->hs_descriptors)
+ goto fail;
+
+ rndis->hs.in = usb_find_endpoint(eth_hs_function,
+ f->hs_descriptors, &hs_in_desc);
+ rndis->hs.out = usb_find_endpoint(eth_hs_function,
+ f->hs_descriptors, &hs_out_desc);
+ rndis->hs.notify = usb_find_endpoint(eth_hs_function,
+ f->hs_descriptors, &hs_notify_desc);
+ }
+
+ rndis->port.open = rndis_open;
+ rndis->port.close = rndis_close;
+
+ status = rndis_register(rndis_response_available, rndis);
+ if (status < 0)
+ goto fail;
+ rndis->config = status;
+
+ rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0);
+ rndis_set_host_mac(rndis->config, rndis->ethaddr);
+
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ if (rndis_pdata) {
+ if (rndis_set_param_vendor(rndis->config, rndis_pdata->vendorID,
+ rndis_pdata->vendorDescr))
+ goto fail;
+ }
+#endif
+
+ /* NOTE: all that is done without knowing or caring about
+ * the network link ... which is unavailable to this code
+ * until we're activated via set_alt().
+ */
+
+ DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ rndis->port.in_ep->name, rndis->port.out_ep->name,
+ rndis->notify->name);
+ return 0;
+
+fail:
+ if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors)
+ usb_free_descriptors(f->hs_descriptors);
+ if (f->descriptors)
+ usb_free_descriptors(f->descriptors);
+
+ if (rndis->notify_req) {
+ kfree(rndis->notify_req->buf);
+ usb_ep_free_request(rndis->notify, rndis->notify_req);
+ }
+
+ /* we might as well release our claims on endpoints */
+ if (rndis->notify)
+ rndis->notify->driver_data = NULL;
+ if (rndis->port.out)
+ rndis->port.out_ep->driver_data = NULL;
+ if (rndis->port.in)
+ rndis->port.in_ep->driver_data = NULL;
+
+ ERROR(cdev, "%s: can't bind, err %d\n", f->name, status);
+
+ return status;
+}
+
+static void
+rndis_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_rndis *rndis = func_to_rndis(f);
+
+ rndis_deregister(rndis->config);
+ rndis_exit();
+
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ usb_free_descriptors(f->hs_descriptors);
+ usb_free_descriptors(f->descriptors);
+
+ kfree(rndis->notify_req->buf);
+ usb_ep_free_request(rndis->notify, rndis->notify_req);
+
+ kfree(rndis);
+}
+
+/* Some controllers can't support RNDIS ... */
+static inline bool can_support_rndis(struct usb_configuration *c)
+{
+ /* everything else is *presumably* fine */
+ return true;
+}
+
+/**
+ * rndis_bind_config - add RNDIS network link to a configuration
+ * @c: the configuration to support the network link
+ * @ethaddr: a buffer in which the ethernet address of the host side
+ * side of the link was recorded
+ * Context: single threaded during gadget setup
+ *
+ * Returns zero on success, else negative errno.
+ *
+ * Caller must have called @gether_setup(). Caller is also responsible
+ * for calling @gether_cleanup() before module unload.
+ */
+int
+rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
+{
+ struct f_rndis *rndis;
+ int status;
+
+ if (!can_support_rndis(c) || !ethaddr)
+ return -EINVAL;
+
+ /* maybe allocate device-global string IDs */
+ if (rndis_string_defs[0].id == 0) {
+
+ /* ... and setup RNDIS itself */
+ status = rndis_init();
+ if (status < 0)
+ return status;
+
+ /* control interface label */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ rndis_string_defs[0].id = status;
+ rndis_control_intf.iInterface = status;
+
+ /* data interface label */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ rndis_string_defs[1].id = status;
+ rndis_data_intf.iInterface = status;
+
+ /* IAD iFunction label */
+ status = usb_string_id(c->cdev);
+ if (status < 0)
+ return status;
+ rndis_string_defs[2].id = status;
+ rndis_iad_descriptor.iFunction = status;
+ }
+
+ /* allocate and initialize one new instance */
+ status = -ENOMEM;
+ rndis = kzalloc(sizeof *rndis, GFP_KERNEL);
+ if (!rndis)
+ goto fail;
+
+ memcpy(rndis->ethaddr, ethaddr, ETH_ALEN);
+
+ /* RNDIS activates when the host changes this filter */
+ rndis->port.cdc_filter = 0;
+
+ /* RNDIS has special (and complex) framing */
+ rndis->port.header_len = sizeof(struct rndis_packet_msg_type);
+ rndis->port.wrap = rndis_add_header;
+ rndis->port.unwrap = rndis_rm_hdr;
+
+ rndis->port.func.name = "rndis";
+ rndis->port.func.strings = rndis_strings;
+ /* descriptors are per-instance copies */
+ rndis->port.func.bind = rndis_bind;
+ rndis->port.func.unbind = rndis_unbind;
+ rndis->port.func.set_alt = rndis_set_alt;
+ rndis->port.func.setup = rndis_setup;
+ rndis->port.func.disable = rndis_disable;
+
+#ifdef CONFIG_USB_ANDROID_RNDIS
+ /* start disabled */
+ rndis->port.func.disabled = 1;
+#endif
+
+ status = usb_add_function(c, &rndis->port.func);
+ if (status) {
+ kfree(rndis);
+fail:
+ rndis_exit();
+ }
+ return status;
+}
+
+#ifdef CONFIG_USB_ANDROID_RNDIS
+#include "rndis.c"
+
+static int rndis_probe(struct platform_device *pdev)
+{
+ rndis_pdata = pdev->dev.platform_data;
+ return 0;
+}
+
+static struct platform_driver rndis_platform_driver = {
+ .driver = { .name = "rndis", },
+ .probe = rndis_probe,
+};
+
+int rndis_function_bind_config(struct usb_configuration *c)
+{
+ int ret;
+
+ if (!rndis_pdata) {
+ printk(KERN_ERR "rndis_pdata null in rndis_function_bind_config\n");
+ return -1;
+ }
+
+ printk(KERN_INFO
+ "rndis_function_bind_config MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
+ rndis_pdata->ethaddr[0], rndis_pdata->ethaddr[1],
+ rndis_pdata->ethaddr[2], rndis_pdata->ethaddr[3],
+ rndis_pdata->ethaddr[4], rndis_pdata->ethaddr[5]);
+
+ ret = gether_setup(c->cdev->gadget, rndis_pdata->ethaddr);
+ if (ret == 0)
+ ret = rndis_bind_config(c, rndis_pdata->ethaddr);
+ return ret;
+}
+
+static struct android_usb_function rndis_function = {
+ .name = "rndis",
+ .bind_config = rndis_function_bind_config,
+};
+
+static int __init init(void)
+{
+ printk(KERN_INFO "f_rndis init\n");
+ platform_driver_register(&rndis_platform_driver);
+ android_register_function(&rndis_function);
+ return 0;
+}
+module_init(init);
+
+#endif /* CONFIG_USB_ANDROID_RNDIS */
diff --git a/drivers/usb/gadget/gadget_gbhc/file_storage.c b/drivers/usb/gadget/gadget_gbhc/file_storage.c
new file mode 100644
index 0000000..0360f56
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/file_storage.c
@@ -0,0 +1,3626 @@
+/*
+ * file_storage.c -- File-backed USB Storage Gadget, for USB development
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") as published by the Free Software
+ * Foundation, either version 2 of that License or (at your option) any
+ * later version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * The File-backed Storage Gadget acts as a USB Mass Storage device,
+ * appearing to the host as a disk drive or as a CD-ROM drive. In addition
+ * to providing an example of a genuinely useful gadget driver for a USB
+ * device, it also illustrates a technique of double-buffering for increased
+ * throughput. Last but not least, it gives an easy way to probe the
+ * behavior of the Mass Storage drivers in a USB host.
+ *
+ * Backing storage is provided by a regular file or a block device, specified
+ * by the "file" module parameter. Access can be limited to read-only by
+ * setting the optional "ro" module parameter. (For CD-ROM emulation,
+ * access is always read-only.) The gadget will indicate that it has
+ * removable media if the optional "removable" module parameter is set.
+ *
+ * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI),
+ * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected
+ * by the optional "transport" module parameter. It also supports the
+ * following protocols: RBC (0x01), ATAPI or SFF-8020i (0x02), QIC-157 (0c03),
+ * UFI (0x04), SFF-8070i (0x05), and transparent SCSI (0x06), selected by
+ * the optional "protocol" module parameter. In addition, the default
+ * Vendor ID, Product ID, release number and serial number can be overridden.
+ *
+ * There is support for multiple logical units (LUNs), each of which has
+ * its own backing file. The number of LUNs can be set using the optional
+ * "luns" module parameter (anywhere from 1 to 8), and the corresponding
+ * files are specified using comma-separated lists for "file" and "ro".
+ * The default number of LUNs is taken from the number of "file" elements;
+ * it is 1 if "file" is not given. If "removable" is not set then a backing
+ * file must be specified for each LUN. If it is set, then an unspecified
+ * or empty backing filename means the LUN's medium is not loaded. Ideally
+ * each LUN would be settable independently as a disk drive or a CD-ROM
+ * drive, but currently all LUNs have to be the same type. The CD-ROM
+ * emulation includes a single data track and no audio tracks; hence there
+ * need be only one backing file per LUN. Note also that the CD-ROM block
+ * length is set to 512 rather than the more common value 2048.
+ *
+ * Requirements are modest; only a bulk-in and a bulk-out endpoint are
+ * needed (an interrupt-out endpoint is also needed for CBI). The memory
+ * requirement amounts to two 16K buffers, size configurable by a parameter.
+ * Support is included for both full-speed and high-speed operation.
+ *
+ * Note that the driver is slightly non-portable in that it assumes a
+ * single memory/DMA buffer will be useable for bulk-in, bulk-out, and
+ * interrupt-in endpoints. With most device controllers this isn't an
+ * issue, but there may be some with hardware restrictions that prevent
+ * a buffer from being used by more than one endpoint.
+ *
+ * Module options:
+ *
+ * file=filename[,filename...]
+ * Required if "removable" is not set, names of
+ * the files or block devices used for
+ * backing storage
+ * serial=HHHH... Required serial number (string of hex chars)
+ * ro=b[,b...] Default false, booleans for read-only access
+ * removable Default false, boolean for removable media
+ * luns=N Default N = number of filenames, number of
+ * LUNs to support
+ * nofua=b[,b...] Default false, booleans for ignore FUA flag
+ * in SCSI WRITE(10,12) commands
+ * stall Default determined according to the type of
+ * USB device controller (usually true),
+ * boolean to permit the driver to halt
+ * bulk endpoints
+ * cdrom Default false, boolean for whether to emulate
+ * a CD-ROM drive
+ * transport=XXX Default BBB, transport name (CB, CBI, or BBB)
+ * protocol=YYY Default SCSI, protocol name (RBC, 8020 or
+ * ATAPI, QIC, UFI, 8070, or SCSI;
+ * also 1 - 6)
+ * vendor=0xVVVV Default 0x0525 (NetChip), USB Vendor ID
+ * product=0xPPPP Default 0xa4a5 (FSG), USB Product ID
+ * release=0xRRRR Override the USB release number (bcdDevice)
+ * buflen=N Default N=16384, buffer size used (will be
+ * rounded down to a multiple of
+ * PAGE_CACHE_SIZE)
+ *
+ * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file", "serial", "ro",
+ * "removable", "luns", "nofua", "stall", and "cdrom" options are available;
+ * default values are used for everything else.
+ *
+ * The pathnames of the backing files and the ro settings are available in
+ * the attribute files "file", "nofua", and "ro" in the lun<n> subdirectory of
+ * the gadget's sysfs directory. If the "removable" option is set, writing to
+ * these files will simulate ejecting/loading the medium (writing an empty
+ * line means eject) and adjusting a write-enable tab. Changes to the ro
+ * setting are not allowed when the medium is loaded or if CD-ROM emulation
+ * is being used.
+ *
+ * This gadget driver is heavily based on "Gadget Zero" by David Brownell.
+ * The driver's SCSI command interface was based on the "Information
+ * technology - Small Computer System Interface - 2" document from
+ * X3T9.2 Project 375D, Revision 10L, 7-SEP-93, available at
+ * <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>. The single exception
+ * is opcode 0x23 (READ FORMAT CAPACITIES), which was based on the
+ * "Universal Serial Bus Mass Storage Class UFI Command Specification"
+ * document, Revision 1.0, December 14, 1998, available at
+ * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>.
+ */
+
+
+/*
+ * Driver Design
+ *
+ * The FSG driver is fairly straightforward. There is a main kernel
+ * thread that handles most of the work. Interrupt routines field
+ * callbacks from the controller driver: bulk- and interrupt-request
+ * completion notifications, endpoint-0 events, and disconnect events.
+ * Completion events are passed to the main thread by wakeup calls. Many
+ * ep0 requests are handled at interrupt time, but SetInterface,
+ * SetConfiguration, and device reset requests are forwarded to the
+ * thread in the form of "exceptions" using SIGUSR1 signals (since they
+ * should interrupt any ongoing file I/O operations).
+ *
+ * The thread's main routine implements the standard command/data/status
+ * parts of a SCSI interaction. It and its subroutines are full of tests
+ * for pending signals/exceptions -- all this polling is necessary since
+ * the kernel has no setjmp/longjmp equivalents. (Maybe this is an
+ * indication that the driver really wants to be running in userspace.)
+ * An important point is that so long as the thread is alive it keeps an
+ * open reference to the backing file. This will prevent unmounting
+ * the backing file's underlying filesystem and could cause problems
+ * during system shutdown, for example. To prevent such problems, the
+ * thread catches INT, TERM, and KILL signals and converts them into
+ * an EXIT exception.
+ *
+ * In normal operation the main thread is started during the gadget's
+ * fsg_bind() callback and stopped during fsg_unbind(). But it can also
+ * exit when it receives a signal, and there's no point leaving the
+ * gadget running when the thread is dead. So just before the thread
+ * exits, it deregisters the gadget driver. This makes things a little
+ * tricky: The driver is deregistered at two places, and the exiting
+ * thread can indirectly call fsg_unbind() which in turn can tell the
+ * thread to exit. The first problem is resolved through the use of the
+ * REGISTERED atomic bitflag; the driver will only be deregistered once.
+ * The second problem is resolved by having fsg_unbind() check
+ * fsg->state; it won't try to stop the thread if the state is already
+ * FSG_STATE_TERMINATED.
+ *
+ * To provide maximum throughput, the driver uses a circular pipeline of
+ * buffer heads (struct fsg_buffhd). In principle the pipeline can be
+ * arbitrarily long; in practice the benefits don't justify having more
+ * than 2 stages (i.e., double buffering). But it helps to think of the
+ * pipeline as being a long one. Each buffer head contains a bulk-in and
+ * a bulk-out request pointer (since the buffer can be used for both
+ * output and input -- directions always are given from the host's
+ * point of view) as well as a pointer to the buffer and various state
+ * variables.
+ *
+ * Use of the pipeline follows a simple protocol. There is a variable
+ * (fsg->next_buffhd_to_fill) that points to the next buffer head to use.
+ * At any time that buffer head may still be in use from an earlier
+ * request, so each buffer head has a state variable indicating whether
+ * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the
+ * buffer head to be EMPTY, filling the buffer either by file I/O or by
+ * USB I/O (during which the buffer head is BUSY), and marking the buffer
+ * head FULL when the I/O is complete. Then the buffer will be emptied
+ * (again possibly by USB I/O, during which it is marked BUSY) and
+ * finally marked EMPTY again (possibly by a completion routine).
+ *
+ * A module parameter tells the driver to avoid stalling the bulk
+ * endpoints wherever the transport specification allows. This is
+ * necessary for some UDCs like the SuperH, which cannot reliably clear a
+ * halt on a bulk endpoint. However, under certain circumstances the
+ * Bulk-only specification requires a stall. In such cases the driver
+ * will halt the endpoint and set a flag indicating that it should clear
+ * the halt in software during the next device reset. Hopefully this
+ * will permit everything to work correctly. Furthermore, although the
+ * specification allows the bulk-out endpoint to halt when the host sends
+ * too much data, implementing this would cause an unavoidable race.
+ * The driver will always use the "no-stall" approach for OUT transfers.
+ *
+ * One subtle point concerns sending status-stage responses for ep0
+ * requests. Some of these requests, such as device reset, can involve
+ * interrupting an ongoing file I/O operation, which might take an
+ * arbitrarily long time. During that delay the host might give up on
+ * the original ep0 request and issue a new one. When that happens the
+ * driver should not notify the host about completion of the original
+ * request, as the host will no longer be waiting for it. So the driver
+ * assigns to each ep0 request a unique tag, and it keeps track of the
+ * tag value of the request associated with a long-running exception
+ * (device-reset, interface-change, or configuration-change). When the
+ * exception handler is finished, the status-stage response is submitted
+ * only if the current ep0 request tag is equal to the exception request
+ * tag. Thus only the most recently received ep0 request will get a
+ * status-stage response.
+ *
+ * Warning: This driver source file is too long. It ought to be split up
+ * into a header file plus about 3 separate .c files, to handle the details
+ * of the Gadget, USB Mass Storage, and SCSI protocols.
+ */
+
+
+/* #define VERBOSE_DEBUG */
+/* #define DUMP_MSGS */
+
+
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/dcache.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/limits.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/freezer.h>
+#include <linux/utsname.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "gadget_chips.h"
+
+
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module. So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+
+/*-------------------------------------------------------------------------*/
+
+#define DRIVER_DESC "File-backed Storage Gadget"
+#define DRIVER_NAME "g_file_storage"
+#define DRIVER_VERSION "1 September 2010"
+
+static char fsg_string_manufacturer[64];
+static const char fsg_string_product[] = DRIVER_DESC;
+static const char fsg_string_config[] = "Self-powered";
+static const char fsg_string_interface[] = "Mass Storage";
+
+
+#include "storage_common.c"
+
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Alan Stern");
+MODULE_LICENSE("Dual BSD/GPL");
+
+/*
+ * This driver assumes self-powered hardware and has no way for users to
+ * trigger remote wakeup. It uses autoconfiguration to select endpoints
+ * and endpoint addresses.
+ */
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/* Encapsulate the module parameter settings */
+
+static struct {
+ char *file[FSG_MAX_LUNS];
+ char *serial;
+ int ro[FSG_MAX_LUNS];
+ int nofua[FSG_MAX_LUNS];
+ unsigned int num_filenames;
+ unsigned int num_ros;
+ unsigned int num_nofuas;
+ unsigned int nluns;
+
+ int removable;
+ int can_stall;
+ int cdrom;
+
+ char *transport_parm;
+ char *protocol_parm;
+ unsigned short vendor;
+ unsigned short product;
+ unsigned short release;
+ unsigned int buflen;
+
+ int transport_type;
+ char *transport_name;
+ int protocol_type;
+ char *protocol_name;
+
+} mod_data = { // Default values
+ .transport_parm = "BBB",
+ .protocol_parm = "SCSI",
+ .removable = 0,
+ .can_stall = 1,
+ .cdrom = 0,
+ .vendor = FSG_VENDOR_ID,
+ .product = FSG_PRODUCT_ID,
+ .release = 0xffff, // Use controller chip type
+ .buflen = 16384,
+ };
+
+
+module_param_array_named(file, mod_data.file, charp, &mod_data.num_filenames,
+ S_IRUGO);
+MODULE_PARM_DESC(file, "names of backing files or devices");
+
+module_param_named(serial, mod_data.serial, charp, S_IRUGO);
+MODULE_PARM_DESC(serial, "USB serial number");
+
+module_param_array_named(ro, mod_data.ro, bool, &mod_data.num_ros, S_IRUGO);
+MODULE_PARM_DESC(ro, "true to force read-only");
+
+module_param_array_named(nofua, mod_data.nofua, bool, &mod_data.num_nofuas,
+ S_IRUGO);
+MODULE_PARM_DESC(nofua, "true to ignore SCSI WRITE(10,12) FUA bit");
+
+module_param_named(luns, mod_data.nluns, uint, S_IRUGO);
+MODULE_PARM_DESC(luns, "number of LUNs");
+
+module_param_named(removable, mod_data.removable, bool, S_IRUGO);
+MODULE_PARM_DESC(removable, "true to simulate removable media");
+
+module_param_named(stall, mod_data.can_stall, bool, S_IRUGO);
+MODULE_PARM_DESC(stall, "false to prevent bulk stalls");
+
+module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO);
+MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk");
+
+/* In the non-TEST version, only the module parameters listed above
+ * are available. */
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+
+module_param_named(transport, mod_data.transport_parm, charp, S_IRUGO);
+MODULE_PARM_DESC(transport, "type of transport (BBB, CBI, or CB)");
+
+module_param_named(protocol, mod_data.protocol_parm, charp, S_IRUGO);
+MODULE_PARM_DESC(protocol, "type of protocol (RBC, 8020, QIC, UFI, "
+ "8070, or SCSI)");
+
+module_param_named(vendor, mod_data.vendor, ushort, S_IRUGO);
+MODULE_PARM_DESC(vendor, "USB Vendor ID");
+
+module_param_named(product, mod_data.product, ushort, S_IRUGO);
+MODULE_PARM_DESC(product, "USB Product ID");
+
+module_param_named(release, mod_data.release, ushort, S_IRUGO);
+MODULE_PARM_DESC(release, "USB release number");
+
+module_param_named(buflen, mod_data.buflen, uint, S_IRUGO);
+MODULE_PARM_DESC(buflen, "I/O buffer size");
+
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+/*
+ * These definitions will permit the compiler to avoid generating code for
+ * parts of the driver that aren't used in the non-TEST version. Even gcc
+ * can recognize when a test of a constant expression yields a dead code
+ * path.
+ */
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+
+#define transport_is_bbb() (mod_data.transport_type == USB_PR_BULK)
+#define transport_is_cbi() (mod_data.transport_type == USB_PR_CBI)
+#define protocol_is_scsi() (mod_data.protocol_type == USB_SC_SCSI)
+
+#else
+
+#define transport_is_bbb() 1
+#define transport_is_cbi() 0
+#define protocol_is_scsi() 1
+
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+/*-------------------------------------------------------------------------*/
+
+
+struct fsg_dev {
+ /* lock protects: state, all the req_busy's, and cbbuf_cmnd */
+ spinlock_t lock;
+ struct usb_gadget *gadget;
+
+ /* filesem protects: backing files in use */
+ struct rw_semaphore filesem;
+
+ /* reference counting: wait until all LUNs are released */
+ struct kref ref;
+
+ struct usb_ep *ep0; // Handy copy of gadget->ep0
+ struct usb_request *ep0req; // For control responses
+ unsigned int ep0_req_tag;
+ const char *ep0req_name;
+
+ struct usb_request *intreq; // For interrupt responses
+ int intreq_busy;
+ struct fsg_buffhd *intr_buffhd;
+
+ unsigned int bulk_out_maxpacket;
+ enum fsg_state state; // For exception handling
+ unsigned int exception_req_tag;
+
+ u8 config, new_config;
+
+ unsigned int running : 1;
+ unsigned int bulk_in_enabled : 1;
+ unsigned int bulk_out_enabled : 1;
+ unsigned int intr_in_enabled : 1;
+ unsigned int phase_error : 1;
+ unsigned int short_packet_received : 1;
+ unsigned int bad_lun_okay : 1;
+
+ unsigned long atomic_bitflags;
+#define REGISTERED 0
+#define IGNORE_BULK_OUT 1
+#define SUSPENDED 2
+
+ struct usb_ep *bulk_in;
+ struct usb_ep *bulk_out;
+ struct usb_ep *intr_in;
+
+ struct fsg_buffhd *next_buffhd_to_fill;
+ struct fsg_buffhd *next_buffhd_to_drain;
+ struct fsg_buffhd buffhds[FSG_NUM_BUFFERS];
+
+ int thread_wakeup_needed;
+ struct completion thread_notifier;
+ struct task_struct *thread_task;
+
+ int cmnd_size;
+ u8 cmnd[MAX_COMMAND_SIZE];
+ enum data_direction data_dir;
+ u32 data_size;
+ u32 data_size_from_cmnd;
+ u32 tag;
+ unsigned int lun;
+ u32 residue;
+ u32 usb_amount_left;
+
+ /* The CB protocol offers no way for a host to know when a command
+ * has completed. As a result the next command may arrive early,
+ * and we will still have to handle it. For that reason we need
+ * a buffer to store new commands when using CB (or CBI, which
+ * does not oblige a host to wait for command completion either). */
+ int cbbuf_cmnd_size;
+ u8 cbbuf_cmnd[MAX_COMMAND_SIZE];
+
+ unsigned int nluns;
+ struct fsg_lun *luns;
+ struct fsg_lun *curlun;
+};
+
+typedef void (*fsg_routine_t)(struct fsg_dev *);
+
+static int exception_in_progress(struct fsg_dev *fsg)
+{
+ return (fsg->state > FSG_STATE_IDLE);
+}
+
+/* Make bulk-out requests be divisible by the maxpacket size */
+static void set_bulk_out_req_length(struct fsg_dev *fsg,
+ struct fsg_buffhd *bh, unsigned int length)
+{
+ unsigned int rem;
+
+ bh->bulk_out_intended_length = length;
+ rem = length % fsg->bulk_out_maxpacket;
+ if (rem > 0)
+ length += fsg->bulk_out_maxpacket - rem;
+ bh->outreq->length = length;
+}
+
+static struct fsg_dev *the_fsg;
+static struct usb_gadget_driver fsg_driver;
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
+{
+ const char *name;
+
+ if (ep == fsg->bulk_in)
+ name = "bulk-in";
+ else if (ep == fsg->bulk_out)
+ name = "bulk-out";
+ else
+ name = ep->name;
+ DBG(fsg, "%s set halt\n", name);
+ return usb_ep_set_halt(ep);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * DESCRIPTORS ... most are static, but strings and (full) configuration
+ * descriptors are built on demand. Also the (static) config and interface
+ * descriptors are adjusted during fsg_bind().
+ */
+
+/* There is only one configuration. */
+#define CONFIG_VALUE 1
+
+static struct usb_device_descriptor
+device_desc = {
+ .bLength = sizeof device_desc,
+ .bDescriptorType = USB_DT_DEVICE,
+
+ .bcdUSB = cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_PER_INTERFACE,
+
+ /* The next three values can be overridden by module parameters */
+ .idVendor = cpu_to_le16(FSG_VENDOR_ID),
+ .idProduct = cpu_to_le16(FSG_PRODUCT_ID),
+ .bcdDevice = cpu_to_le16(0xffff),
+
+ .iManufacturer = FSG_STRING_MANUFACTURER,
+ .iProduct = FSG_STRING_PRODUCT,
+ .iSerialNumber = FSG_STRING_SERIAL,
+ .bNumConfigurations = 1,
+};
+
+static struct usb_config_descriptor
+config_desc = {
+ .bLength = sizeof config_desc,
+ .bDescriptorType = USB_DT_CONFIG,
+
+ /* wTotalLength computed by usb_gadget_config_buf() */
+ .bNumInterfaces = 1,
+ .bConfigurationValue = CONFIG_VALUE,
+ .iConfiguration = FSG_STRING_CONFIG,
+ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+ .bMaxPower = CONFIG_USB_GADGET_VBUS_DRAW / 2,
+};
+
+
+static struct usb_qualifier_descriptor
+dev_qualifier = {
+ .bLength = sizeof dev_qualifier,
+ .bDescriptorType = USB_DT_DEVICE_QUALIFIER,
+
+ .bcdUSB = cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_PER_INTERFACE,
+
+ .bNumConfigurations = 1,
+};
+
+
+
+/*
+ * Config descriptors must agree with the code that sets configurations
+ * and with code managing interfaces and their altsettings. They must
+ * also handle different speeds and other-speed requests.
+ */
+static int populate_config_buf(struct usb_gadget *gadget,
+ u8 *buf, u8 type, unsigned index)
+{
+ enum usb_device_speed speed = gadget->speed;
+ int len;
+ const struct usb_descriptor_header **function;
+
+ if (index > 0)
+ return -EINVAL;
+
+ if (gadget_is_dualspeed(gadget) && type == USB_DT_OTHER_SPEED_CONFIG)
+ speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed;
+ function = gadget_is_dualspeed(gadget) && speed == USB_SPEED_HIGH
+ ? (const struct usb_descriptor_header **)fsg_hs_function
+ : (const struct usb_descriptor_header **)fsg_fs_function;
+
+ /* for now, don't advertise srp-only devices */
+ if (!gadget_is_otg(gadget))
+ function++;
+
+ len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function);
+ ((struct usb_config_descriptor *) buf)->bDescriptorType = type;
+ return len;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* These routines may be called in process context or in_irq */
+
+/* Caller must hold fsg->lock */
+static void wakeup_thread(struct fsg_dev *fsg)
+{
+ /* Tell the main thread that something has happened */
+ fsg->thread_wakeup_needed = 1;
+ if (fsg->thread_task)
+ wake_up_process(fsg->thread_task);
+}
+
+
+static void raise_exception(struct fsg_dev *fsg, enum fsg_state new_state)
+{
+ unsigned long flags;
+
+ /* Do nothing if a higher-priority exception is already in progress.
+ * If a lower-or-equal priority exception is in progress, preempt it
+ * and notify the main thread by sending it a signal. */
+ spin_lock_irqsave(&fsg->lock, flags);
+ if (fsg->state <= new_state) {
+ fsg->exception_req_tag = fsg->ep0_req_tag;
+ fsg->state = new_state;
+ if (fsg->thread_task)
+ send_sig_info(SIGUSR1, SEND_SIG_FORCED,
+ fsg->thread_task);
+ }
+ spin_unlock_irqrestore(&fsg->lock, flags);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* The disconnect callback and ep0 routines. These always run in_irq,
+ * except that ep0_queue() is called in the main thread to acknowledge
+ * completion of various requests: set config, set interface, and
+ * Bulk-only device reset. */
+
+static void fsg_disconnect(struct usb_gadget *gadget)
+{
+ struct fsg_dev *fsg = get_gadget_data(gadget);
+
+ DBG(fsg, "disconnect or port reset\n");
+ raise_exception(fsg, FSG_STATE_DISCONNECT);
+}
+
+
+static int ep0_queue(struct fsg_dev *fsg)
+{
+ int rc;
+
+ rc = usb_ep_queue(fsg->ep0, fsg->ep0req, GFP_ATOMIC);
+ if (rc != 0 && rc != -ESHUTDOWN) {
+
+ /* We can't do much more than wait for a reset */
+ WARNING(fsg, "error in submission: %s --> %d\n",
+ fsg->ep0->name, rc);
+ }
+ return rc;
+}
+
+static void ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct fsg_dev *fsg = ep->driver_data;
+
+ if (req->actual > 0)
+ dump_msg(fsg, fsg->ep0req_name, req->buf, req->actual);
+ if (req->status || req->actual != req->length)
+ DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+ req->status, req->actual, req->length);
+ if (req->status == -ECONNRESET) // Request was cancelled
+ usb_ep_fifo_flush(ep);
+
+ if (req->status == 0 && req->context)
+ ((fsg_routine_t) (req->context))(fsg);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Bulk and interrupt endpoint completion handlers.
+ * These always run in_irq. */
+
+static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct fsg_dev *fsg = ep->driver_data;
+ struct fsg_buffhd *bh = req->context;
+
+ if (req->status || req->actual != req->length)
+ DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+ req->status, req->actual, req->length);
+ if (req->status == -ECONNRESET) // Request was cancelled
+ usb_ep_fifo_flush(ep);
+
+ /* Hold the lock while we update the request and buffer states */
+ smp_wmb();
+ spin_lock(&fsg->lock);
+ bh->inreq_busy = 0;
+ bh->state = BUF_STATE_EMPTY;
+ wakeup_thread(fsg);
+ spin_unlock(&fsg->lock);
+}
+
+static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct fsg_dev *fsg = ep->driver_data;
+ struct fsg_buffhd *bh = req->context;
+
+ dump_msg(fsg, "bulk-out", req->buf, req->actual);
+ if (req->status || req->actual != bh->bulk_out_intended_length)
+ DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+ req->status, req->actual,
+ bh->bulk_out_intended_length);
+ if (req->status == -ECONNRESET) // Request was cancelled
+ usb_ep_fifo_flush(ep);
+
+ /* Hold the lock while we update the request and buffer states */
+ smp_wmb();
+ spin_lock(&fsg->lock);
+ bh->outreq_busy = 0;
+ bh->state = BUF_STATE_FULL;
+ wakeup_thread(fsg);
+ spin_unlock(&fsg->lock);
+}
+
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+static void intr_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct fsg_dev *fsg = ep->driver_data;
+ struct fsg_buffhd *bh = req->context;
+
+ if (req->status || req->actual != req->length)
+ DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+ req->status, req->actual, req->length);
+ if (req->status == -ECONNRESET) // Request was cancelled
+ usb_ep_fifo_flush(ep);
+
+ /* Hold the lock while we update the request and buffer states */
+ smp_wmb();
+ spin_lock(&fsg->lock);
+ fsg->intreq_busy = 0;
+ bh->state = BUF_STATE_EMPTY;
+ wakeup_thread(fsg);
+ spin_unlock(&fsg->lock);
+}
+
+#else
+static void intr_in_complete(struct usb_ep *ep, struct usb_request *req)
+{}
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Ep0 class-specific handlers. These always run in_irq. */
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct usb_request *req = fsg->ep0req;
+ static u8 cbi_reset_cmnd[6] = {
+ SEND_DIAGNOSTIC, 4, 0xff, 0xff, 0xff, 0xff};
+
+ /* Error in command transfer? */
+ if (req->status || req->length != req->actual ||
+ req->actual < 6 || req->actual > MAX_COMMAND_SIZE) {
+
+ /* Not all controllers allow a protocol stall after
+ * receiving control-out data, but we'll try anyway. */
+ fsg_set_halt(fsg, fsg->ep0);
+ return; // Wait for reset
+ }
+
+ /* Is it the special reset command? */
+ if (req->actual >= sizeof cbi_reset_cmnd &&
+ memcmp(req->buf, cbi_reset_cmnd,
+ sizeof cbi_reset_cmnd) == 0) {
+
+ /* Raise an exception to stop the current operation
+ * and reinitialize our state. */
+ DBG(fsg, "cbi reset request\n");
+ raise_exception(fsg, FSG_STATE_RESET);
+ return;
+ }
+
+ VDBG(fsg, "CB[I] accept device-specific command\n");
+ spin_lock(&fsg->lock);
+
+ /* Save the command for later */
+ if (fsg->cbbuf_cmnd_size)
+ WARNING(fsg, "CB[I] overwriting previous command\n");
+ fsg->cbbuf_cmnd_size = req->actual;
+ memcpy(fsg->cbbuf_cmnd, req->buf, fsg->cbbuf_cmnd_size);
+
+ wakeup_thread(fsg);
+ spin_unlock(&fsg->lock);
+}
+
+#else
+static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{}
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+static int class_setup_req(struct fsg_dev *fsg,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_request *req = fsg->ep0req;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ if (!fsg->config)
+ return value;
+
+ /* Handle Bulk-only class-specific requests */
+ if (transport_is_bbb()) {
+ switch (ctrl->bRequest) {
+
+ case USB_BULK_RESET_REQUEST:
+ if (ctrl->bRequestType != (USB_DIR_OUT |
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+ break;
+ if (w_index != 0 || w_value != 0) {
+ value = -EDOM;
+ break;
+ }
+
+ /* Raise an exception to stop the current operation
+ * and reinitialize our state. */
+ DBG(fsg, "bulk reset request\n");
+ raise_exception(fsg, FSG_STATE_RESET);
+ value = DELAYED_STATUS;
+ break;
+
+ case USB_BULK_GET_MAX_LUN_REQUEST:
+ if (ctrl->bRequestType != (USB_DIR_IN |
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+ break;
+ if (w_index != 0 || w_value != 0) {
+ value = -EDOM;
+ break;
+ }
+ VDBG(fsg, "get max LUN\n");
+ *(u8 *) req->buf = fsg->nluns - 1;
+ value = 1;
+ break;
+ }
+ }
+
+ /* Handle CBI class-specific requests */
+ else {
+ switch (ctrl->bRequest) {
+
+ case USB_CBI_ADSC_REQUEST:
+ if (ctrl->bRequestType != (USB_DIR_OUT |
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+ break;
+ if (w_index != 0 || w_value != 0) {
+ value = -EDOM;
+ break;
+ }
+ if (w_length > MAX_COMMAND_SIZE) {
+ value = -EOVERFLOW;
+ break;
+ }
+ value = w_length;
+ fsg->ep0req->context = received_cbi_adsc;
+ break;
+ }
+ }
+
+ if (value == -EOPNOTSUPP)
+ VDBG(fsg,
+ "unknown class-specific control req "
+ "%02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ le16_to_cpu(ctrl->wValue), w_index, w_length);
+ return value;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Ep0 standard request handlers. These always run in_irq. */
+
+static int standard_setup_req(struct fsg_dev *fsg,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_request *req = fsg->ep0req;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+
+ /* Usually this just stores reply data in the pre-allocated ep0 buffer,
+ * but config change events will also reconfigure hardware. */
+ switch (ctrl->bRequest) {
+
+ case USB_REQ_GET_DESCRIPTOR:
+ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+ USB_RECIP_DEVICE))
+ break;
+ switch (w_value >> 8) {
+
+ case USB_DT_DEVICE:
+ VDBG(fsg, "get device descriptor\n");
+ value = sizeof device_desc;
+ memcpy(req->buf, &device_desc, value);
+ break;
+ case USB_DT_DEVICE_QUALIFIER:
+ VDBG(fsg, "get device qualifier\n");
+ if (!gadget_is_dualspeed(fsg->gadget))
+ break;
+ value = sizeof dev_qualifier;
+ memcpy(req->buf, &dev_qualifier, value);
+ break;
+
+ case USB_DT_OTHER_SPEED_CONFIG:
+ VDBG(fsg, "get other-speed config descriptor\n");
+ if (!gadget_is_dualspeed(fsg->gadget))
+ break;
+ goto get_config;
+ case USB_DT_CONFIG:
+ VDBG(fsg, "get configuration descriptor\n");
+get_config:
+ value = populate_config_buf(fsg->gadget,
+ req->buf,
+ w_value >> 8,
+ w_value & 0xff);
+ break;
+
+ case USB_DT_STRING:
+ VDBG(fsg, "get string descriptor\n");
+
+ /* wIndex == language code */
+ value = usb_gadget_get_string(&fsg_stringtab,
+ w_value & 0xff, req->buf);
+ break;
+ }
+ break;
+
+ /* One config, two speeds */
+ case USB_REQ_SET_CONFIGURATION:
+ if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD |
+ USB_RECIP_DEVICE))
+ break;
+ VDBG(fsg, "set configuration\n");
+ if (w_value == CONFIG_VALUE || w_value == 0) {
+ fsg->new_config = w_value;
+
+ /* Raise an exception to wipe out previous transaction
+ * state (queued bufs, etc) and set the new config. */
+ raise_exception(fsg, FSG_STATE_CONFIG_CHANGE);
+ value = DELAYED_STATUS;
+ }
+ break;
+ case USB_REQ_GET_CONFIGURATION:
+ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+ USB_RECIP_DEVICE))
+ break;
+ VDBG(fsg, "get configuration\n");
+ *(u8 *) req->buf = fsg->config;
+ value = 1;
+ break;
+
+ case USB_REQ_SET_INTERFACE:
+ if (ctrl->bRequestType != (USB_DIR_OUT| USB_TYPE_STANDARD |
+ USB_RECIP_INTERFACE))
+ break;
+ if (fsg->config && w_index == 0) {
+
+ /* Raise an exception to wipe out previous transaction
+ * state (queued bufs, etc) and install the new
+ * interface altsetting. */
+ raise_exception(fsg, FSG_STATE_INTERFACE_CHANGE);
+ value = DELAYED_STATUS;
+ }
+ break;
+ case USB_REQ_GET_INTERFACE:
+ if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+ USB_RECIP_INTERFACE))
+ break;
+ if (!fsg->config)
+ break;
+ if (w_index != 0) {
+ value = -EDOM;
+ break;
+ }
+ VDBG(fsg, "get interface\n");
+ *(u8 *) req->buf = 0;
+ value = 1;
+ break;
+
+ default:
+ VDBG(fsg,
+ "unknown control req %02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, le16_to_cpu(ctrl->wLength));
+ }
+
+ return value;
+}
+
+
+static int fsg_setup(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct fsg_dev *fsg = get_gadget_data(gadget);
+ int rc;
+ int w_length = le16_to_cpu(ctrl->wLength);
+
+ ++fsg->ep0_req_tag; // Record arrival of a new request
+ fsg->ep0req->context = NULL;
+ fsg->ep0req->length = 0;
+ dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl));
+
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS)
+ rc = class_setup_req(fsg, ctrl);
+ else
+ rc = standard_setup_req(fsg, ctrl);
+
+ /* Respond with data/status or defer until later? */
+ if (rc >= 0 && rc != DELAYED_STATUS) {
+ rc = min(rc, w_length);
+ fsg->ep0req->length = rc;
+ fsg->ep0req->zero = rc < w_length;
+ fsg->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ?
+ "ep0-in" : "ep0-out");
+ rc = ep0_queue(fsg);
+ }
+
+ /* Device either stalls (rc < 0) or reports success */
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* All the following routines run in process context */
+
+
+/* Use this for bulk or interrupt transfers, not ep0 */
+static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
+ struct usb_request *req, int *pbusy,
+ enum fsg_buffer_state *state)
+{
+ int rc;
+
+ if (ep == fsg->bulk_in)
+ dump_msg(fsg, "bulk-in", req->buf, req->length);
+ else if (ep == fsg->intr_in)
+ dump_msg(fsg, "intr-in", req->buf, req->length);
+
+ spin_lock_irq(&fsg->lock);
+ *pbusy = 1;
+ *state = BUF_STATE_BUSY;
+ spin_unlock_irq(&fsg->lock);
+ rc = usb_ep_queue(ep, req, GFP_KERNEL);
+ if (rc != 0) {
+ *pbusy = 0;
+ *state = BUF_STATE_EMPTY;
+
+ /* We can't do much more than wait for a reset */
+
+ /* Note: currently the net2280 driver fails zero-length
+ * submissions if DMA is enabled. */
+ if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP &&
+ req->length == 0))
+ WARNING(fsg, "error in submission: %s --> %d\n",
+ ep->name, rc);
+ }
+}
+
+
+static int sleep_thread(struct fsg_dev *fsg)
+{
+ int rc = 0;
+
+ /* Wait until a signal arrives or we are woken up */
+ for (;;) {
+ try_to_freeze();
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (signal_pending(current)) {
+ rc = -EINTR;
+ break;
+ }
+ if (fsg->thread_wakeup_needed)
+ break;
+ schedule();
+ }
+ __set_current_state(TASK_RUNNING);
+ fsg->thread_wakeup_needed = 0;
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_read(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ u32 lba;
+ struct fsg_buffhd *bh;
+ int rc;
+ u32 amount_left;
+ loff_t file_offset, file_offset_tmp;
+ unsigned int amount;
+ unsigned int partial_page;
+ ssize_t nread;
+
+ /* Get the starting Logical Block Address and check that it's
+ * not too big */
+ if (fsg->cmnd[0] == READ_6)
+ lba = get_unaligned_be24(&fsg->cmnd[1]);
+ else {
+ lba = get_unaligned_be32(&fsg->cmnd[2]);
+
+ /* We allow DPO (Disable Page Out = don't save data in the
+ * cache) and FUA (Force Unit Access = don't read from the
+ * cache), but we don't implement them. */
+ if ((fsg->cmnd[1] & ~0x18) != 0) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ }
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+ file_offset = ((loff_t) lba) << 9;
+
+ /* Carry out the file reads */
+ amount_left = fsg->data_size_from_cmnd;
+ if (unlikely(amount_left == 0))
+ return -EIO; // No default reply
+
+ for (;;) {
+
+ /* Figure out how much we need to read:
+ * Try to read the remaining amount.
+ * But don't read more than the buffer size.
+ * And don't try to read past the end of the file.
+ * Finally, if we're not at a page boundary, don't read past
+ * the next page.
+ * If this means reading 0 then we were asked to read past
+ * the end of file. */
+ amount = min((unsigned int) amount_left, mod_data.buflen);
+ amount = min((loff_t) amount,
+ curlun->file_length - file_offset);
+ partial_page = file_offset & (PAGE_CACHE_SIZE - 1);
+ if (partial_page > 0)
+ amount = min(amount, (unsigned int) PAGE_CACHE_SIZE -
+ partial_page);
+
+ /* Wait for the next buffer to become available */
+ bh = fsg->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+
+ /* If we were asked to read past the end of file,
+ * end with an empty buffer. */
+ if (amount == 0) {
+ curlun->sense_data =
+ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ bh->inreq->length = 0;
+ bh->state = BUF_STATE_FULL;
+ break;
+ }
+
+ /* Perform the read */
+ file_offset_tmp = file_offset;
+ nread = vfs_read(curlun->filp,
+ (char __user *) bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+ (unsigned long long) file_offset,
+ (int) nread);
+ if (signal_pending(current))
+ return -EINTR;
+
+ if (nread < 0) {
+ LDBG(curlun, "error in file read: %d\n",
+ (int) nread);
+ nread = 0;
+ } else if (nread < amount) {
+ LDBG(curlun, "partial file read: %d/%u\n",
+ (int) nread, amount);
+ nread -= (nread & 511); // Round down to a block
+ }
+ file_offset += nread;
+ amount_left -= nread;
+ fsg->residue -= nread;
+ bh->inreq->length = nread;
+ bh->state = BUF_STATE_FULL;
+
+ /* If an error occurred, report it and its position */
+ if (nread < amount) {
+ curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ if (amount_left == 0)
+ break; // No more left to read
+
+ /* Send this buffer and go read some more */
+ bh->inreq->zero = 0;
+ start_transfer(fsg, fsg->bulk_in, bh->inreq,
+ &bh->inreq_busy, &bh->state);
+ fsg->next_buffhd_to_fill = bh->next;
+ }
+
+ return -EIO; // No default reply
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_write(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ u32 lba;
+ struct fsg_buffhd *bh;
+ int get_some_more;
+ u32 amount_left_to_req, amount_left_to_write;
+ loff_t usb_offset, file_offset, file_offset_tmp;
+ unsigned int amount;
+ unsigned int partial_page;
+ ssize_t nwritten;
+ int rc;
+
+ if (curlun->ro) {
+ curlun->sense_data = SS_WRITE_PROTECTED;
+ return -EINVAL;
+ }
+ spin_lock(&curlun->filp->f_lock);
+ curlun->filp->f_flags &= ~O_SYNC; // Default is not to wait
+ spin_unlock(&curlun->filp->f_lock);
+
+ /* Get the starting Logical Block Address and check that it's
+ * not too big */
+ if (fsg->cmnd[0] == WRITE_6)
+ lba = get_unaligned_be24(&fsg->cmnd[1]);
+ else {
+ lba = get_unaligned_be32(&fsg->cmnd[2]);
+
+ /* We allow DPO (Disable Page Out = don't save data in the
+ * cache) and FUA (Force Unit Access = write directly to the
+ * medium). We don't implement DPO; we implement FUA by
+ * performing synchronous output. */
+ if ((fsg->cmnd[1] & ~0x18) != 0) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ /* FUA */
+ if (!curlun->nofua && (fsg->cmnd[1] & 0x08)) {
+ spin_lock(&curlun->filp->f_lock);
+ curlun->filp->f_flags |= O_DSYNC;
+ spin_unlock(&curlun->filp->f_lock);
+ }
+ }
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ /* Carry out the file writes */
+ get_some_more = 1;
+ file_offset = usb_offset = ((loff_t) lba) << 9;
+ amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd;
+
+ while (amount_left_to_write > 0) {
+
+ /* Queue a request for more data from the host */
+ bh = fsg->next_buffhd_to_fill;
+ if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+
+ /* Figure out how much we want to get:
+ * Try to get the remaining amount.
+ * But don't get more than the buffer size.
+ * And don't try to go past the end of the file.
+ * If we're not at a page boundary,
+ * don't go past the next page.
+ * If this means getting 0, then we were asked
+ * to write past the end of file.
+ * Finally, round down to a block boundary. */
+ amount = min(amount_left_to_req, mod_data.buflen);
+ amount = min((loff_t) amount, curlun->file_length -
+ usb_offset);
+ partial_page = usb_offset & (PAGE_CACHE_SIZE - 1);
+ if (partial_page > 0)
+ amount = min(amount,
+ (unsigned int) PAGE_CACHE_SIZE - partial_page);
+
+ if (amount == 0) {
+ get_some_more = 0;
+ curlun->sense_data =
+ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ curlun->sense_data_info = usb_offset >> 9;
+ curlun->info_valid = 1;
+ continue;
+ }
+ amount -= (amount & 511);
+ if (amount == 0) {
+
+ /* Why were we were asked to transfer a
+ * partial block? */
+ get_some_more = 0;
+ continue;
+ }
+
+ /* Get the next buffer */
+ usb_offset += amount;
+ fsg->usb_amount_left -= amount;
+ amount_left_to_req -= amount;
+ if (amount_left_to_req == 0)
+ get_some_more = 0;
+
+ /* amount is always divisible by 512, hence by
+ * the bulk-out maxpacket size */
+ bh->outreq->length = bh->bulk_out_intended_length =
+ amount;
+ bh->outreq->short_not_ok = 1;
+ start_transfer(fsg, fsg->bulk_out, bh->outreq,
+ &bh->outreq_busy, &bh->state);
+ fsg->next_buffhd_to_fill = bh->next;
+ continue;
+ }
+
+ /* Write the received data to the backing file */
+ bh = fsg->next_buffhd_to_drain;
+ if (bh->state == BUF_STATE_EMPTY && !get_some_more)
+ break; // We stopped early
+ if (bh->state == BUF_STATE_FULL) {
+ smp_rmb();
+ fsg->next_buffhd_to_drain = bh->next;
+ bh->state = BUF_STATE_EMPTY;
+
+ /* Did something go wrong with the transfer? */
+ if (bh->outreq->status != 0) {
+ curlun->sense_data = SS_COMMUNICATION_FAILURE;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ amount = bh->outreq->actual;
+ if (curlun->file_length - file_offset < amount) {
+ LERROR(curlun,
+ "write %u @ %llu beyond end %llu\n",
+ amount, (unsigned long long) file_offset,
+ (unsigned long long) curlun->file_length);
+ amount = curlun->file_length - file_offset;
+ }
+
+ /* Perform the write */
+ file_offset_tmp = file_offset;
+ nwritten = vfs_write(curlun->filp,
+ (char __user *) bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
+ (unsigned long long) file_offset,
+ (int) nwritten);
+ if (signal_pending(current))
+ return -EINTR; // Interrupted!
+
+ if (nwritten < 0) {
+ LDBG(curlun, "error in file write: %d\n",
+ (int) nwritten);
+ nwritten = 0;
+ } else if (nwritten < amount) {
+ LDBG(curlun, "partial file write: %d/%u\n",
+ (int) nwritten, amount);
+ nwritten -= (nwritten & 511);
+ // Round down to a block
+ }
+ file_offset += nwritten;
+ amount_left_to_write -= nwritten;
+ fsg->residue -= nwritten;
+
+ /* If an error occurred, report it and its position */
+ if (nwritten < amount) {
+ curlun->sense_data = SS_WRITE_ERROR;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ /* Did the host decide to stop early? */
+ if (bh->outreq->actual != bh->outreq->length) {
+ fsg->short_packet_received = 1;
+ break;
+ }
+ continue;
+ }
+
+ /* Wait for something to happen */
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+
+ return -EIO; // No default reply
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_synchronize_cache(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ int rc;
+
+ /* We ignore the requested LBA and write out all file's
+ * dirty data buffers. */
+ rc = fsg_lun_fsync_sub(curlun);
+ if (rc)
+ curlun->sense_data = SS_WRITE_ERROR;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void invalidate_sub(struct fsg_lun *curlun)
+{
+ struct file *filp = curlun->filp;
+ struct inode *inode = filp->f_path.dentry->d_inode;
+ unsigned long rc;
+
+ rc = invalidate_mapping_pages(inode->i_mapping, 0, -1);
+ VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc);
+}
+
+static int do_verify(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ u32 lba;
+ u32 verification_length;
+ struct fsg_buffhd *bh = fsg->next_buffhd_to_fill;
+ loff_t file_offset, file_offset_tmp;
+ u32 amount_left;
+ unsigned int amount;
+ ssize_t nread;
+
+ /* Get the starting Logical Block Address and check that it's
+ * not too big */
+ lba = get_unaligned_be32(&fsg->cmnd[2]);
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ /* We allow DPO (Disable Page Out = don't save data in the
+ * cache) but we don't implement it. */
+ if ((fsg->cmnd[1] & ~0x10) != 0) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ verification_length = get_unaligned_be16(&fsg->cmnd[7]);
+ if (unlikely(verification_length == 0))
+ return -EIO; // No default reply
+
+ /* Prepare to carry out the file verify */
+ amount_left = verification_length << 9;
+ file_offset = ((loff_t) lba) << 9;
+
+ /* Write out all the dirty buffers before invalidating them */
+ fsg_lun_fsync_sub(curlun);
+ if (signal_pending(current))
+ return -EINTR;
+
+ invalidate_sub(curlun);
+ if (signal_pending(current))
+ return -EINTR;
+
+ /* Just try to read the requested blocks */
+ while (amount_left > 0) {
+
+ /* Figure out how much we need to read:
+ * Try to read the remaining amount, but not more than
+ * the buffer size.
+ * And don't try to read past the end of the file.
+ * If this means reading 0 then we were asked to read
+ * past the end of file. */
+ amount = min((unsigned int) amount_left, mod_data.buflen);
+ amount = min((loff_t) amount,
+ curlun->file_length - file_offset);
+ if (amount == 0) {
+ curlun->sense_data =
+ SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+
+ /* Perform the read */
+ file_offset_tmp = file_offset;
+ nread = vfs_read(curlun->filp,
+ (char __user *) bh->buf,
+ amount, &file_offset_tmp);
+ VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+ (unsigned long long) file_offset,
+ (int) nread);
+ if (signal_pending(current))
+ return -EINTR;
+
+ if (nread < 0) {
+ LDBG(curlun, "error in file verify: %d\n",
+ (int) nread);
+ nread = 0;
+ } else if (nread < amount) {
+ LDBG(curlun, "partial file verify: %d/%u\n",
+ (int) nread, amount);
+ nread -= (nread & 511); // Round down to a sector
+ }
+ if (nread == 0) {
+ curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+ curlun->sense_data_info = file_offset >> 9;
+ curlun->info_valid = 1;
+ break;
+ }
+ file_offset += nread;
+ amount_left -= nread;
+ }
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ u8 *buf = (u8 *) bh->buf;
+
+ static char vendor_id[] = "Linux ";
+ static char product_disk_id[] = "File-Stor Gadget";
+ static char product_cdrom_id[] = "File-CD Gadget ";
+
+ if (!fsg->curlun) { // Unsupported LUNs are okay
+ fsg->bad_lun_okay = 1;
+ memset(buf, 0, 36);
+ buf[0] = 0x7f; // Unsupported, no device-type
+ buf[4] = 31; // Additional length
+ return 36;
+ }
+
+ memset(buf, 0, 8);
+ buf[0] = (mod_data.cdrom ? TYPE_ROM : TYPE_DISK);
+ if (mod_data.removable)
+ buf[1] = 0x80;
+ buf[2] = 2; // ANSI SCSI level 2
+ buf[3] = 2; // SCSI-2 INQUIRY data format
+ buf[4] = 31; // Additional length
+ // No special options
+ sprintf(buf + 8, "%-8s%-16s%04x", vendor_id,
+ (mod_data.cdrom ? product_cdrom_id :
+ product_disk_id),
+ mod_data.release);
+ return 36;
+}
+
+
+static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ u8 *buf = (u8 *) bh->buf;
+ u32 sd, sdinfo;
+ int valid;
+
+ /*
+ * From the SCSI-2 spec., section 7.9 (Unit attention condition):
+ *
+ * If a REQUEST SENSE command is received from an initiator
+ * with a pending unit attention condition (before the target
+ * generates the contingent allegiance condition), then the
+ * target shall either:
+ * a) report any pending sense data and preserve the unit
+ * attention condition on the logical unit, or,
+ * b) report the unit attention condition, may discard any
+ * pending sense data, and clear the unit attention
+ * condition on the logical unit for that initiator.
+ *
+ * FSG normally uses option a); enable this code to use option b).
+ */
+#if 0
+ if (curlun && curlun->unit_attention_data != SS_NO_SENSE) {
+ curlun->sense_data = curlun->unit_attention_data;
+ curlun->unit_attention_data = SS_NO_SENSE;
+ }
+#endif
+
+ if (!curlun) { // Unsupported LUNs are okay
+ fsg->bad_lun_okay = 1;
+ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+ sdinfo = 0;
+ valid = 0;
+ } else {
+ sd = curlun->sense_data;
+ sdinfo = curlun->sense_data_info;
+ valid = curlun->info_valid << 7;
+ curlun->sense_data = SS_NO_SENSE;
+ curlun->sense_data_info = 0;
+ curlun->info_valid = 0;
+ }
+
+ memset(buf, 0, 18);
+ buf[0] = valid | 0x70; // Valid, current error
+ buf[2] = SK(sd);
+ put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */
+ buf[7] = 18 - 8; // Additional sense length
+ buf[12] = ASC(sd);
+ buf[13] = ASCQ(sd);
+ return 18;
+}
+
+
+static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ u32 lba = get_unaligned_be32(&fsg->cmnd[2]);
+ int pmi = fsg->cmnd[8];
+ u8 *buf = (u8 *) bh->buf;
+
+ /* Check the PMI and LBA fields */
+ if (pmi > 1 || (pmi == 0 && lba != 0)) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ put_unaligned_be32(curlun->num_sectors - 1, &buf[0]);
+ /* Max logical block */
+ put_unaligned_be32(512, &buf[4]); /* Block length */
+ return 8;
+}
+
+
+static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ int msf = fsg->cmnd[1] & 0x02;
+ u32 lba = get_unaligned_be32(&fsg->cmnd[2]);
+ u8 *buf = (u8 *) bh->buf;
+
+ if ((fsg->cmnd[1] & ~0x02) != 0) { /* Mask away MSF */
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ if (lba >= curlun->num_sectors) {
+ curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+ return -EINVAL;
+ }
+
+ memset(buf, 0, 8);
+ buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */
+ store_cdrom_address(&buf[4], msf, lba);
+ return 8;
+}
+
+
+static int do_read_toc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ int msf = fsg->cmnd[1] & 0x02;
+ int start_track = fsg->cmnd[6];
+ u8 *buf = (u8 *) bh->buf;
+
+ if ((fsg->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */
+ start_track > 1) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ memset(buf, 0, 20);
+ buf[1] = (20-2); /* TOC data length */
+ buf[2] = 1; /* First track number */
+ buf[3] = 1; /* Last track number */
+ buf[5] = 0x16; /* Data track, copying allowed */
+ buf[6] = 0x01; /* Only track is number 1 */
+ store_cdrom_address(&buf[8], msf, 0);
+
+ buf[13] = 0x16; /* Lead-out track is data */
+ buf[14] = 0xAA; /* Lead-out track number */
+ store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+ return 20;
+}
+
+
+static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ int mscmnd = fsg->cmnd[0];
+ u8 *buf = (u8 *) bh->buf;
+ u8 *buf0 = buf;
+ int pc, page_code;
+ int changeable_values, all_pages;
+ int valid_page = 0;
+ int len, limit;
+
+ if ((fsg->cmnd[1] & ~0x08) != 0) { // Mask away DBD
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ pc = fsg->cmnd[2] >> 6;
+ page_code = fsg->cmnd[2] & 0x3f;
+ if (pc == 3) {
+ curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED;
+ return -EINVAL;
+ }
+ changeable_values = (pc == 1);
+ all_pages = (page_code == 0x3f);
+
+ /* Write the mode parameter header. Fixed values are: default
+ * medium type, no cache control (DPOFUA), and no block descriptors.
+ * The only variable value is the WriteProtect bit. We will fill in
+ * the mode data length later. */
+ memset(buf, 0, 8);
+ if (mscmnd == MODE_SENSE) {
+ buf[2] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA
+ buf += 4;
+ limit = 255;
+ } else { // MODE_SENSE_10
+ buf[3] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA
+ buf += 8;
+ limit = 65535; // Should really be mod_data.buflen
+ }
+
+ /* No block descriptors */
+
+ /* The mode pages, in numerical order. The only page we support
+ * is the Caching page. */
+ if (page_code == 0x08 || all_pages) {
+ valid_page = 1;
+ buf[0] = 0x08; // Page code
+ buf[1] = 10; // Page length
+ memset(buf+2, 0, 10); // None of the fields are changeable
+
+ if (!changeable_values) {
+ buf[2] = 0x04; // Write cache enable,
+ // Read cache not disabled
+ // No cache retention priorities
+ put_unaligned_be16(0xffff, &buf[4]);
+ /* Don't disable prefetch */
+ /* Minimum prefetch = 0 */
+ put_unaligned_be16(0xffff, &buf[8]);
+ /* Maximum prefetch */
+ put_unaligned_be16(0xffff, &buf[10]);
+ /* Maximum prefetch ceiling */
+ }
+ buf += 12;
+ }
+
+ /* Check that a valid page was requested and the mode data length
+ * isn't too long. */
+ len = buf - buf0;
+ if (!valid_page || len > limit) {
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ /* Store the mode data length */
+ if (mscmnd == MODE_SENSE)
+ buf0[0] = len - 1;
+ else
+ put_unaligned_be16(len - 2, buf0);
+ return len;
+}
+
+
+static int do_start_stop(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ int loej, start;
+
+ if (!mod_data.removable) {
+ curlun->sense_data = SS_INVALID_COMMAND;
+ return -EINVAL;
+ }
+
+ // int immed = fsg->cmnd[1] & 0x01;
+ loej = fsg->cmnd[4] & 0x02;
+ start = fsg->cmnd[4] & 0x01;
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+ if ((fsg->cmnd[1] & ~0x01) != 0 || // Mask away Immed
+ (fsg->cmnd[4] & ~0x03) != 0) { // Mask LoEj, Start
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ if (!start) {
+
+ /* Are we allowed to unload the media? */
+ if (curlun->prevent_medium_removal) {
+ LDBG(curlun, "unload attempt prevented\n");
+ curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED;
+ return -EINVAL;
+ }
+ if (loej) { // Simulate an unload/eject
+ up_read(&fsg->filesem);
+ down_write(&fsg->filesem);
+ fsg_lun_close(curlun);
+ up_write(&fsg->filesem);
+ down_read(&fsg->filesem);
+ }
+ } else {
+
+ /* Our emulation doesn't support mounting; the medium is
+ * available for use as soon as it is loaded. */
+ if (!fsg_lun_is_open(curlun)) {
+ curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+ return -EINVAL;
+ }
+ }
+#endif
+ return 0;
+}
+
+
+static int do_prevent_allow(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ int prevent;
+
+ if (!mod_data.removable) {
+ curlun->sense_data = SS_INVALID_COMMAND;
+ return -EINVAL;
+ }
+
+ prevent = fsg->cmnd[4] & 0x01;
+ if ((fsg->cmnd[4] & ~0x01) != 0) { // Mask away Prevent
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+
+ if (curlun->prevent_medium_removal && !prevent)
+ fsg_lun_fsync_sub(curlun);
+ curlun->prevent_medium_removal = prevent;
+ return 0;
+}
+
+
+static int do_read_format_capacities(struct fsg_dev *fsg,
+ struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ u8 *buf = (u8 *) bh->buf;
+
+ buf[0] = buf[1] = buf[2] = 0;
+ buf[3] = 8; // Only the Current/Maximum Capacity Descriptor
+ buf += 4;
+
+ put_unaligned_be32(curlun->num_sectors, &buf[0]);
+ /* Number of blocks */
+ put_unaligned_be32(512, &buf[4]); /* Block length */
+ buf[4] = 0x02; /* Current capacity */
+ return 12;
+}
+
+
+static int do_mode_select(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+
+ /* We don't support MODE SELECT */
+ curlun->sense_data = SS_INVALID_COMMAND;
+ return -EINVAL;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int halt_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+ int rc;
+
+ rc = fsg_set_halt(fsg, fsg->bulk_in);
+ if (rc == -EAGAIN)
+ VDBG(fsg, "delayed bulk-in endpoint halt\n");
+ while (rc != 0) {
+ if (rc != -EAGAIN) {
+ WARNING(fsg, "usb_ep_set_halt -> %d\n", rc);
+ rc = 0;
+ break;
+ }
+
+ /* Wait for a short time and then try again */
+ if (msleep_interruptible(100) != 0)
+ return -EINTR;
+ rc = usb_ep_set_halt(fsg->bulk_in);
+ }
+ return rc;
+}
+
+static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+ int rc;
+
+ DBG(fsg, "bulk-in set wedge\n");
+ rc = usb_ep_set_wedge(fsg->bulk_in);
+ if (rc == -EAGAIN)
+ VDBG(fsg, "delayed bulk-in endpoint wedge\n");
+ while (rc != 0) {
+ if (rc != -EAGAIN) {
+ WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc);
+ rc = 0;
+ break;
+ }
+
+ /* Wait for a short time and then try again */
+ if (msleep_interruptible(100) != 0)
+ return -EINTR;
+ rc = usb_ep_set_wedge(fsg->bulk_in);
+ }
+ return rc;
+}
+
+static int throw_away_data(struct fsg_dev *fsg)
+{
+ struct fsg_buffhd *bh;
+ u32 amount;
+ int rc;
+
+ while ((bh = fsg->next_buffhd_to_drain)->state != BUF_STATE_EMPTY ||
+ fsg->usb_amount_left > 0) {
+
+ /* Throw away the data in a filled buffer */
+ if (bh->state == BUF_STATE_FULL) {
+ smp_rmb();
+ bh->state = BUF_STATE_EMPTY;
+ fsg->next_buffhd_to_drain = bh->next;
+
+ /* A short packet or an error ends everything */
+ if (bh->outreq->actual != bh->outreq->length ||
+ bh->outreq->status != 0) {
+ raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT);
+ return -EINTR;
+ }
+ continue;
+ }
+
+ /* Try to submit another request if we need one */
+ bh = fsg->next_buffhd_to_fill;
+ if (bh->state == BUF_STATE_EMPTY && fsg->usb_amount_left > 0) {
+ amount = min(fsg->usb_amount_left,
+ (u32) mod_data.buflen);
+
+ /* amount is always divisible by 512, hence by
+ * the bulk-out maxpacket size */
+ bh->outreq->length = bh->bulk_out_intended_length =
+ amount;
+ bh->outreq->short_not_ok = 1;
+ start_transfer(fsg, fsg->bulk_out, bh->outreq,
+ &bh->outreq_busy, &bh->state);
+ fsg->next_buffhd_to_fill = bh->next;
+ fsg->usb_amount_left -= amount;
+ continue;
+ }
+
+ /* Otherwise wait for something to happen */
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+
+static int finish_reply(struct fsg_dev *fsg)
+{
+ struct fsg_buffhd *bh = fsg->next_buffhd_to_fill;
+ int rc = 0;
+
+ switch (fsg->data_dir) {
+ case DATA_DIR_NONE:
+ break; // Nothing to send
+
+ /* If we don't know whether the host wants to read or write,
+ * this must be CB or CBI with an unknown command. We mustn't
+ * try to send or receive any data. So stall both bulk pipes
+ * if we can and wait for a reset. */
+ case DATA_DIR_UNKNOWN:
+ if (mod_data.can_stall) {
+ fsg_set_halt(fsg, fsg->bulk_out);
+ rc = halt_bulk_in_endpoint(fsg);
+ }
+ break;
+
+ /* All but the last buffer of data must have already been sent */
+ case DATA_DIR_TO_HOST:
+ if (fsg->data_size == 0)
+ ; // Nothing to send
+
+ /* If there's no residue, simply send the last buffer */
+ else if (fsg->residue == 0) {
+ bh->inreq->zero = 0;
+ start_transfer(fsg, fsg->bulk_in, bh->inreq,
+ &bh->inreq_busy, &bh->state);
+ fsg->next_buffhd_to_fill = bh->next;
+ }
+
+ /* There is a residue. For CB and CBI, simply mark the end
+ * of the data with a short packet. However, if we are
+ * allowed to stall, there was no data at all (residue ==
+ * data_size), and the command failed (invalid LUN or
+ * sense data is set), then halt the bulk-in endpoint
+ * instead. */
+ else if (!transport_is_bbb()) {
+ if (mod_data.can_stall &&
+ fsg->residue == fsg->data_size &&
+ (!fsg->curlun || fsg->curlun->sense_data != SS_NO_SENSE)) {
+ bh->state = BUF_STATE_EMPTY;
+ rc = halt_bulk_in_endpoint(fsg);
+ } else {
+ bh->inreq->zero = 1;
+ start_transfer(fsg, fsg->bulk_in, bh->inreq,
+ &bh->inreq_busy, &bh->state);
+ fsg->next_buffhd_to_fill = bh->next;
+ }
+ }
+
+ /*
+ * For Bulk-only, mark the end of the data with a short
+ * packet. If we are allowed to stall, halt the bulk-in
+ * endpoint. (Note: This violates the Bulk-Only Transport
+ * specification, which requires us to pad the data if we
+ * don't halt the endpoint. Presumably nobody will mind.)
+ */
+ else {
+ bh->inreq->zero = 1;
+ start_transfer(fsg, fsg->bulk_in, bh->inreq,
+ &bh->inreq_busy, &bh->state);
+ fsg->next_buffhd_to_fill = bh->next;
+ if (mod_data.can_stall)
+ rc = halt_bulk_in_endpoint(fsg);
+ }
+ break;
+
+ /* We have processed all we want from the data the host has sent.
+ * There may still be outstanding bulk-out requests. */
+ case DATA_DIR_FROM_HOST:
+ if (fsg->residue == 0)
+ ; // Nothing to receive
+
+ /* Did the host stop sending unexpectedly early? */
+ else if (fsg->short_packet_received) {
+ raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT);
+ rc = -EINTR;
+ }
+
+ /* We haven't processed all the incoming data. Even though
+ * we may be allowed to stall, doing so would cause a race.
+ * The controller may already have ACK'ed all the remaining
+ * bulk-out packets, in which case the host wouldn't see a
+ * STALL. Not realizing the endpoint was halted, it wouldn't
+ * clear the halt -- leading to problems later on. */
+#if 0
+ else if (mod_data.can_stall) {
+ fsg_set_halt(fsg, fsg->bulk_out);
+ raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT);
+ rc = -EINTR;
+ }
+#endif
+
+ /* We can't stall. Read in the excess data and throw it
+ * all away. */
+ else
+ rc = throw_away_data(fsg);
+ break;
+ }
+ return rc;
+}
+
+
+static int send_status(struct fsg_dev *fsg)
+{
+ struct fsg_lun *curlun = fsg->curlun;
+ struct fsg_buffhd *bh;
+ int rc;
+ u8 status = USB_STATUS_PASS;
+ u32 sd, sdinfo = 0;
+
+ /* Wait for the next buffer to become available */
+ bh = fsg->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+
+ if (curlun) {
+ sd = curlun->sense_data;
+ sdinfo = curlun->sense_data_info;
+ } else if (fsg->bad_lun_okay)
+ sd = SS_NO_SENSE;
+ else
+ sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+
+ if (fsg->phase_error) {
+ DBG(fsg, "sending phase-error status\n");
+ status = USB_STATUS_PHASE_ERROR;
+ sd = SS_INVALID_COMMAND;
+ } else if (sd != SS_NO_SENSE) {
+ DBG(fsg, "sending command-failure status\n");
+ status = USB_STATUS_FAIL;
+ VDBG(fsg, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;"
+ " info x%x\n",
+ SK(sd), ASC(sd), ASCQ(sd), sdinfo);
+ }
+
+ if (transport_is_bbb()) {
+ struct bulk_cs_wrap *csw = bh->buf;
+
+ /* Store and send the Bulk-only CSW */
+ csw->Signature = cpu_to_le32(USB_BULK_CS_SIG);
+ csw->Tag = fsg->tag;
+ csw->Residue = cpu_to_le32(fsg->residue);
+ csw->Status = status;
+
+ bh->inreq->length = USB_BULK_CS_WRAP_LEN;
+ bh->inreq->zero = 0;
+ start_transfer(fsg, fsg->bulk_in, bh->inreq,
+ &bh->inreq_busy, &bh->state);
+
+ } else if (mod_data.transport_type == USB_PR_CB) {
+
+ /* Control-Bulk transport has no status phase! */
+ return 0;
+
+ } else { // USB_PR_CBI
+ struct interrupt_data *buf = bh->buf;
+
+ /* Store and send the Interrupt data. UFI sends the ASC
+ * and ASCQ bytes. Everything else sends a Type (which
+ * is always 0) and the status Value. */
+ if (mod_data.protocol_type == USB_SC_UFI) {
+ buf->bType = ASC(sd);
+ buf->bValue = ASCQ(sd);
+ } else {
+ buf->bType = 0;
+ buf->bValue = status;
+ }
+ fsg->intreq->length = CBI_INTERRUPT_DATA_LEN;
+
+ fsg->intr_buffhd = bh; // Point to the right buffhd
+ fsg->intreq->buf = bh->inreq->buf;
+ fsg->intreq->context = bh;
+ start_transfer(fsg, fsg->intr_in, fsg->intreq,
+ &fsg->intreq_busy, &bh->state);
+ }
+
+ fsg->next_buffhd_to_fill = bh->next;
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Check whether the command is properly formed and whether its data size
+ * and direction agree with the values we already have. */
+static int check_command(struct fsg_dev *fsg, int cmnd_size,
+ enum data_direction data_dir, unsigned int mask,
+ int needs_medium, const char *name)
+{
+ int i;
+ int lun = fsg->cmnd[1] >> 5;
+ static const char dirletter[4] = {'u', 'o', 'i', 'n'};
+ char hdlen[20];
+ struct fsg_lun *curlun;
+
+ /* Adjust the expected cmnd_size for protocol encapsulation padding.
+ * Transparent SCSI doesn't pad. */
+ if (protocol_is_scsi())
+ ;
+
+ /* There's some disagreement as to whether RBC pads commands or not.
+ * We'll play it safe and accept either form. */
+ else if (mod_data.protocol_type == USB_SC_RBC) {
+ if (fsg->cmnd_size == 12)
+ cmnd_size = 12;
+
+ /* All the other protocols pad to 12 bytes */
+ } else
+ cmnd_size = 12;
+
+ hdlen[0] = 0;
+ if (fsg->data_dir != DATA_DIR_UNKNOWN)
+ sprintf(hdlen, ", H%c=%u", dirletter[(int) fsg->data_dir],
+ fsg->data_size);
+ VDBG(fsg, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n",
+ name, cmnd_size, dirletter[(int) data_dir],
+ fsg->data_size_from_cmnd, fsg->cmnd_size, hdlen);
+
+ /* We can't reply at all until we know the correct data direction
+ * and size. */
+ if (fsg->data_size_from_cmnd == 0)
+ data_dir = DATA_DIR_NONE;
+ if (fsg->data_dir == DATA_DIR_UNKNOWN) { // CB or CBI
+ fsg->data_dir = data_dir;
+ fsg->data_size = fsg->data_size_from_cmnd;
+
+ } else { // Bulk-only
+ if (fsg->data_size < fsg->data_size_from_cmnd) {
+
+ /* Host data size < Device data size is a phase error.
+ * Carry out the command, but only transfer as much
+ * as we are allowed. */
+ fsg->data_size_from_cmnd = fsg->data_size;
+ fsg->phase_error = 1;
+ }
+ }
+ fsg->residue = fsg->usb_amount_left = fsg->data_size;
+
+ /* Conflicting data directions is a phase error */
+ if (fsg->data_dir != data_dir && fsg->data_size_from_cmnd > 0) {
+ fsg->phase_error = 1;
+ return -EINVAL;
+ }
+
+ /* Verify the length of the command itself */
+ if (cmnd_size != fsg->cmnd_size) {
+
+ /* Special case workaround: There are plenty of buggy SCSI
+ * implementations. Many have issues with cbw->Length
+ * field passing a wrong command size. For those cases we
+ * always try to work around the problem by using the length
+ * sent by the host side provided it is at least as large
+ * as the correct command length.
+ * Examples of such cases would be MS-Windows, which issues
+ * REQUEST SENSE with cbw->Length == 12 where it should
+ * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and
+ * REQUEST SENSE with cbw->Length == 10 where it should
+ * be 6 as well.
+ */
+ if (cmnd_size <= fsg->cmnd_size) {
+ DBG(fsg, "%s is buggy! Expected length %d "
+ "but we got %d\n", name,
+ cmnd_size, fsg->cmnd_size);
+ cmnd_size = fsg->cmnd_size;
+ } else {
+ fsg->phase_error = 1;
+ return -EINVAL;
+ }
+ }
+
+ /* Check that the LUN values are consistent */
+ if (transport_is_bbb()) {
+ if (fsg->lun != lun)
+ DBG(fsg, "using LUN %d from CBW, "
+ "not LUN %d from CDB\n",
+ fsg->lun, lun);
+ } else
+ fsg->lun = lun; // Use LUN from the command
+
+ /* Check the LUN */
+ if (fsg->lun < fsg->nluns) {
+ fsg->curlun = curlun = &fsg->luns[fsg->lun];
+ if (fsg->cmnd[0] != REQUEST_SENSE) {
+ curlun->sense_data = SS_NO_SENSE;
+ curlun->sense_data_info = 0;
+ curlun->info_valid = 0;
+ }
+ } else {
+ fsg->curlun = curlun = NULL;
+ fsg->bad_lun_okay = 0;
+
+ /* INQUIRY and REQUEST SENSE commands are explicitly allowed
+ * to use unsupported LUNs; all others may not. */
+ if (fsg->cmnd[0] != INQUIRY &&
+ fsg->cmnd[0] != REQUEST_SENSE) {
+ DBG(fsg, "unsupported LUN %d\n", fsg->lun);
+ return -EINVAL;
+ }
+ }
+
+ /* If a unit attention condition exists, only INQUIRY and
+ * REQUEST SENSE commands are allowed; anything else must fail. */
+ if (curlun && curlun->unit_attention_data != SS_NO_SENSE &&
+ fsg->cmnd[0] != INQUIRY &&
+ fsg->cmnd[0] != REQUEST_SENSE) {
+ curlun->sense_data = curlun->unit_attention_data;
+ curlun->unit_attention_data = SS_NO_SENSE;
+ return -EINVAL;
+ }
+
+ /* Check that only command bytes listed in the mask are non-zero */
+ fsg->cmnd[1] &= 0x1f; // Mask away the LUN
+ for (i = 1; i < cmnd_size; ++i) {
+ if (fsg->cmnd[i] && !(mask & (1 << i))) {
+ if (curlun)
+ curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+ return -EINVAL;
+ }
+ }
+
+ /* If the medium isn't mounted and the command needs to access
+ * it, return an error. */
+ if (curlun && !fsg_lun_is_open(curlun) && needs_medium) {
+ curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int do_scsi_command(struct fsg_dev *fsg)
+{
+ struct fsg_buffhd *bh;
+ int rc;
+ int reply = -EINVAL;
+ int i;
+ static char unknown[16];
+
+ dump_cdb(fsg);
+
+ /* Wait for the next buffer to become available for data or status */
+ bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+ fsg->phase_error = 0;
+ fsg->short_packet_received = 0;
+
+ down_read(&fsg->filesem); // We're using the backing file
+ switch (fsg->cmnd[0]) {
+
+ case INQUIRY:
+ fsg->data_size_from_cmnd = fsg->cmnd[4];
+ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+ (1<<4), 0,
+ "INQUIRY")) == 0)
+ reply = do_inquiry(fsg, bh);
+ break;
+
+ case MODE_SELECT:
+ fsg->data_size_from_cmnd = fsg->cmnd[4];
+ if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST,
+ (1<<1) | (1<<4), 0,
+ "MODE SELECT(6)")) == 0)
+ reply = do_mode_select(fsg, bh);
+ break;
+
+ case MODE_SELECT_10:
+ fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+ if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST,
+ (1<<1) | (3<<7), 0,
+ "MODE SELECT(10)")) == 0)
+ reply = do_mode_select(fsg, bh);
+ break;
+
+ case MODE_SENSE:
+ fsg->data_size_from_cmnd = fsg->cmnd[4];
+ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+ (1<<1) | (1<<2) | (1<<4), 0,
+ "MODE SENSE(6)")) == 0)
+ reply = do_mode_sense(fsg, bh);
+ break;
+
+ case MODE_SENSE_10:
+ fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+ (1<<1) | (1<<2) | (3<<7), 0,
+ "MODE SENSE(10)")) == 0)
+ reply = do_mode_sense(fsg, bh);
+ break;
+
+ case ALLOW_MEDIUM_REMOVAL:
+ fsg->data_size_from_cmnd = 0;
+ if ((reply = check_command(fsg, 6, DATA_DIR_NONE,
+ (1<<4), 0,
+ "PREVENT-ALLOW MEDIUM REMOVAL")) == 0)
+ reply = do_prevent_allow(fsg);
+ break;
+
+ case READ_6:
+ i = fsg->cmnd[4];
+ fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9;
+ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+ (7<<1) | (1<<4), 1,
+ "READ(6)")) == 0)
+ reply = do_read(fsg);
+ break;
+
+ case READ_10:
+ fsg->data_size_from_cmnd =
+ get_unaligned_be16(&fsg->cmnd[7]) << 9;
+ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+ (1<<1) | (0xf<<2) | (3<<7), 1,
+ "READ(10)")) == 0)
+ reply = do_read(fsg);
+ break;
+
+ case READ_12:
+ fsg->data_size_from_cmnd =
+ get_unaligned_be32(&fsg->cmnd[6]) << 9;
+ if ((reply = check_command(fsg, 12, DATA_DIR_TO_HOST,
+ (1<<1) | (0xf<<2) | (0xf<<6), 1,
+ "READ(12)")) == 0)
+ reply = do_read(fsg);
+ break;
+
+ case READ_CAPACITY:
+ fsg->data_size_from_cmnd = 8;
+ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+ (0xf<<2) | (1<<8), 1,
+ "READ CAPACITY")) == 0)
+ reply = do_read_capacity(fsg, bh);
+ break;
+
+ case READ_HEADER:
+ if (!mod_data.cdrom)
+ goto unknown_cmnd;
+ fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+ (3<<7) | (0x1f<<1), 1,
+ "READ HEADER")) == 0)
+ reply = do_read_header(fsg, bh);
+ break;
+
+ case READ_TOC:
+ if (!mod_data.cdrom)
+ goto unknown_cmnd;
+ fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+ (7<<6) | (1<<1), 1,
+ "READ TOC")) == 0)
+ reply = do_read_toc(fsg, bh);
+ break;
+
+ case READ_FORMAT_CAPACITIES:
+ fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+ if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+ (3<<7), 1,
+ "READ FORMAT CAPACITIES")) == 0)
+ reply = do_read_format_capacities(fsg, bh);
+ break;
+
+ case REQUEST_SENSE:
+ fsg->data_size_from_cmnd = fsg->cmnd[4];
+ if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+ (1<<4), 0,
+ "REQUEST SENSE")) == 0)
+ reply = do_request_sense(fsg, bh);
+ break;
+
+ case START_STOP:
+ fsg->data_size_from_cmnd = 0;
+ if ((reply = check_command(fsg, 6, DATA_DIR_NONE,
+ (1<<1) | (1<<4), 0,
+ "START-STOP UNIT")) == 0)
+ reply = do_start_stop(fsg);
+ break;
+
+ case SYNCHRONIZE_CACHE:
+ fsg->data_size_from_cmnd = 0;
+ if ((reply = check_command(fsg, 10, DATA_DIR_NONE,
+ (0xf<<2) | (3<<7), 1,
+ "SYNCHRONIZE CACHE")) == 0)
+ reply = do_synchronize_cache(fsg);
+ break;
+
+ case TEST_UNIT_READY:
+ fsg->data_size_from_cmnd = 0;
+ reply = check_command(fsg, 6, DATA_DIR_NONE,
+ 0, 1,
+ "TEST UNIT READY");
+ break;
+
+ /* Although optional, this command is used by MS-Windows. We
+ * support a minimal version: BytChk must be 0. */
+ case VERIFY:
+ fsg->data_size_from_cmnd = 0;
+ if ((reply = check_command(fsg, 10, DATA_DIR_NONE,
+ (1<<1) | (0xf<<2) | (3<<7), 1,
+ "VERIFY")) == 0)
+ reply = do_verify(fsg);
+ break;
+
+ case WRITE_6:
+ i = fsg->cmnd[4];
+ fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9;
+ if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST,
+ (7<<1) | (1<<4), 1,
+ "WRITE(6)")) == 0)
+ reply = do_write(fsg);
+ break;
+
+ case WRITE_10:
+ fsg->data_size_from_cmnd =
+ get_unaligned_be16(&fsg->cmnd[7]) << 9;
+ if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST,
+ (1<<1) | (0xf<<2) | (3<<7), 1,
+ "WRITE(10)")) == 0)
+ reply = do_write(fsg);
+ break;
+
+ case WRITE_12:
+ fsg->data_size_from_cmnd =
+ get_unaligned_be32(&fsg->cmnd[6]) << 9;
+ if ((reply = check_command(fsg, 12, DATA_DIR_FROM_HOST,
+ (1<<1) | (0xf<<2) | (0xf<<6), 1,
+ "WRITE(12)")) == 0)
+ reply = do_write(fsg);
+ break;
+
+ /* Some mandatory commands that we recognize but don't implement.
+ * They don't mean much in this setting. It's left as an exercise
+ * for anyone interested to implement RESERVE and RELEASE in terms
+ * of Posix locks. */
+ case FORMAT_UNIT:
+ case RELEASE:
+ case RESERVE:
+ case SEND_DIAGNOSTIC:
+ // Fall through
+
+ default:
+ unknown_cmnd:
+ fsg->data_size_from_cmnd = 0;
+ sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]);
+ if ((reply = check_command(fsg, fsg->cmnd_size,
+ DATA_DIR_UNKNOWN, 0xff, 0, unknown)) == 0) {
+ fsg->curlun->sense_data = SS_INVALID_COMMAND;
+ reply = -EINVAL;
+ }
+ break;
+ }
+ up_read(&fsg->filesem);
+
+ if (reply == -EINTR || signal_pending(current))
+ return -EINTR;
+
+ /* Set up the single reply buffer for finish_reply() */
+ if (reply == -EINVAL)
+ reply = 0; // Error reply length
+ if (reply >= 0 && fsg->data_dir == DATA_DIR_TO_HOST) {
+ reply = min((u32) reply, fsg->data_size_from_cmnd);
+ bh->inreq->length = reply;
+ bh->state = BUF_STATE_FULL;
+ fsg->residue -= reply;
+ } // Otherwise it's already set
+
+ return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+ struct usb_request *req = bh->outreq;
+ struct fsg_bulk_cb_wrap *cbw = req->buf;
+
+ /* Was this a real packet? Should it be ignored? */
+ if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags))
+ return -EINVAL;
+
+ /* Is the CBW valid? */
+ if (req->actual != USB_BULK_CB_WRAP_LEN ||
+ cbw->Signature != cpu_to_le32(
+ USB_BULK_CB_SIG)) {
+ DBG(fsg, "invalid CBW: len %u sig 0x%x\n",
+ req->actual,
+ le32_to_cpu(cbw->Signature));
+
+ /* The Bulk-only spec says we MUST stall the IN endpoint
+ * (6.6.1), so it's unavoidable. It also says we must
+ * retain this state until the next reset, but there's
+ * no way to tell the controller driver it should ignore
+ * Clear-Feature(HALT) requests.
+ *
+ * We aren't required to halt the OUT endpoint; instead
+ * we can simply accept and discard any data received
+ * until the next reset. */
+ wedge_bulk_in_endpoint(fsg);
+ set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+ return -EINVAL;
+ }
+
+ /* Is the CBW meaningful? */
+ if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG ||
+ cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) {
+ DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, "
+ "cmdlen %u\n",
+ cbw->Lun, cbw->Flags, cbw->Length);
+
+ /* We can do anything we want here, so let's stall the
+ * bulk pipes if we are allowed to. */
+ if (mod_data.can_stall) {
+ fsg_set_halt(fsg, fsg->bulk_out);
+ halt_bulk_in_endpoint(fsg);
+ }
+ return -EINVAL;
+ }
+
+ /* Save the command for later */
+ fsg->cmnd_size = cbw->Length;
+ memcpy(fsg->cmnd, cbw->CDB, fsg->cmnd_size);
+ if (cbw->Flags & USB_BULK_IN_FLAG)
+ fsg->data_dir = DATA_DIR_TO_HOST;
+ else
+ fsg->data_dir = DATA_DIR_FROM_HOST;
+ fsg->data_size = le32_to_cpu(cbw->DataTransferLength);
+ if (fsg->data_size == 0)
+ fsg->data_dir = DATA_DIR_NONE;
+ fsg->lun = cbw->Lun;
+ fsg->tag = cbw->Tag;
+ return 0;
+}
+
+
+static int get_next_command(struct fsg_dev *fsg)
+{
+ struct fsg_buffhd *bh;
+ int rc = 0;
+
+ if (transport_is_bbb()) {
+
+ /* Wait for the next buffer to become available */
+ bh = fsg->next_buffhd_to_fill;
+ while (bh->state != BUF_STATE_EMPTY) {
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+
+ /* Queue a request to read a Bulk-only CBW */
+ set_bulk_out_req_length(fsg, bh, USB_BULK_CB_WRAP_LEN);
+ bh->outreq->short_not_ok = 1;
+ start_transfer(fsg, fsg->bulk_out, bh->outreq,
+ &bh->outreq_busy, &bh->state);
+
+ /* We will drain the buffer in software, which means we
+ * can reuse it for the next filling. No need to advance
+ * next_buffhd_to_fill. */
+
+ /* Wait for the CBW to arrive */
+ while (bh->state != BUF_STATE_FULL) {
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+ smp_rmb();
+ rc = received_cbw(fsg, bh);
+ bh->state = BUF_STATE_EMPTY;
+
+ } else { // USB_PR_CB or USB_PR_CBI
+
+ /* Wait for the next command to arrive */
+ while (fsg->cbbuf_cmnd_size == 0) {
+ rc = sleep_thread(fsg);
+ if (rc)
+ return rc;
+ }
+
+ /* Is the previous status interrupt request still busy?
+ * The host is allowed to skip reading the status,
+ * so we must cancel it. */
+ if (fsg->intreq_busy)
+ usb_ep_dequeue(fsg->intr_in, fsg->intreq);
+
+ /* Copy the command and mark the buffer empty */
+ fsg->data_dir = DATA_DIR_UNKNOWN;
+ spin_lock_irq(&fsg->lock);
+ fsg->cmnd_size = fsg->cbbuf_cmnd_size;
+ memcpy(fsg->cmnd, fsg->cbbuf_cmnd, fsg->cmnd_size);
+ fsg->cbbuf_cmnd_size = 0;
+ spin_unlock_irq(&fsg->lock);
+ }
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int enable_endpoint(struct fsg_dev *fsg, struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *d)
+{
+ int rc;
+
+ ep->driver_data = fsg;
+ rc = usb_ep_enable(ep, d);
+ if (rc)
+ ERROR(fsg, "can't enable %s, result %d\n", ep->name, rc);
+ return rc;
+}
+
+static int alloc_request(struct fsg_dev *fsg, struct usb_ep *ep,
+ struct usb_request **preq)
+{
+ *preq = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (*preq)
+ return 0;
+ ERROR(fsg, "can't allocate request for %s\n", ep->name);
+ return -ENOMEM;
+}
+
+/*
+ * Reset interface setting and re-init endpoint state (toggle etc).
+ * Call with altsetting < 0 to disable the interface. The only other
+ * available altsetting is 0, which enables the interface.
+ */
+static int do_set_interface(struct fsg_dev *fsg, int altsetting)
+{
+ int rc = 0;
+ int i;
+ const struct usb_endpoint_descriptor *d;
+
+ if (fsg->running)
+ DBG(fsg, "reset interface\n");
+
+reset:
+ /* Deallocate the requests */
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ struct fsg_buffhd *bh = &fsg->buffhds[i];
+
+ if (bh->inreq) {
+ usb_ep_free_request(fsg->bulk_in, bh->inreq);
+ bh->inreq = NULL;
+ }
+ if (bh->outreq) {
+ usb_ep_free_request(fsg->bulk_out, bh->outreq);
+ bh->outreq = NULL;
+ }
+ }
+ if (fsg->intreq) {
+ usb_ep_free_request(fsg->intr_in, fsg->intreq);
+ fsg->intreq = NULL;
+ }
+
+ /* Disable the endpoints */
+ if (fsg->bulk_in_enabled) {
+ usb_ep_disable(fsg->bulk_in);
+ fsg->bulk_in_enabled = 0;
+ }
+ if (fsg->bulk_out_enabled) {
+ usb_ep_disable(fsg->bulk_out);
+ fsg->bulk_out_enabled = 0;
+ }
+ if (fsg->intr_in_enabled) {
+ usb_ep_disable(fsg->intr_in);
+ fsg->intr_in_enabled = 0;
+ }
+
+ fsg->running = 0;
+ if (altsetting < 0 || rc != 0)
+ return rc;
+
+ DBG(fsg, "set interface %d\n", altsetting);
+
+ /* Enable the endpoints */
+ d = fsg_ep_desc(fsg->gadget,
+ &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc);
+ if ((rc = enable_endpoint(fsg, fsg->bulk_in, d)) != 0)
+ goto reset;
+ fsg->bulk_in_enabled = 1;
+
+ d = fsg_ep_desc(fsg->gadget,
+ &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc);
+ if ((rc = enable_endpoint(fsg, fsg->bulk_out, d)) != 0)
+ goto reset;
+ fsg->bulk_out_enabled = 1;
+ fsg->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize);
+ clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+
+ if (transport_is_cbi()) {
+ d = fsg_ep_desc(fsg->gadget,
+ &fsg_fs_intr_in_desc, &fsg_hs_intr_in_desc);
+ if ((rc = enable_endpoint(fsg, fsg->intr_in, d)) != 0)
+ goto reset;
+ fsg->intr_in_enabled = 1;
+ }
+
+ /* Allocate the requests */
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ struct fsg_buffhd *bh = &fsg->buffhds[i];
+
+ if ((rc = alloc_request(fsg, fsg->bulk_in, &bh->inreq)) != 0)
+ goto reset;
+ if ((rc = alloc_request(fsg, fsg->bulk_out, &bh->outreq)) != 0)
+ goto reset;
+ bh->inreq->buf = bh->outreq->buf = bh->buf;
+ bh->inreq->context = bh->outreq->context = bh;
+ bh->inreq->complete = bulk_in_complete;
+ bh->outreq->complete = bulk_out_complete;
+ }
+ if (transport_is_cbi()) {
+ if ((rc = alloc_request(fsg, fsg->intr_in, &fsg->intreq)) != 0)
+ goto reset;
+ fsg->intreq->complete = intr_in_complete;
+ }
+
+ fsg->running = 1;
+ for (i = 0; i < fsg->nluns; ++i)
+ fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED;
+ return rc;
+}
+
+
+/*
+ * Change our operational configuration. This code must agree with the code
+ * that returns config descriptors, and with interface altsetting code.
+ *
+ * It's also responsible for power management interactions. Some
+ * configurations might not work with our current power sources.
+ * For now we just assume the gadget is always self-powered.
+ */
+static int do_set_config(struct fsg_dev *fsg, u8 new_config)
+{
+ int rc = 0;
+
+ /* Disable the single interface */
+ if (fsg->config != 0) {
+ DBG(fsg, "reset config\n");
+ fsg->config = 0;
+ rc = do_set_interface(fsg, -1);
+ }
+
+ /* Enable the interface */
+ if (new_config != 0) {
+ fsg->config = new_config;
+ if ((rc = do_set_interface(fsg, 0)) != 0)
+ fsg->config = 0; // Reset on errors
+ else {
+ char *speed;
+
+ switch (fsg->gadget->speed) {
+ case USB_SPEED_LOW: speed = "low"; break;
+ case USB_SPEED_FULL: speed = "full"; break;
+ case USB_SPEED_HIGH: speed = "high"; break;
+ default: speed = "?"; break;
+ }
+ INFO(fsg, "%s speed config #%d\n", speed, fsg->config);
+ }
+ }
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void handle_exception(struct fsg_dev *fsg)
+{
+ siginfo_t info;
+ int sig;
+ int i;
+ int num_active;
+ struct fsg_buffhd *bh;
+ enum fsg_state old_state;
+ u8 new_config;
+ struct fsg_lun *curlun;
+ unsigned int exception_req_tag;
+ int rc;
+
+ /* Clear the existing signals. Anything but SIGUSR1 is converted
+ * into a high-priority EXIT exception. */
+ for (;;) {
+ sig = dequeue_signal_lock(current, &current->blocked, &info);
+ if (!sig)
+ break;
+ if (sig != SIGUSR1) {
+ if (fsg->state < FSG_STATE_EXIT)
+ DBG(fsg, "Main thread exiting on signal\n");
+ raise_exception(fsg, FSG_STATE_EXIT);
+ }
+ }
+
+ /* Cancel all the pending transfers */
+ if (fsg->intreq_busy)
+ usb_ep_dequeue(fsg->intr_in, fsg->intreq);
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ bh = &fsg->buffhds[i];
+ if (bh->inreq_busy)
+ usb_ep_dequeue(fsg->bulk_in, bh->inreq);
+ if (bh->outreq_busy)
+ usb_ep_dequeue(fsg->bulk_out, bh->outreq);
+ }
+
+ /* Wait until everything is idle */
+ for (;;) {
+ num_active = fsg->intreq_busy;
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ bh = &fsg->buffhds[i];
+ num_active += bh->inreq_busy + bh->outreq_busy;
+ }
+ if (num_active == 0)
+ break;
+ if (sleep_thread(fsg))
+ return;
+ }
+
+ /* Clear out the controller's fifos */
+ if (fsg->bulk_in_enabled)
+ usb_ep_fifo_flush(fsg->bulk_in);
+ if (fsg->bulk_out_enabled)
+ usb_ep_fifo_flush(fsg->bulk_out);
+ if (fsg->intr_in_enabled)
+ usb_ep_fifo_flush(fsg->intr_in);
+
+ /* Reset the I/O buffer states and pointers, the SCSI
+ * state, and the exception. Then invoke the handler. */
+ spin_lock_irq(&fsg->lock);
+
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ bh = &fsg->buffhds[i];
+ bh->state = BUF_STATE_EMPTY;
+ }
+ fsg->next_buffhd_to_fill = fsg->next_buffhd_to_drain =
+ &fsg->buffhds[0];
+
+ exception_req_tag = fsg->exception_req_tag;
+ new_config = fsg->new_config;
+ old_state = fsg->state;
+
+ if (old_state == FSG_STATE_ABORT_BULK_OUT)
+ fsg->state = FSG_STATE_STATUS_PHASE;
+ else {
+ for (i = 0; i < fsg->nluns; ++i) {
+ curlun = &fsg->luns[i];
+ curlun->prevent_medium_removal = 0;
+ curlun->sense_data = curlun->unit_attention_data =
+ SS_NO_SENSE;
+ curlun->sense_data_info = 0;
+ curlun->info_valid = 0;
+ }
+ fsg->state = FSG_STATE_IDLE;
+ }
+ spin_unlock_irq(&fsg->lock);
+
+ /* Carry out any extra actions required for the exception */
+ switch (old_state) {
+ default:
+ break;
+
+ case FSG_STATE_ABORT_BULK_OUT:
+ send_status(fsg);
+ spin_lock_irq(&fsg->lock);
+ if (fsg->state == FSG_STATE_STATUS_PHASE)
+ fsg->state = FSG_STATE_IDLE;
+ spin_unlock_irq(&fsg->lock);
+ break;
+
+ case FSG_STATE_RESET:
+ /* In case we were forced against our will to halt a
+ * bulk endpoint, clear the halt now. (The SuperH UDC
+ * requires this.) */
+ if (test_and_clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags))
+ usb_ep_clear_halt(fsg->bulk_in);
+
+ if (transport_is_bbb()) {
+ if (fsg->ep0_req_tag == exception_req_tag)
+ ep0_queue(fsg); // Complete the status stage
+
+ } else if (transport_is_cbi())
+ send_status(fsg); // Status by interrupt pipe
+
+ /* Technically this should go here, but it would only be
+ * a waste of time. Ditto for the INTERFACE_CHANGE and
+ * CONFIG_CHANGE cases. */
+ // for (i = 0; i < fsg->nluns; ++i)
+ // fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED;
+ break;
+
+ case FSG_STATE_INTERFACE_CHANGE:
+ rc = do_set_interface(fsg, 0);
+ if (fsg->ep0_req_tag != exception_req_tag)
+ break;
+ if (rc != 0) // STALL on errors
+ fsg_set_halt(fsg, fsg->ep0);
+ else // Complete the status stage
+ ep0_queue(fsg);
+ break;
+
+ case FSG_STATE_CONFIG_CHANGE:
+ rc = do_set_config(fsg, new_config);
+ if (fsg->ep0_req_tag != exception_req_tag)
+ break;
+ if (rc != 0) // STALL on errors
+ fsg_set_halt(fsg, fsg->ep0);
+ else // Complete the status stage
+ ep0_queue(fsg);
+ break;
+
+ case FSG_STATE_DISCONNECT:
+ for (i = 0; i < fsg->nluns; ++i)
+ fsg_lun_fsync_sub(fsg->luns + i);
+ do_set_config(fsg, 0); // Unconfigured state
+ break;
+
+ case FSG_STATE_EXIT:
+ case FSG_STATE_TERMINATED:
+ do_set_config(fsg, 0); // Free resources
+ spin_lock_irq(&fsg->lock);
+ fsg->state = FSG_STATE_TERMINATED; // Stop the thread
+ spin_unlock_irq(&fsg->lock);
+ break;
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_main_thread(void *fsg_)
+{
+ struct fsg_dev *fsg = fsg_;
+
+ /* Allow the thread to be killed by a signal, but set the signal mask
+ * to block everything but INT, TERM, KILL, and USR1. */
+ allow_signal(SIGINT);
+ allow_signal(SIGTERM);
+ allow_signal(SIGKILL);
+ allow_signal(SIGUSR1);
+
+ /* Allow the thread to be frozen */
+ set_freezable();
+
+ /* Arrange for userspace references to be interpreted as kernel
+ * pointers. That way we can pass a kernel pointer to a routine
+ * that expects a __user pointer and it will work okay. */
+ set_fs(get_ds());
+
+ /* The main loop */
+ while (fsg->state != FSG_STATE_TERMINATED) {
+ if (exception_in_progress(fsg) || signal_pending(current)) {
+ handle_exception(fsg);
+ continue;
+ }
+
+ if (!fsg->running) {
+ sleep_thread(fsg);
+ continue;
+ }
+
+ if (get_next_command(fsg))
+ continue;
+
+ spin_lock_irq(&fsg->lock);
+ if (!exception_in_progress(fsg))
+ fsg->state = FSG_STATE_DATA_PHASE;
+ spin_unlock_irq(&fsg->lock);
+
+ if (do_scsi_command(fsg) || finish_reply(fsg))
+ continue;
+
+ spin_lock_irq(&fsg->lock);
+ if (!exception_in_progress(fsg))
+ fsg->state = FSG_STATE_STATUS_PHASE;
+ spin_unlock_irq(&fsg->lock);
+
+ if (send_status(fsg))
+ continue;
+
+ spin_lock_irq(&fsg->lock);
+ if (!exception_in_progress(fsg))
+ fsg->state = FSG_STATE_IDLE;
+ spin_unlock_irq(&fsg->lock);
+ }
+
+ spin_lock_irq(&fsg->lock);
+ fsg->thread_task = NULL;
+ spin_unlock_irq(&fsg->lock);
+
+ /* If we are exiting because of a signal, unregister the
+ * gadget driver. */
+ if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags))
+ usb_gadget_unregister_driver(&fsg_driver);
+
+ /* Let the unbind and cleanup routines know the thread has exited */
+ complete_and_exit(&fsg->thread_notifier, 0);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/* The write permissions and store_xxx pointers are set in fsg_bind() */
+static DEVICE_ATTR(ro, 0444, fsg_show_ro, NULL);
+static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, NULL);
+static DEVICE_ATTR(file, 0444, fsg_show_file, NULL);
+
+
+/*-------------------------------------------------------------------------*/
+
+static void fsg_release(struct kref *ref)
+{
+ struct fsg_dev *fsg = container_of(ref, struct fsg_dev, ref);
+
+ kfree(fsg->luns);
+ kfree(fsg);
+}
+
+static void lun_release(struct device *dev)
+{
+ struct rw_semaphore *filesem = dev_get_drvdata(dev);
+ struct fsg_dev *fsg =
+ container_of(filesem, struct fsg_dev, filesem);
+
+ kref_put(&fsg->ref, fsg_release);
+}
+
+static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget)
+{
+ struct fsg_dev *fsg = get_gadget_data(gadget);
+ int i;
+ struct fsg_lun *curlun;
+ struct usb_request *req = fsg->ep0req;
+
+ DBG(fsg, "unbind\n");
+ clear_bit(REGISTERED, &fsg->atomic_bitflags);
+
+ /* Unregister the sysfs attribute files and the LUNs */
+ for (i = 0; i < fsg->nluns; ++i) {
+ curlun = &fsg->luns[i];
+ if (curlun->registered) {
+ device_remove_file(&curlun->dev, &dev_attr_nofua);
+ device_remove_file(&curlun->dev, &dev_attr_ro);
+ device_remove_file(&curlun->dev, &dev_attr_file);
+ fsg_lun_close(curlun);
+ device_unregister(&curlun->dev);
+ curlun->registered = 0;
+ }
+ }
+
+ /* If the thread isn't already dead, tell it to exit now */
+ if (fsg->state != FSG_STATE_TERMINATED) {
+ raise_exception(fsg, FSG_STATE_EXIT);
+ wait_for_completion(&fsg->thread_notifier);
+
+ /* The cleanup routine waits for this completion also */
+ complete(&fsg->thread_notifier);
+ }
+
+ /* Free the data buffers */
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i)
+ kfree(fsg->buffhds[i].buf);
+
+ /* Free the request and buffer for endpoint 0 */
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(fsg->ep0, req);
+ }
+
+ set_gadget_data(gadget, NULL);
+}
+
+
+static int __init check_parameters(struct fsg_dev *fsg)
+{
+ int prot;
+ int gcnum;
+
+ /* Store the default values */
+ mod_data.transport_type = USB_PR_BULK;
+ mod_data.transport_name = "Bulk-only";
+ mod_data.protocol_type = USB_SC_SCSI;
+ mod_data.protocol_name = "Transparent SCSI";
+
+ /* Some peripheral controllers are known not to be able to
+ * halt bulk endpoints correctly. If one of them is present,
+ * disable stalls.
+ */
+ if (gadget_is_at91(fsg->gadget))
+ mod_data.can_stall = 0;
+
+ if (mod_data.release == 0xffff) { // Parameter wasn't set
+ gcnum = usb_gadget_controller_number(fsg->gadget);
+ if (gcnum >= 0)
+ mod_data.release = 0x0300 + gcnum;
+ else {
+ WARNING(fsg, "controller '%s' not recognized\n",
+ fsg->gadget->name);
+ mod_data.release = 0x0399;
+ }
+ }
+
+ prot = simple_strtol(mod_data.protocol_parm, NULL, 0);
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+ if (strnicmp(mod_data.transport_parm, "BBB", 10) == 0) {
+ ; // Use default setting
+ } else if (strnicmp(mod_data.transport_parm, "CB", 10) == 0) {
+ mod_data.transport_type = USB_PR_CB;
+ mod_data.transport_name = "Control-Bulk";
+ } else if (strnicmp(mod_data.transport_parm, "CBI", 10) == 0) {
+ mod_data.transport_type = USB_PR_CBI;
+ mod_data.transport_name = "Control-Bulk-Interrupt";
+ } else {
+ ERROR(fsg, "invalid transport: %s\n", mod_data.transport_parm);
+ return -EINVAL;
+ }
+
+ if (strnicmp(mod_data.protocol_parm, "SCSI", 10) == 0 ||
+ prot == USB_SC_SCSI) {
+ ; // Use default setting
+ } else if (strnicmp(mod_data.protocol_parm, "RBC", 10) == 0 ||
+ prot == USB_SC_RBC) {
+ mod_data.protocol_type = USB_SC_RBC;
+ mod_data.protocol_name = "RBC";
+ } else if (strnicmp(mod_data.protocol_parm, "8020", 4) == 0 ||
+ strnicmp(mod_data.protocol_parm, "ATAPI", 10) == 0 ||
+ prot == USB_SC_8020) {
+ mod_data.protocol_type = USB_SC_8020;
+ mod_data.protocol_name = "8020i (ATAPI)";
+ } else if (strnicmp(mod_data.protocol_parm, "QIC", 3) == 0 ||
+ prot == USB_SC_QIC) {
+ mod_data.protocol_type = USB_SC_QIC;
+ mod_data.protocol_name = "QIC-157";
+ } else if (strnicmp(mod_data.protocol_parm, "UFI", 10) == 0 ||
+ prot == USB_SC_UFI) {
+ mod_data.protocol_type = USB_SC_UFI;
+ mod_data.protocol_name = "UFI";
+ } else if (strnicmp(mod_data.protocol_parm, "8070", 4) == 0 ||
+ prot == USB_SC_8070) {
+ mod_data.protocol_type = USB_SC_8070;
+ mod_data.protocol_name = "8070i";
+ } else {
+ ERROR(fsg, "invalid protocol: %s\n", mod_data.protocol_parm);
+ return -EINVAL;
+ }
+
+ mod_data.buflen &= PAGE_CACHE_MASK;
+ if (mod_data.buflen <= 0) {
+ ERROR(fsg, "invalid buflen\n");
+ return -ETOOSMALL;
+ }
+
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+ /* Serial string handling.
+ * On a real device, the serial string would be loaded
+ * from permanent storage. */
+ if (mod_data.serial) {
+ const char *ch;
+ unsigned len = 0;
+
+ /* Sanity check :
+ * The CB[I] specification limits the serial string to
+ * 12 uppercase hexadecimal characters.
+ * BBB need at least 12 uppercase hexadecimal characters,
+ * with a maximum of 126. */
+ for (ch = mod_data.serial; *ch; ++ch) {
+ ++len;
+ if ((*ch < '0' || *ch > '9') &&
+ (*ch < 'A' || *ch > 'F')) { /* not uppercase hex */
+ WARNING(fsg,
+ "Invalid serial string character: %c\n",
+ *ch);
+ goto no_serial;
+ }
+ }
+ if (len > 126 ||
+ (mod_data.transport_type == USB_PR_BULK && len < 12) ||
+ (mod_data.transport_type != USB_PR_BULK && len > 12)) {
+ WARNING(fsg, "Invalid serial string length!\n");
+ goto no_serial;
+ }
+ fsg_strings[FSG_STRING_SERIAL - 1].s = mod_data.serial;
+ } else {
+ WARNING(fsg, "No serial-number string provided!\n");
+ no_serial:
+ device_desc.iSerialNumber = 0;
+ }
+
+ return 0;
+}
+
+
+static int __init fsg_bind(struct usb_gadget *gadget)
+{
+ struct fsg_dev *fsg = the_fsg;
+ int rc;
+ int i;
+ struct fsg_lun *curlun;
+ struct usb_ep *ep;
+ struct usb_request *req;
+ char *pathbuf, *p;
+
+ fsg->gadget = gadget;
+ set_gadget_data(gadget, fsg);
+ fsg->ep0 = gadget->ep0;
+ fsg->ep0->driver_data = fsg;
+
+ if ((rc = check_parameters(fsg)) != 0)
+ goto out;
+
+ if (mod_data.removable) { // Enable the store_xxx attributes
+ dev_attr_file.attr.mode = 0644;
+ dev_attr_file.store = fsg_store_file;
+ if (!mod_data.cdrom) {
+ dev_attr_ro.attr.mode = 0644;
+ dev_attr_ro.store = fsg_store_ro;
+ }
+ }
+
+ /* Only for removable media? */
+ dev_attr_nofua.attr.mode = 0644;
+ dev_attr_nofua.store = fsg_store_nofua;
+
+ /* Find out how many LUNs there should be */
+ i = mod_data.nluns;
+ if (i == 0)
+ i = max(mod_data.num_filenames, 1u);
+ if (i > FSG_MAX_LUNS) {
+ ERROR(fsg, "invalid number of LUNs: %d\n", i);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ /* Create the LUNs, open their backing files, and register the
+ * LUN devices in sysfs. */
+ fsg->luns = kzalloc(i * sizeof(struct fsg_lun), GFP_KERNEL);
+ if (!fsg->luns) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ fsg->nluns = i;
+
+ for (i = 0; i < fsg->nluns; ++i) {
+ curlun = &fsg->luns[i];
+ curlun->cdrom = !!mod_data.cdrom;
+ curlun->ro = mod_data.cdrom || mod_data.ro[i];
+ curlun->initially_ro = curlun->ro;
+ curlun->removable = mod_data.removable;
+ curlun->nofua = mod_data.nofua[i];
+ curlun->dev.release = lun_release;
+ curlun->dev.parent = &gadget->dev;
+ curlun->dev.driver = &fsg_driver.driver;
+ dev_set_drvdata(&curlun->dev, &fsg->filesem);
+ dev_set_name(&curlun->dev,"%s-lun%d",
+ dev_name(&gadget->dev), i);
+
+ kref_get(&fsg->ref);
+ rc = device_register(&curlun->dev);
+ if (rc) {
+ INFO(fsg, "failed to register LUN%d: %d\n", i, rc);
+ put_device(&curlun->dev);
+ goto out;
+ }
+ curlun->registered = 1;
+
+ rc = device_create_file(&curlun->dev, &dev_attr_ro);
+ if (rc)
+ goto out;
+ rc = device_create_file(&curlun->dev, &dev_attr_nofua);
+ if (rc)
+ goto out;
+ rc = device_create_file(&curlun->dev, &dev_attr_file);
+ if (rc)
+ goto out;
+
+ if (mod_data.file[i] && *mod_data.file[i]) {
+ rc = fsg_lun_open(curlun, mod_data.file[i]);
+ if (rc)
+ goto out;
+ } else if (!mod_data.removable) {
+ ERROR(fsg, "no file given for LUN%d\n", i);
+ rc = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* Find all the endpoints we will use */
+ usb_ep_autoconfig_reset(gadget);
+ ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
+ if (!ep)
+ goto autoconf_fail;
+ ep->driver_data = fsg; // claim the endpoint
+ fsg->bulk_in = ep;
+
+ ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
+ if (!ep)
+ goto autoconf_fail;
+ ep->driver_data = fsg; // claim the endpoint
+ fsg->bulk_out = ep;
+
+ if (transport_is_cbi()) {
+ ep = usb_ep_autoconfig(gadget, &fsg_fs_intr_in_desc);
+ if (!ep)
+ goto autoconf_fail;
+ ep->driver_data = fsg; // claim the endpoint
+ fsg->intr_in = ep;
+ }
+
+ /* Fix up the descriptors */
+ device_desc.bMaxPacketSize0 = fsg->ep0->maxpacket;
+ device_desc.idVendor = cpu_to_le16(mod_data.vendor);
+ device_desc.idProduct = cpu_to_le16(mod_data.product);
+ device_desc.bcdDevice = cpu_to_le16(mod_data.release);
+
+ i = (transport_is_cbi() ? 3 : 2); // Number of endpoints
+ fsg_intf_desc.bNumEndpoints = i;
+ fsg_intf_desc.bInterfaceSubClass = mod_data.protocol_type;
+ fsg_intf_desc.bInterfaceProtocol = mod_data.transport_type;
+ fsg_fs_function[i + FSG_FS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+ if (gadget_is_dualspeed(gadget)) {
+ fsg_hs_function[i + FSG_HS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+ /* Assume ep0 uses the same maxpacket value for both speeds */
+ dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket;
+
+ /* Assume endpoint addresses are the same for both speeds */
+ fsg_hs_bulk_in_desc.bEndpointAddress =
+ fsg_fs_bulk_in_desc.bEndpointAddress;
+ fsg_hs_bulk_out_desc.bEndpointAddress =
+ fsg_fs_bulk_out_desc.bEndpointAddress;
+ fsg_hs_intr_in_desc.bEndpointAddress =
+ fsg_fs_intr_in_desc.bEndpointAddress;
+ }
+
+ if (gadget_is_otg(gadget))
+ fsg_otg_desc.bmAttributes |= USB_OTG_HNP;
+
+ rc = -ENOMEM;
+
+ /* Allocate the request and buffer for endpoint 0 */
+ fsg->ep0req = req = usb_ep_alloc_request(fsg->ep0, GFP_KERNEL);
+ if (!req)
+ goto out;
+ req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL);
+ if (!req->buf)
+ goto out;
+ req->complete = ep0_complete;
+
+ /* Allocate the data buffers */
+ for (i = 0; i < FSG_NUM_BUFFERS; ++i) {
+ struct fsg_buffhd *bh = &fsg->buffhds[i];
+
+ /* Allocate for the bulk-in endpoint. We assume that
+ * the buffer will also work with the bulk-out (and
+ * interrupt-in) endpoint. */
+ bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL);
+ if (!bh->buf)
+ goto out;
+ bh->next = bh + 1;
+ }
+ fsg->buffhds[FSG_NUM_BUFFERS - 1].next = &fsg->buffhds[0];
+
+ /* This should reflect the actual gadget power source */
+ usb_gadget_set_selfpowered(gadget);
+
+ snprintf(fsg_string_manufacturer, sizeof fsg_string_manufacturer,
+ "%s %s with %s",
+ init_utsname()->sysname, init_utsname()->release,
+ gadget->name);
+
+ fsg->thread_task = kthread_create(fsg_main_thread, fsg,
+ "file-storage-gadget");
+ if (IS_ERR(fsg->thread_task)) {
+ rc = PTR_ERR(fsg->thread_task);
+ goto out;
+ }
+
+ INFO(fsg, DRIVER_DESC ", version: " DRIVER_VERSION "\n");
+ INFO(fsg, "Number of LUNs=%d\n", fsg->nluns);
+
+ pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+ for (i = 0; i < fsg->nluns; ++i) {
+ curlun = &fsg->luns[i];
+ if (fsg_lun_is_open(curlun)) {
+ p = NULL;
+ if (pathbuf) {
+ p = d_path(&curlun->filp->f_path,
+ pathbuf, PATH_MAX);
+ if (IS_ERR(p))
+ p = NULL;
+ }
+ LINFO(curlun, "ro=%d, nofua=%d, file: %s\n",
+ curlun->ro, curlun->nofua, (p ? p : "(error)"));
+ }
+ }
+ kfree(pathbuf);
+
+ DBG(fsg, "transport=%s (x%02x)\n",
+ mod_data.transport_name, mod_data.transport_type);
+ DBG(fsg, "protocol=%s (x%02x)\n",
+ mod_data.protocol_name, mod_data.protocol_type);
+ DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n",
+ mod_data.vendor, mod_data.product, mod_data.release);
+ DBG(fsg, "removable=%d, stall=%d, cdrom=%d, buflen=%u\n",
+ mod_data.removable, mod_data.can_stall,
+ mod_data.cdrom, mod_data.buflen);
+ DBG(fsg, "I/O thread pid: %d\n", task_pid_nr(fsg->thread_task));
+
+ set_bit(REGISTERED, &fsg->atomic_bitflags);
+
+ /* Tell the thread to start working */
+ wake_up_process(fsg->thread_task);
+ return 0;
+
+autoconf_fail:
+ ERROR(fsg, "unable to autoconfigure all endpoints\n");
+ rc = -ENOTSUPP;
+
+out:
+ fsg->state = FSG_STATE_TERMINATED; // The thread is dead
+ fsg_unbind(gadget);
+ complete(&fsg->thread_notifier);
+ return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void fsg_suspend(struct usb_gadget *gadget)
+{
+ struct fsg_dev *fsg = get_gadget_data(gadget);
+
+ DBG(fsg, "suspend\n");
+ set_bit(SUSPENDED, &fsg->atomic_bitflags);
+}
+
+static void fsg_resume(struct usb_gadget *gadget)
+{
+ struct fsg_dev *fsg = get_gadget_data(gadget);
+
+ DBG(fsg, "resume\n");
+ clear_bit(SUSPENDED, &fsg->atomic_bitflags);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_gadget_driver fsg_driver = {
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+ .speed = USB_SPEED_HIGH,
+#else
+ .speed = USB_SPEED_FULL,
+#endif
+ .function = (char *) fsg_string_product,
+ .unbind = fsg_unbind,
+ .disconnect = fsg_disconnect,
+ .setup = fsg_setup,
+ .suspend = fsg_suspend,
+ .resume = fsg_resume,
+
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ // .release = ...
+ // .suspend = ...
+ // .resume = ...
+ },
+};
+
+
+static int __init fsg_alloc(void)
+{
+ struct fsg_dev *fsg;
+
+ fsg = kzalloc(sizeof *fsg, GFP_KERNEL);
+ if (!fsg)
+ return -ENOMEM;
+ spin_lock_init(&fsg->lock);
+ init_rwsem(&fsg->filesem);
+ kref_init(&fsg->ref);
+ init_completion(&fsg->thread_notifier);
+
+ the_fsg = fsg;
+ return 0;
+}
+
+
+static int __init fsg_init(void)
+{
+ int rc;
+ struct fsg_dev *fsg;
+
+ if ((rc = fsg_alloc()) != 0)
+ return rc;
+ fsg = the_fsg;
+ if ((rc = usb_gadget_probe_driver(&fsg_driver, fsg_bind)) != 0)
+ kref_put(&fsg->ref, fsg_release);
+ return rc;
+}
+module_init(fsg_init);
+
+
+static void __exit fsg_cleanup(void)
+{
+ struct fsg_dev *fsg = the_fsg;
+
+ /* Unregister the driver iff the thread hasn't already done so */
+ if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags))
+ usb_gadget_unregister_driver(&fsg_driver);
+
+ /* Wait for the thread to finish up */
+ wait_for_completion(&fsg->thread_notifier);
+
+ kref_put(&fsg->ref, fsg_release);
+}
+module_exit(fsg_cleanup);
diff --git a/drivers/usb/gadget/gadget_gbhc/gadget_chips.h b/drivers/usb/gadget/gadget_gbhc/gadget_chips.h
new file mode 100644
index 0000000..2db7cb2
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/gadget_chips.h
@@ -0,0 +1,265 @@
+/*
+ * USB device controllers have lots of quirks. Use these macros in
+ * gadget drivers or other code that needs to deal with them, and which
+ * autoconfigures instead of using early binding to the hardware.
+ *
+ * This SHOULD eventually work like the ARM mach_is_*() stuff, driven by
+ * some config file that gets updated as new hardware is supported.
+ * (And avoiding all runtime comparisons in typical one-choice configs!)
+ *
+ * NOTE: some of these controller drivers may not be available yet.
+ * Some are available on 2.4 kernels; several are available, but not
+ * yet pushed in the 2.6 mainline tree.
+ */
+
+#ifndef __GADGET_CHIPS_H
+#define __GADGET_CHIPS_H
+
+#ifdef CONFIG_USB_GADGET_NET2280
+#define gadget_is_net2280(g) !strcmp("net2280", (g)->name)
+#else
+#define gadget_is_net2280(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_AMD5536UDC
+#define gadget_is_amd5536udc(g) !strcmp("amd5536udc", (g)->name)
+#else
+#define gadget_is_amd5536udc(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_DUMMY_HCD
+#define gadget_is_dummy(g) !strcmp("dummy_udc", (g)->name)
+#else
+#define gadget_is_dummy(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_PXA25X
+#define gadget_is_pxa(g) !strcmp("pxa25x_udc", (g)->name)
+#else
+#define gadget_is_pxa(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_GOKU
+#define gadget_is_goku(g) !strcmp("goku_udc", (g)->name)
+#else
+#define gadget_is_goku(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_OMAP
+#define gadget_is_omap(g) !strcmp("omap_udc", (g)->name)
+#else
+#define gadget_is_omap(g) 0
+#endif
+
+/* various unstable versions available */
+#ifdef CONFIG_USB_GADGET_PXA27X
+#define gadget_is_pxa27x(g) !strcmp("pxa27x_udc", (g)->name)
+#else
+#define gadget_is_pxa27x(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_ATMEL_USBA
+#define gadget_is_atmel_usba(g) !strcmp("atmel_usba_udc", (g)->name)
+#else
+#define gadget_is_atmel_usba(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_S3C2410
+#define gadget_is_s3c2410(g) !strcmp("s3c2410_udc", (g)->name)
+#else
+#define gadget_is_s3c2410(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_AT91
+#define gadget_is_at91(g) !strcmp("at91_udc", (g)->name)
+#else
+#define gadget_is_at91(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_IMX
+#define gadget_is_imx(g) !strcmp("imx_udc", (g)->name)
+#else
+#define gadget_is_imx(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_FSL_USB2
+#define gadget_is_fsl_usb2(g) !strcmp("fsl-usb2-udc", (g)->name)
+#else
+#define gadget_is_fsl_usb2(g) 0
+#endif
+
+/* Mentor high speed "dual role" controller, in peripheral role */
+#ifdef CONFIG_USB_GADGET_MUSB_HDRC
+#define gadget_is_musbhdrc(g) !strcmp("musb-hdrc", (g)->name)
+#else
+#define gadget_is_musbhdrc(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_LANGWELL
+#define gadget_is_langwell(g) (!strcmp("langwell_udc", (g)->name))
+#else
+#define gadget_is_langwell(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_M66592
+#define gadget_is_m66592(g) !strcmp("m66592_udc", (g)->name)
+#else
+#define gadget_is_m66592(g) 0
+#endif
+
+/* Freescale CPM/QE UDC SUPPORT */
+#ifdef CONFIG_USB_GADGET_FSL_QE
+#define gadget_is_fsl_qe(g) !strcmp("fsl_qe_udc", (g)->name)
+#else
+#define gadget_is_fsl_qe(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_CI13XXX_PCI
+#define gadget_is_ci13xxx_pci(g) (!strcmp("ci13xxx_pci", (g)->name))
+#else
+#define gadget_is_ci13xxx_pci(g) 0
+#endif
+
+// CONFIG_USB_GADGET_SX2
+// CONFIG_USB_GADGET_AU1X00
+// ...
+
+#ifdef CONFIG_USB_GADGET_R8A66597
+#define gadget_is_r8a66597(g) !strcmp("r8a66597_udc", (g)->name)
+#else
+#define gadget_is_r8a66597(g) 0
+#endif
+
+#ifdef CONFIG_USB_S3C_HSOTG
+#define gadget_is_s3c_hsotg(g) (!strcmp("s3c-hsotg", (g)->name))
+#else
+#define gadget_is_s3c_hsotg(g) 0
+#endif
+
+#ifdef CONFIG_USB_S3C_HSUDC
+#define gadget_is_s3c_hsudc(g) (!strcmp("s3c-hsudc", (g)->name))
+#else
+#define gadget_is_s3c_hsudc(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_S3C_OTGD
+#define gadget_is_s3c(g) !strcmp("s3c-udc", (g)->name)
+#else
+#define gadget_is_s3c(g) 0
+#endif
+
+#ifdef CONFIG_USB_EXYNOS_SS_UDC
+#define gadget_is_exynos_ss_udc(g) (!strcmp("exynos-ss-udc", (g)->name))
+#else
+#define gadget_is_exynos_ss_udc(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_EG20T
+#define gadget_is_pch(g) (!strcmp("pch_udc", (g)->name))
+#else
+#define gadget_is_pch(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_CI13XXX_MSM
+#define gadget_is_ci13xxx_msm(g) (!strcmp("ci13xxx_msm", (g)->name))
+#else
+#define gadget_is_ci13xxx_msm(g) 0
+#endif
+
+#ifdef CONFIG_USB_GADGET_RENESAS_USBHS
+#define gadget_is_renesas_usbhs(g) (!strcmp("renesas_usbhs_udc", (g)->name))
+#else
+#define gadget_is_renesas_usbhs(g) 0
+#endif
+
+/**
+ * usb_gadget_controller_number - support bcdDevice id convention
+ * @gadget: the controller being driven
+ *
+ * Return a 2-digit BCD value associated with the peripheral controller,
+ * suitable for use as part of a bcdDevice value, or a negative error code.
+ *
+ * NOTE: this convention is purely optional, and has no meaning in terms of
+ * any USB specification. If you want to use a different convention in your
+ * gadget driver firmware -- maybe a more formal revision ID -- feel free.
+ *
+ * Hosts see these bcdDevice numbers, and are allowed (but not encouraged!)
+ * to change their behavior accordingly. For example it might help avoiding
+ * some chip bug.
+ */
+static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
+{
+ if (gadget_is_net2280(gadget))
+ return 0x01;
+ else if (gadget_is_dummy(gadget))
+ return 0x02;
+ else if (gadget_is_pxa(gadget))
+ return 0x03;
+ else if (gadget_is_goku(gadget))
+ return 0x06;
+ else if (gadget_is_omap(gadget))
+ return 0x08;
+ else if (gadget_is_pxa27x(gadget))
+ return 0x11;
+ else if (gadget_is_s3c2410(gadget))
+ return 0x12;
+ else if (gadget_is_at91(gadget))
+ return 0x13;
+ else if (gadget_is_imx(gadget))
+ return 0x14;
+ else if (gadget_is_musbhdrc(gadget))
+ return 0x16;
+ else if (gadget_is_atmel_usba(gadget))
+ return 0x18;
+ else if (gadget_is_fsl_usb2(gadget))
+ return 0x19;
+ else if (gadget_is_amd5536udc(gadget))
+ return 0x20;
+ else if (gadget_is_m66592(gadget))
+ return 0x21;
+ else if (gadget_is_fsl_qe(gadget))
+ return 0x22;
+ else if (gadget_is_ci13xxx_pci(gadget))
+ return 0x23;
+ else if (gadget_is_langwell(gadget))
+ return 0x24;
+ else if (gadget_is_r8a66597(gadget))
+ return 0x25;
+ else if (gadget_is_s3c_hsotg(gadget))
+ return 0x26;
+ else if (gadget_is_s3c(gadget))
+ return 0x26;
+ else if (gadget_is_pch(gadget))
+ return 0x27;
+ else if (gadget_is_ci13xxx_msm(gadget))
+ return 0x28;
+ else if (gadget_is_renesas_usbhs(gadget))
+ return 0x29;
+ else if (gadget_is_s3c_hsudc(gadget))
+ return 0x30;
+ else if (gadget_is_exynos_ss_udc(gadget))
+ return 0x31;
+
+ return -ENOENT;
+}
+
+
+/**
+ * gadget_supports_altsettings - return true if altsettings work
+ * @gadget: the gadget in question
+ */
+static inline bool gadget_supports_altsettings(struct usb_gadget *gadget)
+{
+ /* PXA 21x/25x/26x has no altsettings at all */
+ if (gadget_is_pxa(gadget))
+ return false;
+
+ /* PXA 27x and 3xx have *broken* altsetting support */
+ if (gadget_is_pxa27x(gadget))
+ return false;
+
+ /* Everything else is *presumably* fine ... */
+ return true;
+}
+
+#endif /* __GADGET_CHIPS_H */
diff --git a/drivers/usb/gadget/gadget_gbhc/inode.c b/drivers/usb/gadget/gadget_gbhc/inode.c
new file mode 100644
index 0000000..a56876a
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/inode.c
@@ -0,0 +1,2150 @@
+/*
+ * inode.c -- user mode filesystem api for usb gadget controllers
+ *
+ * Copyright (C) 2003-2004 David Brownell
+ * Copyright (C) 2003 Agilent Technologies
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/pagemap.h>
+#include <linux/uts.h>
+#include <linux/wait.h>
+#include <linux/compiler.h>
+#include <asm/uaccess.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+
+#include <linux/usb/gadgetfs.h>
+#include <linux/usb/gadget.h>
+
+
+/*
+ * The gadgetfs API maps each endpoint to a file descriptor so that you
+ * can use standard synchronous read/write calls for I/O. There's some
+ * O_NONBLOCK and O_ASYNC/FASYNC style i/o support. Example usermode
+ * drivers show how this works in practice. You can also use AIO to
+ * eliminate I/O gaps between requests, to help when streaming data.
+ *
+ * Key parts that must be USB-specific are protocols defining how the
+ * read/write operations relate to the hardware state machines. There
+ * are two types of files. One type is for the device, implementing ep0.
+ * The other type is for each IN or OUT endpoint. In both cases, the
+ * user mode driver must configure the hardware before using it.
+ *
+ * - First, dev_config() is called when /dev/gadget/$CHIP is configured
+ * (by writing configuration and device descriptors). Afterwards it
+ * may serve as a source of device events, used to handle all control
+ * requests other than basic enumeration.
+ *
+ * - Then, after a SET_CONFIGURATION control request, ep_config() is
+ * called when each /dev/gadget/ep* file is configured (by writing
+ * endpoint descriptors). Afterwards these files are used to write()
+ * IN data or to read() OUT data. To halt the endpoint, a "wrong
+ * direction" request is issued (like reading an IN endpoint).
+ *
+ * Unlike "usbfs" the only ioctl()s are for things that are rare, and maybe
+ * not possible on all hardware. For example, precise fault handling with
+ * respect to data left in endpoint fifos after aborted operations; or
+ * selective clearing of endpoint halts, to implement SET_INTERFACE.
+ */
+
+#define DRIVER_DESC "USB Gadget filesystem"
+#define DRIVER_VERSION "24 Aug 2004"
+
+static const char driver_desc [] = DRIVER_DESC;
+static const char shortname [] = "gadgetfs";
+
+MODULE_DESCRIPTION (DRIVER_DESC);
+MODULE_AUTHOR ("David Brownell");
+MODULE_LICENSE ("GPL");
+
+
+/*----------------------------------------------------------------------*/
+
+#define GADGETFS_MAGIC 0xaee71ee7
+#define DMA_ADDR_INVALID (~(dma_addr_t)0)
+
+/* /dev/gadget/$CHIP represents ep0 and the whole device */
+enum ep0_state {
+ /* DISBLED is the initial state.
+ */
+ STATE_DEV_DISABLED = 0,
+
+ /* Only one open() of /dev/gadget/$CHIP; only one file tracks
+ * ep0/device i/o modes and binding to the controller. Driver
+ * must always write descriptors to initialize the device, then
+ * the device becomes UNCONNECTED until enumeration.
+ */
+ STATE_DEV_OPENED,
+
+ /* From then on, ep0 fd is in either of two basic modes:
+ * - (UN)CONNECTED: read usb_gadgetfs_event(s) from it
+ * - SETUP: read/write will transfer control data and succeed;
+ * or if "wrong direction", performs protocol stall
+ */
+ STATE_DEV_UNCONNECTED,
+ STATE_DEV_CONNECTED,
+ STATE_DEV_SETUP,
+
+ /* UNBOUND means the driver closed ep0, so the device won't be
+ * accessible again (DEV_DISABLED) until all fds are closed.
+ */
+ STATE_DEV_UNBOUND,
+};
+
+/* enough for the whole queue: most events invalidate others */
+#define N_EVENT 5
+
+struct dev_data {
+ spinlock_t lock;
+ atomic_t count;
+ enum ep0_state state; /* P: lock */
+ struct usb_gadgetfs_event event [N_EVENT];
+ unsigned ev_next;
+ struct fasync_struct *fasync;
+ u8 current_config;
+
+ /* drivers reading ep0 MUST handle control requests (SETUP)
+ * reported that way; else the host will time out.
+ */
+ unsigned usermode_setup : 1,
+ setup_in : 1,
+ setup_can_stall : 1,
+ setup_out_ready : 1,
+ setup_out_error : 1,
+ setup_abort : 1;
+ unsigned setup_wLength;
+
+ /* the rest is basically write-once */
+ struct usb_config_descriptor *config, *hs_config;
+ struct usb_device_descriptor *dev;
+ struct usb_request *req;
+ struct usb_gadget *gadget;
+ struct list_head epfiles;
+ void *buf;
+ wait_queue_head_t wait;
+ struct super_block *sb;
+ struct dentry *dentry;
+
+ /* except this scratch i/o buffer for ep0 */
+ u8 rbuf [256];
+};
+
+static inline void get_dev (struct dev_data *data)
+{
+ atomic_inc (&data->count);
+}
+
+static void put_dev (struct dev_data *data)
+{
+ if (likely (!atomic_dec_and_test (&data->count)))
+ return;
+ /* needs no more cleanup */
+ BUG_ON (waitqueue_active (&data->wait));
+ kfree (data);
+}
+
+static struct dev_data *dev_new (void)
+{
+ struct dev_data *dev;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return NULL;
+ dev->state = STATE_DEV_DISABLED;
+ atomic_set (&dev->count, 1);
+ spin_lock_init (&dev->lock);
+ INIT_LIST_HEAD (&dev->epfiles);
+ init_waitqueue_head (&dev->wait);
+ return dev;
+}
+
+/*----------------------------------------------------------------------*/
+
+/* other /dev/gadget/$ENDPOINT files represent endpoints */
+enum ep_state {
+ STATE_EP_DISABLED = 0,
+ STATE_EP_READY,
+ STATE_EP_ENABLED,
+ STATE_EP_UNBOUND,
+};
+
+struct ep_data {
+ struct mutex lock;
+ enum ep_state state;
+ atomic_t count;
+ struct dev_data *dev;
+ /* must hold dev->lock before accessing ep or req */
+ struct usb_ep *ep;
+ struct usb_request *req;
+ ssize_t status;
+ char name [16];
+ struct usb_endpoint_descriptor desc, hs_desc;
+ struct list_head epfiles;
+ wait_queue_head_t wait;
+ struct dentry *dentry;
+ struct inode *inode;
+};
+
+static inline void get_ep (struct ep_data *data)
+{
+ atomic_inc (&data->count);
+}
+
+static void put_ep (struct ep_data *data)
+{
+ if (likely (!atomic_dec_and_test (&data->count)))
+ return;
+ put_dev (data->dev);
+ /* needs no more cleanup */
+ BUG_ON (!list_empty (&data->epfiles));
+ BUG_ON (waitqueue_active (&data->wait));
+ kfree (data);
+}
+
+/*----------------------------------------------------------------------*/
+
+/* most "how to use the hardware" policy choices are in userspace:
+ * mapping endpoint roles (which the driver needs) to the capabilities
+ * which the usb controller has. most of those capabilities are exposed
+ * implicitly, starting with the driver name and then endpoint names.
+ */
+
+static const char *CHIP;
+
+/*----------------------------------------------------------------------*/
+
+/* NOTE: don't use dev_printk calls before binding to the gadget
+ * at the end of ep0 configuration, or after unbind.
+ */
+
+/* too wordy: dev_printk(level , &(d)->gadget->dev , fmt , ## args) */
+#define xprintk(d,level,fmt,args...) \
+ printk(level "%s: " fmt , shortname , ## args)
+
+#ifdef DEBUG
+#define DBG(dev,fmt,args...) \
+ xprintk(dev , KERN_DEBUG , fmt , ## args)
+#else
+#define DBG(dev,fmt,args...) \
+ do { } while (0)
+#endif /* DEBUG */
+
+#ifdef VERBOSE_DEBUG
+#define VDEBUG DBG
+#else
+#define VDEBUG(dev,fmt,args...) \
+ do { } while (0)
+#endif /* DEBUG */
+
+#define ERROR(dev,fmt,args...) \
+ xprintk(dev , KERN_ERR , fmt , ## args)
+#define INFO(dev,fmt,args...) \
+ xprintk(dev , KERN_INFO , fmt , ## args)
+
+
+/*----------------------------------------------------------------------*/
+
+/* SYNCHRONOUS ENDPOINT OPERATIONS (bulk/intr/iso)
+ *
+ * After opening, configure non-control endpoints. Then use normal
+ * stream read() and write() requests; and maybe ioctl() to get more
+ * precise FIFO status when recovering from cancellation.
+ */
+
+static void epio_complete (struct usb_ep *ep, struct usb_request *req)
+{
+ struct ep_data *epdata = ep->driver_data;
+
+ if (!req->context)
+ return;
+ if (req->status)
+ epdata->status = req->status;
+ else
+ epdata->status = req->actual;
+ complete ((struct completion *)req->context);
+}
+
+/* tasklock endpoint, returning when it's connected.
+ * still need dev->lock to use epdata->ep.
+ */
+static int
+get_ready_ep (unsigned f_flags, struct ep_data *epdata)
+{
+ int val;
+
+ if (f_flags & O_NONBLOCK) {
+ if (!mutex_trylock(&epdata->lock))
+ goto nonblock;
+ if (epdata->state != STATE_EP_ENABLED) {
+ mutex_unlock(&epdata->lock);
+nonblock:
+ val = -EAGAIN;
+ } else
+ val = 0;
+ return val;
+ }
+
+ val = mutex_lock_interruptible(&epdata->lock);
+ if (val < 0)
+ return val;
+
+ switch (epdata->state) {
+ case STATE_EP_ENABLED:
+ break;
+ // case STATE_EP_DISABLED: /* "can't happen" */
+ // case STATE_EP_READY: /* "can't happen" */
+ default: /* error! */
+ pr_debug ("%s: ep %p not available, state %d\n",
+ shortname, epdata, epdata->state);
+ // FALLTHROUGH
+ case STATE_EP_UNBOUND: /* clean disconnect */
+ val = -ENODEV;
+ mutex_unlock(&epdata->lock);
+ }
+ return val;
+}
+
+static ssize_t
+ep_io (struct ep_data *epdata, void *buf, unsigned len)
+{
+ DECLARE_COMPLETION_ONSTACK (done);
+ int value;
+
+ spin_lock_irq (&epdata->dev->lock);
+ if (likely (epdata->ep != NULL)) {
+ struct usb_request *req = epdata->req;
+
+ req->context = &done;
+ req->complete = epio_complete;
+ req->buf = buf;
+ req->length = len;
+ value = usb_ep_queue (epdata->ep, req, GFP_ATOMIC);
+ } else
+ value = -ENODEV;
+ spin_unlock_irq (&epdata->dev->lock);
+
+ if (likely (value == 0)) {
+ value = wait_event_interruptible (done.wait, done.done);
+ if (value != 0) {
+ spin_lock_irq (&epdata->dev->lock);
+ if (likely (epdata->ep != NULL)) {
+ DBG (epdata->dev, "%s i/o interrupted\n",
+ epdata->name);
+ usb_ep_dequeue (epdata->ep, epdata->req);
+ spin_unlock_irq (&epdata->dev->lock);
+
+ wait_event (done.wait, done.done);
+ if (epdata->status == -ECONNRESET)
+ epdata->status = -EINTR;
+ } else {
+ spin_unlock_irq (&epdata->dev->lock);
+
+ DBG (epdata->dev, "endpoint gone\n");
+ epdata->status = -ENODEV;
+ }
+ }
+ return epdata->status;
+ }
+ return value;
+}
+
+
+/* handle a synchronous OUT bulk/intr/iso transfer */
+static ssize_t
+ep_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr)
+{
+ struct ep_data *data = fd->private_data;
+ void *kbuf;
+ ssize_t value;
+
+ if ((value = get_ready_ep (fd->f_flags, data)) < 0)
+ return value;
+
+ /* halt any endpoint by doing a "wrong direction" i/o call */
+ if (usb_endpoint_dir_in(&data->desc)) {
+ if (usb_endpoint_xfer_isoc(&data->desc)) {
+ mutex_unlock(&data->lock);
+ return -EINVAL;
+ }
+ DBG (data->dev, "%s halt\n", data->name);
+ spin_lock_irq (&data->dev->lock);
+ if (likely (data->ep != NULL))
+ usb_ep_set_halt (data->ep);
+ spin_unlock_irq (&data->dev->lock);
+ mutex_unlock(&data->lock);
+ return -EBADMSG;
+ }
+
+ /* FIXME readahead for O_NONBLOCK and poll(); careful with ZLPs */
+
+ value = -ENOMEM;
+ kbuf = kmalloc (len, GFP_KERNEL);
+ if (unlikely (!kbuf))
+ goto free1;
+
+ value = ep_io (data, kbuf, len);
+ VDEBUG (data->dev, "%s read %zu OUT, status %d\n",
+ data->name, len, (int) value);
+ if (value >= 0 && copy_to_user (buf, kbuf, value))
+ value = -EFAULT;
+
+free1:
+ mutex_unlock(&data->lock);
+ kfree (kbuf);
+ return value;
+}
+
+/* handle a synchronous IN bulk/intr/iso transfer */
+static ssize_t
+ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
+{
+ struct ep_data *data = fd->private_data;
+ void *kbuf;
+ ssize_t value;
+
+ if ((value = get_ready_ep (fd->f_flags, data)) < 0)
+ return value;
+
+ /* halt any endpoint by doing a "wrong direction" i/o call */
+ if (!usb_endpoint_dir_in(&data->desc)) {
+ if (usb_endpoint_xfer_isoc(&data->desc)) {
+ mutex_unlock(&data->lock);
+ return -EINVAL;
+ }
+ DBG (data->dev, "%s halt\n", data->name);
+ spin_lock_irq (&data->dev->lock);
+ if (likely (data->ep != NULL))
+ usb_ep_set_halt (data->ep);
+ spin_unlock_irq (&data->dev->lock);
+ mutex_unlock(&data->lock);
+ return -EBADMSG;
+ }
+
+ /* FIXME writebehind for O_NONBLOCK and poll(), qlen = 1 */
+
+ value = -ENOMEM;
+ kbuf = kmalloc (len, GFP_KERNEL);
+ if (!kbuf)
+ goto free1;
+ if (copy_from_user (kbuf, buf, len)) {
+ value = -EFAULT;
+ goto free1;
+ }
+
+ value = ep_io (data, kbuf, len);
+ VDEBUG (data->dev, "%s write %zu IN, status %d\n",
+ data->name, len, (int) value);
+free1:
+ mutex_unlock(&data->lock);
+ kfree (kbuf);
+ return value;
+}
+
+static int
+ep_release (struct inode *inode, struct file *fd)
+{
+ struct ep_data *data = fd->private_data;
+ int value;
+
+ value = mutex_lock_interruptible(&data->lock);
+ if (value < 0)
+ return value;
+
+ /* clean up if this can be reopened */
+ if (data->state != STATE_EP_UNBOUND) {
+ data->state = STATE_EP_DISABLED;
+ data->desc.bDescriptorType = 0;
+ data->hs_desc.bDescriptorType = 0;
+ usb_ep_disable(data->ep);
+ }
+ mutex_unlock(&data->lock);
+ put_ep (data);
+ return 0;
+}
+
+static long ep_ioctl(struct file *fd, unsigned code, unsigned long value)
+{
+ struct ep_data *data = fd->private_data;
+ int status;
+
+ if ((status = get_ready_ep (fd->f_flags, data)) < 0)
+ return status;
+
+ spin_lock_irq (&data->dev->lock);
+ if (likely (data->ep != NULL)) {
+ switch (code) {
+ case GADGETFS_FIFO_STATUS:
+ status = usb_ep_fifo_status (data->ep);
+ break;
+ case GADGETFS_FIFO_FLUSH:
+ usb_ep_fifo_flush (data->ep);
+ break;
+ case GADGETFS_CLEAR_HALT:
+ status = usb_ep_clear_halt (data->ep);
+ break;
+ default:
+ status = -ENOTTY;
+ }
+ } else
+ status = -ENODEV;
+ spin_unlock_irq (&data->dev->lock);
+ mutex_unlock(&data->lock);
+ return status;
+}
+
+/*----------------------------------------------------------------------*/
+
+/* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */
+
+struct kiocb_priv {
+ struct usb_request *req;
+ struct ep_data *epdata;
+ void *buf;
+ const struct iovec *iv;
+ unsigned long nr_segs;
+ unsigned actual;
+};
+
+static int ep_aio_cancel(struct kiocb *iocb, struct io_event *e)
+{
+ struct kiocb_priv *priv = iocb->private;
+ struct ep_data *epdata;
+ int value;
+
+ local_irq_disable();
+ epdata = priv->epdata;
+ // spin_lock(&epdata->dev->lock);
+ kiocbSetCancelled(iocb);
+ if (likely(epdata && epdata->ep && priv->req))
+ value = usb_ep_dequeue (epdata->ep, priv->req);
+ else
+ value = -EINVAL;
+ // spin_unlock(&epdata->dev->lock);
+ local_irq_enable();
+
+ aio_put_req(iocb);
+ return value;
+}
+
+static ssize_t ep_aio_read_retry(struct kiocb *iocb)
+{
+ struct kiocb_priv *priv = iocb->private;
+ ssize_t len, total;
+ void *to_copy;
+ int i;
+
+ /* we "retry" to get the right mm context for this: */
+
+ /* copy stuff into user buffers */
+ total = priv->actual;
+ len = 0;
+ to_copy = priv->buf;
+ for (i=0; i < priv->nr_segs; i++) {
+ ssize_t this = min((ssize_t)(priv->iv[i].iov_len), total);
+
+ if (copy_to_user(priv->iv[i].iov_base, to_copy, this)) {
+ if (len == 0)
+ len = -EFAULT;
+ break;
+ }
+
+ total -= this;
+ len += this;
+ to_copy += this;
+ if (total == 0)
+ break;
+ }
+ kfree(priv->buf);
+ kfree(priv);
+ return len;
+}
+
+static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct kiocb *iocb = req->context;
+ struct kiocb_priv *priv = iocb->private;
+ struct ep_data *epdata = priv->epdata;
+
+ /* lock against disconnect (and ideally, cancel) */
+ spin_lock(&epdata->dev->lock);
+ priv->req = NULL;
+ priv->epdata = NULL;
+
+ /* if this was a write or a read returning no data then we
+ * don't need to copy anything to userspace, so we can
+ * complete the aio request immediately.
+ */
+ if (priv->iv == NULL || unlikely(req->actual == 0)) {
+ kfree(req->buf);
+ kfree(priv);
+ iocb->private = NULL;
+ /* aio_complete() reports bytes-transferred _and_ faults */
+ aio_complete(iocb, req->actual ? req->actual : req->status,
+ req->status);
+ } else {
+ /* retry() won't report both; so we hide some faults */
+ if (unlikely(0 != req->status))
+ DBG(epdata->dev, "%s fault %d len %d\n",
+ ep->name, req->status, req->actual);
+
+ priv->buf = req->buf;
+ priv->actual = req->actual;
+ kick_iocb(iocb);
+ }
+ spin_unlock(&epdata->dev->lock);
+
+ usb_ep_free_request(ep, req);
+ put_ep(epdata);
+}
+
+static ssize_t
+ep_aio_rwtail(
+ struct kiocb *iocb,
+ char *buf,
+ size_t len,
+ struct ep_data *epdata,
+ const struct iovec *iv,
+ unsigned long nr_segs
+)
+{
+ struct kiocb_priv *priv;
+ struct usb_request *req;
+ ssize_t value;
+
+ priv = kmalloc(sizeof *priv, GFP_KERNEL);
+ if (!priv) {
+ value = -ENOMEM;
+fail:
+ kfree(buf);
+ return value;
+ }
+ iocb->private = priv;
+ priv->iv = iv;
+ priv->nr_segs = nr_segs;
+
+ value = get_ready_ep(iocb->ki_filp->f_flags, epdata);
+ if (unlikely(value < 0)) {
+ kfree(priv);
+ goto fail;
+ }
+
+ iocb->ki_cancel = ep_aio_cancel;
+ get_ep(epdata);
+ priv->epdata = epdata;
+ priv->actual = 0;
+
+ /* each kiocb is coupled to one usb_request, but we can't
+ * allocate or submit those if the host disconnected.
+ */
+ spin_lock_irq(&epdata->dev->lock);
+ if (likely(epdata->ep)) {
+ req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC);
+ if (likely(req)) {
+ priv->req = req;
+ req->buf = buf;
+ req->length = len;
+ req->complete = ep_aio_complete;
+ req->context = iocb;
+ value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC);
+ if (unlikely(0 != value))
+ usb_ep_free_request(epdata->ep, req);
+ } else
+ value = -EAGAIN;
+ } else
+ value = -ENODEV;
+ spin_unlock_irq(&epdata->dev->lock);
+
+ mutex_unlock(&epdata->lock);
+
+ if (unlikely(value)) {
+ kfree(priv);
+ put_ep(epdata);
+ } else
+ value = (iv ? -EIOCBRETRY : -EIOCBQUEUED);
+ return value;
+}
+
+static ssize_t
+ep_aio_read(struct kiocb *iocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t o)
+{
+ struct ep_data *epdata = iocb->ki_filp->private_data;
+ char *buf;
+
+ if (unlikely(usb_endpoint_dir_in(&epdata->desc)))
+ return -EINVAL;
+
+ buf = kmalloc(iocb->ki_left, GFP_KERNEL);
+ if (unlikely(!buf))
+ return -ENOMEM;
+
+ iocb->ki_retry = ep_aio_read_retry;
+ return ep_aio_rwtail(iocb, buf, iocb->ki_left, epdata, iov, nr_segs);
+}
+
+static ssize_t
+ep_aio_write(struct kiocb *iocb, const struct iovec *iov,
+ unsigned long nr_segs, loff_t o)
+{
+ struct ep_data *epdata = iocb->ki_filp->private_data;
+ char *buf;
+ size_t len = 0;
+ int i = 0;
+
+ if (unlikely(!usb_endpoint_dir_in(&epdata->desc)))
+ return -EINVAL;
+
+ buf = kmalloc(iocb->ki_left, GFP_KERNEL);
+ if (unlikely(!buf))
+ return -ENOMEM;
+
+ for (i=0; i < nr_segs; i++) {
+ if (unlikely(copy_from_user(&buf[len], iov[i].iov_base,
+ iov[i].iov_len) != 0)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+ len += iov[i].iov_len;
+ }
+ return ep_aio_rwtail(iocb, buf, len, epdata, NULL, 0);
+}
+
+/*----------------------------------------------------------------------*/
+
+/* used after endpoint configuration */
+static const struct file_operations ep_io_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+
+ .read = ep_read,
+ .write = ep_write,
+ .unlocked_ioctl = ep_ioctl,
+ .release = ep_release,
+
+ .aio_read = ep_aio_read,
+ .aio_write = ep_aio_write,
+};
+
+/* ENDPOINT INITIALIZATION
+ *
+ * fd = open ("/dev/gadget/$ENDPOINT", O_RDWR)
+ * status = write (fd, descriptors, sizeof descriptors)
+ *
+ * That write establishes the endpoint configuration, configuring
+ * the controller to process bulk, interrupt, or isochronous transfers
+ * at the right maxpacket size, and so on.
+ *
+ * The descriptors are message type 1, identified by a host order u32
+ * at the beginning of what's written. Descriptor order is: full/low
+ * speed descriptor, then optional high speed descriptor.
+ */
+static ssize_t
+ep_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
+{
+ struct ep_data *data = fd->private_data;
+ struct usb_ep *ep;
+ u32 tag;
+ int value, length = len;
+
+ value = mutex_lock_interruptible(&data->lock);
+ if (value < 0)
+ return value;
+
+ if (data->state != STATE_EP_READY) {
+ value = -EL2HLT;
+ goto fail;
+ }
+
+ value = len;
+ if (len < USB_DT_ENDPOINT_SIZE + 4)
+ goto fail0;
+
+ /* we might need to change message format someday */
+ if (copy_from_user (&tag, buf, 4)) {
+ goto fail1;
+ }
+ if (tag != 1) {
+ DBG(data->dev, "config %s, bad tag %d\n", data->name, tag);
+ goto fail0;
+ }
+ buf += 4;
+ len -= 4;
+
+ /* NOTE: audio endpoint extensions not accepted here;
+ * just don't include the extra bytes.
+ */
+
+ /* full/low speed descriptor, then high speed */
+ if (copy_from_user (&data->desc, buf, USB_DT_ENDPOINT_SIZE)) {
+ goto fail1;
+ }
+ if (data->desc.bLength != USB_DT_ENDPOINT_SIZE
+ || data->desc.bDescriptorType != USB_DT_ENDPOINT)
+ goto fail0;
+ if (len != USB_DT_ENDPOINT_SIZE) {
+ if (len != 2 * USB_DT_ENDPOINT_SIZE)
+ goto fail0;
+ if (copy_from_user (&data->hs_desc, buf + USB_DT_ENDPOINT_SIZE,
+ USB_DT_ENDPOINT_SIZE)) {
+ goto fail1;
+ }
+ if (data->hs_desc.bLength != USB_DT_ENDPOINT_SIZE
+ || data->hs_desc.bDescriptorType
+ != USB_DT_ENDPOINT) {
+ DBG(data->dev, "config %s, bad hs length or type\n",
+ data->name);
+ goto fail0;
+ }
+ }
+
+ spin_lock_irq (&data->dev->lock);
+ if (data->dev->state == STATE_DEV_UNBOUND) {
+ value = -ENOENT;
+ goto gone;
+ } else if ((ep = data->ep) == NULL) {
+ value = -ENODEV;
+ goto gone;
+ }
+ switch (data->dev->gadget->speed) {
+ case USB_SPEED_LOW:
+ case USB_SPEED_FULL:
+ value = usb_ep_enable (ep, &data->desc);
+ if (value == 0)
+ data->state = STATE_EP_ENABLED;
+ break;
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+ case USB_SPEED_HIGH:
+ /* fails if caller didn't provide that descriptor... */
+ value = usb_ep_enable (ep, &data->hs_desc);
+ if (value == 0)
+ data->state = STATE_EP_ENABLED;
+ break;
+#endif
+ default:
+ DBG(data->dev, "unconnected, %s init abandoned\n",
+ data->name);
+ value = -EINVAL;
+ }
+ if (value == 0) {
+ fd->f_op = &ep_io_operations;
+ value = length;
+ }
+gone:
+ spin_unlock_irq (&data->dev->lock);
+ if (value < 0) {
+fail:
+ data->desc.bDescriptorType = 0;
+ data->hs_desc.bDescriptorType = 0;
+ }
+ mutex_unlock(&data->lock);
+ return value;
+fail0:
+ value = -EINVAL;
+ goto fail;
+fail1:
+ value = -EFAULT;
+ goto fail;
+}
+
+static int
+ep_open (struct inode *inode, struct file *fd)
+{
+ struct ep_data *data = inode->i_private;
+ int value = -EBUSY;
+
+ if (mutex_lock_interruptible(&data->lock) != 0)
+ return -EINTR;
+ spin_lock_irq (&data->dev->lock);
+ if (data->dev->state == STATE_DEV_UNBOUND)
+ value = -ENOENT;
+ else if (data->state == STATE_EP_DISABLED) {
+ value = 0;
+ data->state = STATE_EP_READY;
+ get_ep (data);
+ fd->private_data = data;
+ VDEBUG (data->dev, "%s ready\n", data->name);
+ } else
+ DBG (data->dev, "%s state %d\n",
+ data->name, data->state);
+ spin_unlock_irq (&data->dev->lock);
+ mutex_unlock(&data->lock);
+ return value;
+}
+
+/* used before endpoint configuration */
+static const struct file_operations ep_config_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+
+ .open = ep_open,
+ .write = ep_config,
+ .release = ep_release,
+};
+
+/*----------------------------------------------------------------------*/
+
+/* EP0 IMPLEMENTATION can be partly in userspace.
+ *
+ * Drivers that use this facility receive various events, including
+ * control requests the kernel doesn't handle. Drivers that don't
+ * use this facility may be too simple-minded for real applications.
+ */
+
+static inline void ep0_readable (struct dev_data *dev)
+{
+ wake_up (&dev->wait);
+ kill_fasync (&dev->fasync, SIGIO, POLL_IN);
+}
+
+static void clean_req (struct usb_ep *ep, struct usb_request *req)
+{
+ struct dev_data *dev = ep->driver_data;
+
+ if (req->buf != dev->rbuf) {
+ kfree(req->buf);
+ req->buf = dev->rbuf;
+ req->dma = DMA_ADDR_INVALID;
+ }
+ req->complete = epio_complete;
+ dev->setup_out_ready = 0;
+}
+
+static void ep0_complete (struct usb_ep *ep, struct usb_request *req)
+{
+ struct dev_data *dev = ep->driver_data;
+ unsigned long flags;
+ int free = 1;
+
+ /* for control OUT, data must still get to userspace */
+ spin_lock_irqsave(&dev->lock, flags);
+ if (!dev->setup_in) {
+ dev->setup_out_error = (req->status != 0);
+ if (!dev->setup_out_error)
+ free = 0;
+ dev->setup_out_ready = 1;
+ ep0_readable (dev);
+ }
+
+ /* clean up as appropriate */
+ if (free && req->buf != &dev->rbuf)
+ clean_req (ep, req);
+ req->complete = epio_complete;
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static int setup_req (struct usb_ep *ep, struct usb_request *req, u16 len)
+{
+ struct dev_data *dev = ep->driver_data;
+
+ if (dev->setup_out_ready) {
+ DBG (dev, "ep0 request busy!\n");
+ return -EBUSY;
+ }
+ if (len > sizeof (dev->rbuf))
+ req->buf = kmalloc(len, GFP_ATOMIC);
+ if (req->buf == NULL) {
+ req->buf = dev->rbuf;
+ return -ENOMEM;
+ }
+ req->complete = ep0_complete;
+ req->length = len;
+ req->zero = 0;
+ return 0;
+}
+
+static ssize_t
+ep0_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr)
+{
+ struct dev_data *dev = fd->private_data;
+ ssize_t retval;
+ enum ep0_state state;
+
+ spin_lock_irq (&dev->lock);
+
+ /* report fd mode change before acting on it */
+ if (dev->setup_abort) {
+ dev->setup_abort = 0;
+ retval = -EIDRM;
+ goto done;
+ }
+
+ /* control DATA stage */
+ if ((state = dev->state) == STATE_DEV_SETUP) {
+
+ if (dev->setup_in) { /* stall IN */
+ VDEBUG(dev, "ep0in stall\n");
+ (void) usb_ep_set_halt (dev->gadget->ep0);
+ retval = -EL2HLT;
+ dev->state = STATE_DEV_CONNECTED;
+
+ } else if (len == 0) { /* ack SET_CONFIGURATION etc */
+ struct usb_ep *ep = dev->gadget->ep0;
+ struct usb_request *req = dev->req;
+
+ if ((retval = setup_req (ep, req, 0)) == 0)
+ retval = usb_ep_queue (ep, req, GFP_ATOMIC);
+ dev->state = STATE_DEV_CONNECTED;
+
+ /* assume that was SET_CONFIGURATION */
+ if (dev->current_config) {
+ unsigned power;
+
+ if (gadget_is_dualspeed(dev->gadget)
+ && (dev->gadget->speed
+ == USB_SPEED_HIGH))
+ power = dev->hs_config->bMaxPower;
+ else
+ power = dev->config->bMaxPower;
+ usb_gadget_vbus_draw(dev->gadget, 2 * power);
+ }
+
+ } else { /* collect OUT data */
+ if ((fd->f_flags & O_NONBLOCK) != 0
+ && !dev->setup_out_ready) {
+ retval = -EAGAIN;
+ goto done;
+ }
+ spin_unlock_irq (&dev->lock);
+ retval = wait_event_interruptible (dev->wait,
+ dev->setup_out_ready != 0);
+
+ /* FIXME state could change from under us */
+ spin_lock_irq (&dev->lock);
+ if (retval)
+ goto done;
+
+ if (dev->state != STATE_DEV_SETUP) {
+ retval = -ECANCELED;
+ goto done;
+ }
+ dev->state = STATE_DEV_CONNECTED;
+
+ if (dev->setup_out_error)
+ retval = -EIO;
+ else {
+ len = min (len, (size_t)dev->req->actual);
+// FIXME don't call this with the spinlock held ...
+ if (copy_to_user (buf, dev->req->buf, len))
+ retval = -EFAULT;
+ clean_req (dev->gadget->ep0, dev->req);
+ /* NOTE userspace can't yet choose to stall */
+ }
+ }
+ goto done;
+ }
+
+ /* else normal: return event data */
+ if (len < sizeof dev->event [0]) {
+ retval = -EINVAL;
+ goto done;
+ }
+ len -= len % sizeof (struct usb_gadgetfs_event);
+ dev->usermode_setup = 1;
+
+scan:
+ /* return queued events right away */
+ if (dev->ev_next != 0) {
+ unsigned i, n;
+
+ n = len / sizeof (struct usb_gadgetfs_event);
+ if (dev->ev_next < n)
+ n = dev->ev_next;
+
+ /* ep0 i/o has special semantics during STATE_DEV_SETUP */
+ for (i = 0; i < n; i++) {
+ if (dev->event [i].type == GADGETFS_SETUP) {
+ dev->state = STATE_DEV_SETUP;
+ n = i + 1;
+ break;
+ }
+ }
+ spin_unlock_irq (&dev->lock);
+ len = n * sizeof (struct usb_gadgetfs_event);
+ if (copy_to_user (buf, &dev->event, len))
+ retval = -EFAULT;
+ else
+ retval = len;
+ if (len > 0) {
+ /* NOTE this doesn't guard against broken drivers;
+ * concurrent ep0 readers may lose events.
+ */
+ spin_lock_irq (&dev->lock);
+ if (dev->ev_next > n) {
+ memmove(&dev->event[0], &dev->event[n],
+ sizeof (struct usb_gadgetfs_event)
+ * (dev->ev_next - n));
+ }
+ dev->ev_next -= n;
+ spin_unlock_irq (&dev->lock);
+ }
+ return retval;
+ }
+ if (fd->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto done;
+ }
+
+ switch (state) {
+ default:
+ DBG (dev, "fail %s, state %d\n", __func__, state);
+ retval = -ESRCH;
+ break;
+ case STATE_DEV_UNCONNECTED:
+ case STATE_DEV_CONNECTED:
+ spin_unlock_irq (&dev->lock);
+ DBG (dev, "%s wait\n", __func__);
+
+ /* wait for events */
+ retval = wait_event_interruptible (dev->wait,
+ dev->ev_next != 0);
+ if (retval < 0)
+ return retval;
+ spin_lock_irq (&dev->lock);
+ goto scan;
+ }
+
+done:
+ spin_unlock_irq (&dev->lock);
+ return retval;
+}
+
+static struct usb_gadgetfs_event *
+next_event (struct dev_data *dev, enum usb_gadgetfs_event_type type)
+{
+ struct usb_gadgetfs_event *event;
+ unsigned i;
+
+ switch (type) {
+ /* these events purge the queue */
+ case GADGETFS_DISCONNECT:
+ if (dev->state == STATE_DEV_SETUP)
+ dev->setup_abort = 1;
+ // FALL THROUGH
+ case GADGETFS_CONNECT:
+ dev->ev_next = 0;
+ break;
+ case GADGETFS_SETUP: /* previous request timed out */
+ case GADGETFS_SUSPEND: /* same effect */
+ /* these events can't be repeated */
+ for (i = 0; i != dev->ev_next; i++) {
+ if (dev->event [i].type != type)
+ continue;
+ DBG(dev, "discard old event[%d] %d\n", i, type);
+ dev->ev_next--;
+ if (i == dev->ev_next)
+ break;
+ /* indices start at zero, for simplicity */
+ memmove (&dev->event [i], &dev->event [i + 1],
+ sizeof (struct usb_gadgetfs_event)
+ * (dev->ev_next - i));
+ }
+ break;
+ default:
+ BUG ();
+ }
+ VDEBUG(dev, "event[%d] = %d\n", dev->ev_next, type);
+ event = &dev->event [dev->ev_next++];
+ BUG_ON (dev->ev_next > N_EVENT);
+ memset (event, 0, sizeof *event);
+ event->type = type;
+ return event;
+}
+
+static ssize_t
+ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
+{
+ struct dev_data *dev = fd->private_data;
+ ssize_t retval = -ESRCH;
+
+ spin_lock_irq (&dev->lock);
+
+ /* report fd mode change before acting on it */
+ if (dev->setup_abort) {
+ dev->setup_abort = 0;
+ retval = -EIDRM;
+
+ /* data and/or status stage for control request */
+ } else if (dev->state == STATE_DEV_SETUP) {
+
+ /* IN DATA+STATUS caller makes len <= wLength */
+ if (dev->setup_in) {
+ retval = setup_req (dev->gadget->ep0, dev->req, len);
+ if (retval == 0) {
+ dev->state = STATE_DEV_CONNECTED;
+ spin_unlock_irq (&dev->lock);
+ if (copy_from_user (dev->req->buf, buf, len))
+ retval = -EFAULT;
+ else {
+ if (len < dev->setup_wLength)
+ dev->req->zero = 1;
+ retval = usb_ep_queue (
+ dev->gadget->ep0, dev->req,
+ GFP_KERNEL);
+ }
+ if (retval < 0) {
+ spin_lock_irq (&dev->lock);
+ clean_req (dev->gadget->ep0, dev->req);
+ spin_unlock_irq (&dev->lock);
+ } else
+ retval = len;
+
+ return retval;
+ }
+
+ /* can stall some OUT transfers */
+ } else if (dev->setup_can_stall) {
+ VDEBUG(dev, "ep0out stall\n");
+ (void) usb_ep_set_halt (dev->gadget->ep0);
+ retval = -EL2HLT;
+ dev->state = STATE_DEV_CONNECTED;
+ } else {
+ DBG(dev, "bogus ep0out stall!\n");
+ }
+ } else
+ DBG (dev, "fail %s, state %d\n", __func__, dev->state);
+
+ spin_unlock_irq (&dev->lock);
+ return retval;
+}
+
+static int
+ep0_fasync (int f, struct file *fd, int on)
+{
+ struct dev_data *dev = fd->private_data;
+ // caller must F_SETOWN before signal delivery happens
+ VDEBUG (dev, "%s %s\n", __func__, on ? "on" : "off");
+ return fasync_helper (f, fd, on, &dev->fasync);
+}
+
+static struct usb_gadget_driver gadgetfs_driver;
+
+static int
+dev_release (struct inode *inode, struct file *fd)
+{
+ struct dev_data *dev = fd->private_data;
+
+ /* closing ep0 === shutdown all */
+
+ usb_gadget_unregister_driver (&gadgetfs_driver);
+
+ /* at this point "good" hardware has disconnected the
+ * device from USB; the host won't see it any more.
+ * alternatively, all host requests will time out.
+ */
+
+ kfree (dev->buf);
+ dev->buf = NULL;
+ put_dev (dev);
+
+ /* other endpoints were all decoupled from this device */
+ spin_lock_irq(&dev->lock);
+ dev->state = STATE_DEV_DISABLED;
+ spin_unlock_irq(&dev->lock);
+ return 0;
+}
+
+static unsigned int
+ep0_poll (struct file *fd, poll_table *wait)
+{
+ struct dev_data *dev = fd->private_data;
+ int mask = 0;
+
+ poll_wait(fd, &dev->wait, wait);
+
+ spin_lock_irq (&dev->lock);
+
+ /* report fd mode change before acting on it */
+ if (dev->setup_abort) {
+ dev->setup_abort = 0;
+ mask = POLLHUP;
+ goto out;
+ }
+
+ if (dev->state == STATE_DEV_SETUP) {
+ if (dev->setup_in || dev->setup_can_stall)
+ mask = POLLOUT;
+ } else {
+ if (dev->ev_next != 0)
+ mask = POLLIN;
+ }
+out:
+ spin_unlock_irq(&dev->lock);
+ return mask;
+}
+
+static long dev_ioctl (struct file *fd, unsigned code, unsigned long value)
+{
+ struct dev_data *dev = fd->private_data;
+ struct usb_gadget *gadget = dev->gadget;
+ long ret = -ENOTTY;
+
+ if (gadget->ops->ioctl)
+ ret = gadget->ops->ioctl (gadget, code, value);
+
+ return ret;
+}
+
+/* used after device configuration */
+static const struct file_operations ep0_io_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+
+ .read = ep0_read,
+ .write = ep0_write,
+ .fasync = ep0_fasync,
+ .poll = ep0_poll,
+ .unlocked_ioctl = dev_ioctl,
+ .release = dev_release,
+};
+
+/*----------------------------------------------------------------------*/
+
+/* The in-kernel gadget driver handles most ep0 issues, in particular
+ * enumerating the single configuration (as provided from user space).
+ *
+ * Unrecognized ep0 requests may be handled in user space.
+ */
+
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+static void make_qualifier (struct dev_data *dev)
+{
+ struct usb_qualifier_descriptor qual;
+ struct usb_device_descriptor *desc;
+
+ qual.bLength = sizeof qual;
+ qual.bDescriptorType = USB_DT_DEVICE_QUALIFIER;
+ qual.bcdUSB = cpu_to_le16 (0x0200);
+
+ desc = dev->dev;
+ qual.bDeviceClass = desc->bDeviceClass;
+ qual.bDeviceSubClass = desc->bDeviceSubClass;
+ qual.bDeviceProtocol = desc->bDeviceProtocol;
+
+ /* assumes ep0 uses the same value for both speeds ... */
+ qual.bMaxPacketSize0 = desc->bMaxPacketSize0;
+
+ qual.bNumConfigurations = 1;
+ qual.bRESERVED = 0;
+
+ memcpy (dev->rbuf, &qual, sizeof qual);
+}
+#endif
+
+static int
+config_buf (struct dev_data *dev, u8 type, unsigned index)
+{
+ int len;
+ int hs = 0;
+
+ /* only one configuration */
+ if (index > 0)
+ return -EINVAL;
+
+ if (gadget_is_dualspeed(dev->gadget)) {
+ hs = (dev->gadget->speed == USB_SPEED_HIGH);
+ if (type == USB_DT_OTHER_SPEED_CONFIG)
+ hs = !hs;
+ }
+ if (hs) {
+ dev->req->buf = dev->hs_config;
+ len = le16_to_cpu(dev->hs_config->wTotalLength);
+ } else {
+ dev->req->buf = dev->config;
+ len = le16_to_cpu(dev->config->wTotalLength);
+ }
+ ((u8 *)dev->req->buf) [1] = type;
+ return len;
+}
+
+static int
+gadgetfs_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+{
+ struct dev_data *dev = get_gadget_data (gadget);
+ struct usb_request *req = dev->req;
+ int value = -EOPNOTSUPP;
+ struct usb_gadgetfs_event *event;
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ spin_lock (&dev->lock);
+ dev->setup_abort = 0;
+ if (dev->state == STATE_DEV_UNCONNECTED) {
+ if (gadget_is_dualspeed(gadget)
+ && gadget->speed == USB_SPEED_HIGH
+ && dev->hs_config == NULL) {
+ spin_unlock(&dev->lock);
+ ERROR (dev, "no high speed config??\n");
+ return -EINVAL;
+ }
+
+ dev->state = STATE_DEV_CONNECTED;
+ dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket;
+
+ INFO (dev, "connected\n");
+ event = next_event (dev, GADGETFS_CONNECT);
+ event->u.speed = gadget->speed;
+ ep0_readable (dev);
+
+ /* host may have given up waiting for response. we can miss control
+ * requests handled lower down (device/endpoint status and features);
+ * then ep0_{read,write} will report the wrong status. controller
+ * driver will have aborted pending i/o.
+ */
+ } else if (dev->state == STATE_DEV_SETUP)
+ dev->setup_abort = 1;
+
+ req->buf = dev->rbuf;
+ req->dma = DMA_ADDR_INVALID;
+ req->context = NULL;
+ value = -EOPNOTSUPP;
+ switch (ctrl->bRequest) {
+
+ case USB_REQ_GET_DESCRIPTOR:
+ if (ctrl->bRequestType != USB_DIR_IN)
+ goto unrecognized;
+ switch (w_value >> 8) {
+
+ case USB_DT_DEVICE:
+ value = min (w_length, (u16) sizeof *dev->dev);
+ req->buf = dev->dev;
+ break;
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+ case USB_DT_DEVICE_QUALIFIER:
+ if (!dev->hs_config)
+ break;
+ value = min (w_length, (u16)
+ sizeof (struct usb_qualifier_descriptor));
+ make_qualifier (dev);
+ break;
+ case USB_DT_OTHER_SPEED_CONFIG:
+ // FALLTHROUGH
+#endif
+ case USB_DT_CONFIG:
+ value = config_buf (dev,
+ w_value >> 8,
+ w_value & 0xff);
+ if (value >= 0)
+ value = min (w_length, (u16) value);
+ break;
+ case USB_DT_STRING:
+ goto unrecognized;
+
+ default: // all others are errors
+ break;
+ }
+ break;
+
+ /* currently one config, two speeds */
+ case USB_REQ_SET_CONFIGURATION:
+ if (ctrl->bRequestType != 0)
+ goto unrecognized;
+ if (0 == (u8) w_value) {
+ value = 0;
+ dev->current_config = 0;
+ usb_gadget_vbus_draw(gadget, 8 /* mA */ );
+ // user mode expected to disable endpoints
+ } else {
+ u8 config, power;
+
+ if (gadget_is_dualspeed(gadget)
+ && gadget->speed == USB_SPEED_HIGH) {
+ config = dev->hs_config->bConfigurationValue;
+ power = dev->hs_config->bMaxPower;
+ } else {
+ config = dev->config->bConfigurationValue;
+ power = dev->config->bMaxPower;
+ }
+
+ if (config == (u8) w_value) {
+ value = 0;
+ dev->current_config = config;
+ usb_gadget_vbus_draw(gadget, 2 * power);
+ }
+ }
+
+ /* report SET_CONFIGURATION like any other control request,
+ * except that usermode may not stall this. the next
+ * request mustn't be allowed start until this finishes:
+ * endpoints and threads set up, etc.
+ *
+ * NOTE: older PXA hardware (before PXA 255: without UDCCFR)
+ * has bad/racey automagic that prevents synchronizing here.
+ * even kernel mode drivers often miss them.
+ */
+ if (value == 0) {
+ INFO (dev, "configuration #%d\n", dev->current_config);
+ if (dev->usermode_setup) {
+ dev->setup_can_stall = 0;
+ goto delegate;
+ }
+ }
+ break;
+
+#ifndef CONFIG_USB_GADGET_PXA25X
+ /* PXA automagically handles this request too */
+ case USB_REQ_GET_CONFIGURATION:
+ if (ctrl->bRequestType != 0x80)
+ goto unrecognized;
+ *(u8 *)req->buf = dev->current_config;
+ value = min (w_length, (u16) 1);
+ break;
+#endif
+
+ default:
+unrecognized:
+ VDEBUG (dev, "%s req%02x.%02x v%04x i%04x l%d\n",
+ dev->usermode_setup ? "delegate" : "fail",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, le16_to_cpu(ctrl->wIndex), w_length);
+
+ /* if there's an ep0 reader, don't stall */
+ if (dev->usermode_setup) {
+ dev->setup_can_stall = 1;
+delegate:
+ dev->setup_in = (ctrl->bRequestType & USB_DIR_IN)
+ ? 1 : 0;
+ dev->setup_wLength = w_length;
+ dev->setup_out_ready = 0;
+ dev->setup_out_error = 0;
+ value = 0;
+
+ /* read DATA stage for OUT right away */
+ if (unlikely (!dev->setup_in && w_length)) {
+ value = setup_req (gadget->ep0, dev->req,
+ w_length);
+ if (value < 0)
+ break;
+ value = usb_ep_queue (gadget->ep0, dev->req,
+ GFP_ATOMIC);
+ if (value < 0) {
+ clean_req (gadget->ep0, dev->req);
+ break;
+ }
+
+ /* we can't currently stall these */
+ dev->setup_can_stall = 0;
+ }
+
+ /* state changes when reader collects event */
+ event = next_event (dev, GADGETFS_SETUP);
+ event->u.setup = *ctrl;
+ ep0_readable (dev);
+ spin_unlock (&dev->lock);
+ return 0;
+ }
+ }
+
+ /* proceed with data transfer and status phases? */
+ if (value >= 0 && dev->state != STATE_DEV_SETUP) {
+ req->length = value;
+ req->zero = value < w_length;
+ value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0) {
+ DBG (dev, "ep_queue --> %d\n", value);
+ req->status = 0;
+ }
+ }
+
+ /* device stalls when value < 0 */
+ spin_unlock (&dev->lock);
+ return value;
+}
+
+static void destroy_ep_files (struct dev_data *dev)
+{
+ struct list_head *entry, *tmp;
+
+ DBG (dev, "%s %d\n", __func__, dev->state);
+
+ /* dev->state must prevent interference */
+restart:
+ spin_lock_irq (&dev->lock);
+ list_for_each_safe (entry, tmp, &dev->epfiles) {
+ struct ep_data *ep;
+ struct inode *parent;
+ struct dentry *dentry;
+
+ /* break link to FS */
+ ep = list_entry (entry, struct ep_data, epfiles);
+ list_del_init (&ep->epfiles);
+ dentry = ep->dentry;
+ ep->dentry = NULL;
+ parent = dentry->d_parent->d_inode;
+
+ /* break link to controller */
+ if (ep->state == STATE_EP_ENABLED)
+ (void) usb_ep_disable (ep->ep);
+ ep->state = STATE_EP_UNBOUND;
+ usb_ep_free_request (ep->ep, ep->req);
+ ep->ep = NULL;
+ wake_up (&ep->wait);
+ put_ep (ep);
+
+ spin_unlock_irq (&dev->lock);
+
+ /* break link to dcache */
+ mutex_lock (&parent->i_mutex);
+ d_delete (dentry);
+ dput (dentry);
+ mutex_unlock (&parent->i_mutex);
+
+ /* fds may still be open */
+ goto restart;
+ }
+ spin_unlock_irq (&dev->lock);
+}
+
+
+static struct inode *
+gadgetfs_create_file (struct super_block *sb, char const *name,
+ void *data, const struct file_operations *fops,
+ struct dentry **dentry_p);
+
+static int activate_ep_files (struct dev_data *dev)
+{
+ struct usb_ep *ep;
+ struct ep_data *data;
+
+ gadget_for_each_ep (ep, dev->gadget) {
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ goto enomem0;
+ data->state = STATE_EP_DISABLED;
+ mutex_init(&data->lock);
+ init_waitqueue_head (&data->wait);
+
+ strncpy (data->name, ep->name, sizeof (data->name) - 1);
+ atomic_set (&data->count, 1);
+ data->dev = dev;
+ get_dev (dev);
+
+ data->ep = ep;
+ ep->driver_data = data;
+
+ data->req = usb_ep_alloc_request (ep, GFP_KERNEL);
+ if (!data->req)
+ goto enomem1;
+
+ data->inode = gadgetfs_create_file (dev->sb, data->name,
+ data, &ep_config_operations,
+ &data->dentry);
+ if (!data->inode)
+ goto enomem2;
+ list_add_tail (&data->epfiles, &dev->epfiles);
+ }
+ return 0;
+
+enomem2:
+ usb_ep_free_request (ep, data->req);
+enomem1:
+ put_dev (dev);
+ kfree (data);
+enomem0:
+ DBG (dev, "%s enomem\n", __func__);
+ destroy_ep_files (dev);
+ return -ENOMEM;
+}
+
+static void
+gadgetfs_unbind (struct usb_gadget *gadget)
+{
+ struct dev_data *dev = get_gadget_data (gadget);
+
+ DBG (dev, "%s\n", __func__);
+
+ spin_lock_irq (&dev->lock);
+ dev->state = STATE_DEV_UNBOUND;
+ spin_unlock_irq (&dev->lock);
+
+ destroy_ep_files (dev);
+ gadget->ep0->driver_data = NULL;
+ set_gadget_data (gadget, NULL);
+
+ /* we've already been disconnected ... no i/o is active */
+ if (dev->req)
+ usb_ep_free_request (gadget->ep0, dev->req);
+ DBG (dev, "%s done\n", __func__);
+ put_dev (dev);
+}
+
+static struct dev_data *the_device;
+
+static int
+gadgetfs_bind (struct usb_gadget *gadget)
+{
+ struct dev_data *dev = the_device;
+
+ if (!dev)
+ return -ESRCH;
+ if (0 != strcmp (CHIP, gadget->name)) {
+ pr_err("%s expected %s controller not %s\n",
+ shortname, CHIP, gadget->name);
+ return -ENODEV;
+ }
+
+ set_gadget_data (gadget, dev);
+ dev->gadget = gadget;
+ gadget->ep0->driver_data = dev;
+ dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket;
+
+ /* preallocate control response and buffer */
+ dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL);
+ if (!dev->req)
+ goto enomem;
+ dev->req->context = NULL;
+ dev->req->complete = epio_complete;
+
+ if (activate_ep_files (dev) < 0)
+ goto enomem;
+
+ INFO (dev, "bound to %s driver\n", gadget->name);
+ spin_lock_irq(&dev->lock);
+ dev->state = STATE_DEV_UNCONNECTED;
+ spin_unlock_irq(&dev->lock);
+ get_dev (dev);
+ return 0;
+
+enomem:
+ gadgetfs_unbind (gadget);
+ return -ENOMEM;
+}
+
+static void
+gadgetfs_disconnect (struct usb_gadget *gadget)
+{
+ struct dev_data *dev = get_gadget_data (gadget);
+
+ spin_lock (&dev->lock);
+ if (dev->state == STATE_DEV_UNCONNECTED)
+ goto exit;
+ dev->state = STATE_DEV_UNCONNECTED;
+
+ INFO (dev, "disconnected\n");
+ next_event (dev, GADGETFS_DISCONNECT);
+ ep0_readable (dev);
+exit:
+ spin_unlock (&dev->lock);
+}
+
+static void
+gadgetfs_suspend (struct usb_gadget *gadget)
+{
+ struct dev_data *dev = get_gadget_data (gadget);
+
+ INFO (dev, "suspended from state %d\n", dev->state);
+ spin_lock (&dev->lock);
+ switch (dev->state) {
+ case STATE_DEV_SETUP: // VERY odd... host died??
+ case STATE_DEV_CONNECTED:
+ case STATE_DEV_UNCONNECTED:
+ next_event (dev, GADGETFS_SUSPEND);
+ ep0_readable (dev);
+ /* FALLTHROUGH */
+ default:
+ break;
+ }
+ spin_unlock (&dev->lock);
+}
+
+static struct usb_gadget_driver gadgetfs_driver = {
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+ .speed = USB_SPEED_HIGH,
+#else
+ .speed = USB_SPEED_FULL,
+#endif
+ .function = (char *) driver_desc,
+ .unbind = gadgetfs_unbind,
+ .setup = gadgetfs_setup,
+ .disconnect = gadgetfs_disconnect,
+ .suspend = gadgetfs_suspend,
+
+ .driver = {
+ .name = (char *) shortname,
+ },
+};
+
+/*----------------------------------------------------------------------*/
+
+static void gadgetfs_nop(struct usb_gadget *arg) { }
+
+static int gadgetfs_probe (struct usb_gadget *gadget)
+{
+ CHIP = gadget->name;
+ return -EISNAM;
+}
+
+static struct usb_gadget_driver probe_driver = {
+ .speed = USB_SPEED_HIGH,
+ .unbind = gadgetfs_nop,
+ .setup = (void *)gadgetfs_nop,
+ .disconnect = gadgetfs_nop,
+ .driver = {
+ .name = "nop",
+ },
+};
+
+
+/* DEVICE INITIALIZATION
+ *
+ * fd = open ("/dev/gadget/$CHIP", O_RDWR)
+ * status = write (fd, descriptors, sizeof descriptors)
+ *
+ * That write establishes the device configuration, so the kernel can
+ * bind to the controller ... guaranteeing it can handle enumeration
+ * at all necessary speeds. Descriptor order is:
+ *
+ * . message tag (u32, host order) ... for now, must be zero; it
+ * would change to support features like multi-config devices
+ * . full/low speed config ... all wTotalLength bytes (with interface,
+ * class, altsetting, endpoint, and other descriptors)
+ * . high speed config ... all descriptors, for high speed operation;
+ * this one's optional except for high-speed hardware
+ * . device descriptor
+ *
+ * Endpoints are not yet enabled. Drivers must wait until device
+ * configuration and interface altsetting changes create
+ * the need to configure (or unconfigure) them.
+ *
+ * After initialization, the device stays active for as long as that
+ * $CHIP file is open. Events must then be read from that descriptor,
+ * such as configuration notifications.
+ */
+
+static int is_valid_config (struct usb_config_descriptor *config)
+{
+ return config->bDescriptorType == USB_DT_CONFIG
+ && config->bLength == USB_DT_CONFIG_SIZE
+ && config->bConfigurationValue != 0
+ && (config->bmAttributes & USB_CONFIG_ATT_ONE) != 0
+ && (config->bmAttributes & USB_CONFIG_ATT_WAKEUP) == 0;
+ /* FIXME if gadget->is_otg, _must_ include an otg descriptor */
+ /* FIXME check lengths: walk to end */
+}
+
+static ssize_t
+dev_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
+{
+ struct dev_data *dev = fd->private_data;
+ ssize_t value = len, length = len;
+ unsigned total;
+ u32 tag;
+ char *kbuf;
+
+ if (len < (USB_DT_CONFIG_SIZE + USB_DT_DEVICE_SIZE + 4))
+ return -EINVAL;
+
+ /* we might need to change message format someday */
+ if (copy_from_user (&tag, buf, 4))
+ return -EFAULT;
+ if (tag != 0)
+ return -EINVAL;
+ buf += 4;
+ length -= 4;
+
+ kbuf = memdup_user(buf, length);
+ if (IS_ERR(kbuf))
+ return PTR_ERR(kbuf);
+
+ spin_lock_irq (&dev->lock);
+ value = -EINVAL;
+ if (dev->buf)
+ goto fail;
+ dev->buf = kbuf;
+
+ /* full or low speed config */
+ dev->config = (void *) kbuf;
+ total = le16_to_cpu(dev->config->wTotalLength);
+ if (!is_valid_config (dev->config) || total >= length)
+ goto fail;
+ kbuf += total;
+ length -= total;
+
+ /* optional high speed config */
+ if (kbuf [1] == USB_DT_CONFIG) {
+ dev->hs_config = (void *) kbuf;
+ total = le16_to_cpu(dev->hs_config->wTotalLength);
+ if (!is_valid_config (dev->hs_config) || total >= length)
+ goto fail;
+ kbuf += total;
+ length -= total;
+ }
+
+ /* could support multiple configs, using another encoding! */
+
+ /* device descriptor (tweaked for paranoia) */
+ if (length != USB_DT_DEVICE_SIZE)
+ goto fail;
+ dev->dev = (void *)kbuf;
+ if (dev->dev->bLength != USB_DT_DEVICE_SIZE
+ || dev->dev->bDescriptorType != USB_DT_DEVICE
+ || dev->dev->bNumConfigurations != 1)
+ goto fail;
+ dev->dev->bNumConfigurations = 1;
+ dev->dev->bcdUSB = cpu_to_le16 (0x0200);
+
+ /* triggers gadgetfs_bind(); then we can enumerate. */
+ spin_unlock_irq (&dev->lock);
+ value = usb_gadget_probe_driver(&gadgetfs_driver, gadgetfs_bind);
+ if (value != 0) {
+ kfree (dev->buf);
+ dev->buf = NULL;
+ } else {
+ /* at this point "good" hardware has for the first time
+ * let the USB the host see us. alternatively, if users
+ * unplug/replug that will clear all the error state.
+ *
+ * note: everything running before here was guaranteed
+ * to choke driver model style diagnostics. from here
+ * on, they can work ... except in cleanup paths that
+ * kick in after the ep0 descriptor is closed.
+ */
+ fd->f_op = &ep0_io_operations;
+ value = len;
+ }
+ return value;
+
+fail:
+ spin_unlock_irq (&dev->lock);
+ pr_debug ("%s: %s fail %Zd, %p\n", shortname, __func__, value, dev);
+ kfree (dev->buf);
+ dev->buf = NULL;
+ return value;
+}
+
+static int
+dev_open (struct inode *inode, struct file *fd)
+{
+ struct dev_data *dev = inode->i_private;
+ int value = -EBUSY;
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_DEV_DISABLED) {
+ dev->ev_next = 0;
+ dev->state = STATE_DEV_OPENED;
+ fd->private_data = dev;
+ get_dev (dev);
+ value = 0;
+ }
+ spin_unlock_irq(&dev->lock);
+ return value;
+}
+
+static const struct file_operations dev_init_operations = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+
+ .open = dev_open,
+ .write = dev_config,
+ .fasync = ep0_fasync,
+ .unlocked_ioctl = dev_ioctl,
+ .release = dev_release,
+};
+
+/*----------------------------------------------------------------------*/
+
+/* FILESYSTEM AND SUPERBLOCK OPERATIONS
+ *
+ * Mounting the filesystem creates a controller file, used first for
+ * device configuration then later for event monitoring.
+ */
+
+
+/* FIXME PAM etc could set this security policy without mount options
+ * if epfiles inherited ownership and permissons from ep0 ...
+ */
+
+static unsigned default_uid;
+static unsigned default_gid;
+static unsigned default_perm = S_IRUSR | S_IWUSR;
+
+module_param (default_uid, uint, 0644);
+module_param (default_gid, uint, 0644);
+module_param (default_perm, uint, 0644);
+
+
+static struct inode *
+gadgetfs_make_inode (struct super_block *sb,
+ void *data, const struct file_operations *fops,
+ int mode)
+{
+ struct inode *inode = new_inode (sb);
+
+ if (inode) {
+ inode->i_ino = get_next_ino();
+ inode->i_mode = mode;
+ inode->i_uid = default_uid;
+ inode->i_gid = default_gid;
+ inode->i_atime = inode->i_mtime = inode->i_ctime
+ = CURRENT_TIME;
+ inode->i_private = data;
+ inode->i_fop = fops;
+ }
+ return inode;
+}
+
+/* creates in fs root directory, so non-renamable and non-linkable.
+ * so inode and dentry are paired, until device reconfig.
+ */
+static struct inode *
+gadgetfs_create_file (struct super_block *sb, char const *name,
+ void *data, const struct file_operations *fops,
+ struct dentry **dentry_p)
+{
+ struct dentry *dentry;
+ struct inode *inode;
+
+ dentry = d_alloc_name(sb->s_root, name);
+ if (!dentry)
+ return NULL;
+
+ inode = gadgetfs_make_inode (sb, data, fops,
+ S_IFREG | (default_perm & S_IRWXUGO));
+ if (!inode) {
+ dput(dentry);
+ return NULL;
+ }
+ d_add (dentry, inode);
+ *dentry_p = dentry;
+ return inode;
+}
+
+static const struct super_operations gadget_fs_operations = {
+ .statfs = simple_statfs,
+ .drop_inode = generic_delete_inode,
+};
+
+static int
+gadgetfs_fill_super (struct super_block *sb, void *opts, int silent)
+{
+ struct inode *inode;
+ struct dentry *d;
+ struct dev_data *dev;
+
+ if (the_device)
+ return -ESRCH;
+
+ /* fake probe to determine $CHIP */
+ (void) usb_gadget_probe_driver(&probe_driver, gadgetfs_probe);
+ if (!CHIP)
+ return -ENODEV;
+
+ /* superblock */
+ sb->s_blocksize = PAGE_CACHE_SIZE;
+ sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
+ sb->s_magic = GADGETFS_MAGIC;
+ sb->s_op = &gadget_fs_operations;
+ sb->s_time_gran = 1;
+
+ /* root inode */
+ inode = gadgetfs_make_inode (sb,
+ NULL, &simple_dir_operations,
+ S_IFDIR | S_IRUGO | S_IXUGO);
+ if (!inode)
+ goto enomem0;
+ inode->i_op = &simple_dir_inode_operations;
+ if (!(d = d_alloc_root (inode)))
+ goto enomem1;
+ sb->s_root = d;
+
+ /* the ep0 file is named after the controller we expect;
+ * user mode code can use it for sanity checks, like we do.
+ */
+ dev = dev_new ();
+ if (!dev)
+ goto enomem2;
+
+ dev->sb = sb;
+ if (!gadgetfs_create_file (sb, CHIP,
+ dev, &dev_init_operations,
+ &dev->dentry))
+ goto enomem3;
+
+ /* other endpoint files are available after hardware setup,
+ * from binding to a controller.
+ */
+ the_device = dev;
+ return 0;
+
+enomem3:
+ put_dev (dev);
+enomem2:
+ dput (d);
+enomem1:
+ iput (inode);
+enomem0:
+ return -ENOMEM;
+}
+
+/* "mount -t gadgetfs path /dev/gadget" ends up here */
+static struct dentry *
+gadgetfs_mount (struct file_system_type *t, int flags,
+ const char *path, void *opts)
+{
+ return mount_single (t, flags, opts, gadgetfs_fill_super);
+}
+
+static void
+gadgetfs_kill_sb (struct super_block *sb)
+{
+ kill_litter_super (sb);
+ if (the_device) {
+ put_dev (the_device);
+ the_device = NULL;
+ }
+}
+
+/*----------------------------------------------------------------------*/
+
+static struct file_system_type gadgetfs_type = {
+ .owner = THIS_MODULE,
+ .name = shortname,
+ .mount = gadgetfs_mount,
+ .kill_sb = gadgetfs_kill_sb,
+};
+
+/*----------------------------------------------------------------------*/
+
+static int __init init (void)
+{
+ int status;
+
+ status = register_filesystem (&gadgetfs_type);
+ if (status == 0)
+ pr_info ("%s: %s, version " DRIVER_VERSION "\n",
+ shortname, driver_desc);
+ return status;
+}
+module_init (init);
+
+static void __exit cleanup (void)
+{
+ pr_debug ("unregister %s\n", shortname);
+ unregister_filesystem (&gadgetfs_type);
+}
+module_exit (cleanup);
+
diff --git a/drivers/usb/gadget/gadget_gbhc/mass_storage.c b/drivers/usb/gadget/gadget_gbhc/mass_storage.c
new file mode 100644
index 0000000..0182242
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/mass_storage.c
@@ -0,0 +1,190 @@
+/*
+ * mass_storage.c -- Mass Storage USB Gadget
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * Copyright (C) 2009 Samsung Electronics
+ * Author: Michal Nazarewicz <m.nazarewicz@samsung.com>
+ * 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 as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/*
+ * The Mass Storage Gadget acts as a USB Mass Storage device,
+ * appearing to the host as a disk drive or as a CD-ROM drive. In
+ * addition to providing an example of a genuinely useful gadget
+ * driver for a USB device, it also illustrates a technique of
+ * double-buffering for increased throughput. Last but not least, it
+ * gives an easy way to probe the behavior of the Mass Storage drivers
+ * in a USB host.
+ *
+ * Since this file serves only administrative purposes and all the
+ * business logic is implemented in f_mass_storage.* file. Read
+ * comments in this file for more detailed description.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/usb/ch9.h>
+
+
+/*-------------------------------------------------------------------------*/
+
+#define DRIVER_DESC "Mass Storage Gadget"
+#define DRIVER_VERSION "2009/09/11"
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module. So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+
+#include "composite.c"
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+#include "f_mass_storage.c"
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_device_descriptor msg_device_desc = {
+ .bLength = sizeof msg_device_desc,
+ .bDescriptorType = USB_DT_DEVICE,
+
+ .bcdUSB = cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_PER_INTERFACE,
+
+ /* Vendor and product id can be overridden by module parameters. */
+ .idVendor = cpu_to_le16(FSG_VENDOR_ID),
+ .idProduct = cpu_to_le16(FSG_PRODUCT_ID),
+ .bNumConfigurations = 1,
+};
+
+static struct usb_otg_descriptor otg_descriptor = {
+ .bLength = sizeof otg_descriptor,
+ .bDescriptorType = USB_DT_OTG,
+
+ /*
+ * REVISIT SRP-only hardware is possible, although
+ * it would not be called "OTG" ...
+ */
+ .bmAttributes = USB_OTG_SRP | USB_OTG_HNP,
+};
+
+static const struct usb_descriptor_header *otg_desc[] = {
+ (struct usb_descriptor_header *) &otg_descriptor,
+ NULL,
+};
+
+
+/****************************** Configurations ******************************/
+
+static struct fsg_module_parameters mod_data = {
+ .stall = 1
+};
+FSG_MODULE_PARAMETERS(/* no prefix */, mod_data);
+
+static unsigned long msg_registered;
+static void msg_cleanup(void);
+
+static int msg_thread_exits(struct fsg_common *common)
+{
+ msg_cleanup();
+ return 0;
+}
+
+static int __init msg_do_config(struct usb_configuration *c)
+{
+ static const struct fsg_operations ops = {
+ .thread_exits = msg_thread_exits,
+ };
+ static struct fsg_common common;
+
+ struct fsg_common *retp;
+ struct fsg_config config;
+ int ret;
+
+ if (gadget_is_otg(c->cdev->gadget)) {
+ c->descriptors = otg_desc;
+ c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+ }
+
+ fsg_config_from_params(&config, &mod_data);
+ config.ops = &ops;
+
+ retp = fsg_common_init(&common, c->cdev, &config);
+ if (IS_ERR(retp))
+ return PTR_ERR(retp);
+
+ ret = fsg_bind_config(c->cdev, c, &common);
+ fsg_common_put(&common);
+ return ret;
+}
+
+static struct usb_configuration msg_config_driver = {
+ .label = "Linux File-Backed Storage",
+ .bConfigurationValue = 1,
+ .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
+};
+
+
+/****************************** Gadget Bind ******************************/
+
+static int __init msg_bind(struct usb_composite_dev *cdev)
+{
+ int status;
+
+ status = usb_add_config(cdev, &msg_config_driver, msg_do_config);
+ if (status < 0)
+ return status;
+
+ dev_info(&cdev->gadget->dev,
+ DRIVER_DESC ", version: " DRIVER_VERSION "\n");
+ set_bit(0, &msg_registered);
+ return 0;
+}
+
+
+/****************************** Some noise ******************************/
+
+static struct usb_composite_driver msg_driver = {
+ .name = "g_mass_storage",
+ .dev = &msg_device_desc,
+ .iProduct = DRIVER_DESC,
+ .needs_serial = 1,
+};
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Michal Nazarewicz");
+MODULE_LICENSE("GPL");
+
+static int __init msg_init(void)
+{
+ return usb_composite_probe(&msg_driver, msg_bind);
+}
+module_init(msg_init);
+
+static void msg_cleanup(void)
+{
+ if (test_and_clear_bit(0, &msg_registered))
+ usb_composite_unregister(&msg_driver);
+}
+module_exit(msg_cleanup);
diff --git a/drivers/usb/gadget/gadget_gbhc/ncm.c b/drivers/usb/gadget/gadget_gbhc/ncm.c
new file mode 100644
index 0000000..99c179a
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/ncm.c
@@ -0,0 +1,248 @@
+/*
+ * ncm.c -- NCM gadget driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Contact: Yauheni Kaliuta <yauheni.kaliuta@nokia.com>
+ *
+ * The driver borrows from ether.c which is:
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+
+
+#include "u_ether.h"
+
+#define DRIVER_DESC "NCM Gadget"
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module. So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "composite.c"
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+
+#include "f_ncm.c"
+#include "u_ether.c"
+
+/*-------------------------------------------------------------------------*/
+
+/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!!
+ * Instead: allocate your own, using normal USB-IF procedures.
+ */
+
+/* Thanks to NetChip Technologies for donating this product ID.
+ * It's for devices with only CDC Ethernet configurations.
+ */
+#define CDC_VENDOR_NUM 0x0525 /* NetChip */
+#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_device_descriptor device_desc = {
+ .bLength = sizeof device_desc,
+ .bDescriptorType = USB_DT_DEVICE,
+
+ .bcdUSB = cpu_to_le16 (0x0200),
+
+ .bDeviceClass = USB_CLASS_COMM,
+ .bDeviceSubClass = 0,
+ .bDeviceProtocol = 0,
+ /* .bMaxPacketSize0 = f(hardware) */
+
+ /* Vendor and product id defaults change according to what configs
+ * we support. (As does bNumConfigurations.) These values can
+ * also be overridden by module parameters.
+ */
+ .idVendor = cpu_to_le16 (CDC_VENDOR_NUM),
+ .idProduct = cpu_to_le16 (CDC_PRODUCT_NUM),
+ /* .bcdDevice = f(hardware) */
+ /* .iManufacturer = DYNAMIC */
+ /* .iProduct = DYNAMIC */
+ /* NO SERIAL NUMBER */
+ .bNumConfigurations = 1,
+};
+
+static struct usb_otg_descriptor otg_descriptor = {
+ .bLength = sizeof otg_descriptor,
+ .bDescriptorType = USB_DT_OTG,
+
+ /* REVISIT SRP-only hardware is possible, although
+ * it would not be called "OTG" ...
+ */
+ .bmAttributes = USB_OTG_SRP | USB_OTG_HNP,
+};
+
+static const struct usb_descriptor_header *otg_desc[] = {
+ (struct usb_descriptor_header *) &otg_descriptor,
+ NULL,
+};
+
+
+/* string IDs are assigned dynamically */
+
+#define STRING_MANUFACTURER_IDX 0
+#define STRING_PRODUCT_IDX 1
+
+static char manufacturer[50];
+
+static struct usb_string strings_dev[] = {
+ [STRING_MANUFACTURER_IDX].s = manufacturer,
+ [STRING_PRODUCT_IDX].s = DRIVER_DESC,
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dev = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dev,
+};
+
+static struct usb_gadget_strings *dev_strings[] = {
+ &stringtab_dev,
+ NULL,
+};
+
+static u8 hostaddr[ETH_ALEN];
+
+/*-------------------------------------------------------------------------*/
+
+static int __init ncm_do_config(struct usb_configuration *c)
+{
+ /* FIXME alloc iConfiguration string, set it in c->strings */
+
+ if (gadget_is_otg(c->cdev->gadget)) {
+ c->descriptors = otg_desc;
+ c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
+ }
+
+ return ncm_bind_config(c, hostaddr);
+}
+
+static struct usb_configuration ncm_config_driver = {
+ /* .label = f(hardware) */
+ .label = "CDC Ethernet (NCM)",
+ .bConfigurationValue = 1,
+ /* .iConfiguration = DYNAMIC */
+ .bmAttributes = USB_CONFIG_ATT_SELFPOWER,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init gncm_bind(struct usb_composite_dev *cdev)
+{
+ int gcnum;
+ struct usb_gadget *gadget = cdev->gadget;
+ int status;
+
+ /* set up network link layer */
+ status = gether_setup(cdev->gadget, hostaddr);
+ if (status < 0)
+ return status;
+
+ gcnum = usb_gadget_controller_number(gadget);
+ if (gcnum >= 0)
+ device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum);
+ else {
+ /* We assume that can_support_ecm() tells the truth;
+ * but if the controller isn't recognized at all then
+ * that assumption is a bit more likely to be wrong.
+ */
+ dev_warn(&gadget->dev,
+ "controller '%s' not recognized; trying %s\n",
+ gadget->name,
+ ncm_config_driver.label);
+ device_desc.bcdDevice =
+ cpu_to_le16(0x0300 | 0x0099);
+ }
+
+
+ /* Allocate string descriptor numbers ... note that string
+ * contents can be overridden by the composite_dev glue.
+ */
+
+ /* device descriptor strings: manufacturer, product */
+ snprintf(manufacturer, sizeof manufacturer, "%s %s with %s",
+ init_utsname()->sysname, init_utsname()->release,
+ gadget->name);
+ status = usb_string_id(cdev);
+ if (status < 0)
+ goto fail;
+ strings_dev[STRING_MANUFACTURER_IDX].id = status;
+ device_desc.iManufacturer = status;
+
+ status = usb_string_id(cdev);
+ if (status < 0)
+ goto fail;
+ strings_dev[STRING_PRODUCT_IDX].id = status;
+ device_desc.iProduct = status;
+
+ status = usb_add_config(cdev, &ncm_config_driver,
+ ncm_do_config);
+ if (status < 0)
+ goto fail;
+
+ dev_info(&gadget->dev, "%s\n", DRIVER_DESC);
+
+ return 0;
+
+fail:
+ gether_cleanup();
+ return status;
+}
+
+static int __exit gncm_unbind(struct usb_composite_dev *cdev)
+{
+ gether_cleanup();
+ return 0;
+}
+
+static struct usb_composite_driver ncm_driver = {
+ .name = "g_ncm",
+ .dev = &device_desc,
+ .strings = dev_strings,
+ .unbind = __exit_p(gncm_unbind),
+};
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Yauheni Kaliuta");
+MODULE_LICENSE("GPL");
+
+static int __init init(void)
+{
+ return usb_composite_probe(&ncm_driver, gncm_bind);
+}
+module_init(init);
+
+static void __exit cleanup(void)
+{
+ usb_composite_unregister(&ncm_driver);
+}
+module_exit(cleanup);
diff --git a/drivers/usb/gadget/gadget_gbhc/ndis.h b/drivers/usb/gadget/gadget_gbhc/ndis.h
new file mode 100644
index 0000000..df886ce
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/ndis.h
@@ -0,0 +1,217 @@
+/*
+ * ndis.h
+ *
+ * ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de>
+ *
+ * Thanks to the cygwin development team,
+ * espacially to Casper S. Hornstrup <chorns@users.sourceforge.net>
+ *
+ * THIS SOFTWARE IS NOT COPYRIGHTED
+ *
+ * This source code is offered for use in the public domain. You may
+ * use, modify or distribute it freely.
+ *
+ * This code is distributed in the hope that it will be useful but
+ * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
+ * DISCLAIMED. This includes but is not limited to warranties of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#ifndef _LINUX_NDIS_H
+#define _LINUX_NDIS_H
+
+
+#define NDIS_STATUS_MULTICAST_FULL 0xC0010009
+#define NDIS_STATUS_MULTICAST_EXISTS 0xC001000A
+#define NDIS_STATUS_MULTICAST_NOT_FOUND 0xC001000B
+
+enum NDIS_DEVICE_POWER_STATE {
+ NdisDeviceStateUnspecified = 0,
+ NdisDeviceStateD0,
+ NdisDeviceStateD1,
+ NdisDeviceStateD2,
+ NdisDeviceStateD3,
+ NdisDeviceStateMaximum
+};
+
+struct NDIS_PM_WAKE_UP_CAPABILITIES {
+ enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp;
+ enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp;
+ enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp;
+};
+
+/* NDIS_PNP_CAPABILITIES.Flags constants */
+#define NDIS_DEVICE_WAKE_UP_ENABLE 0x00000001
+#define NDIS_DEVICE_WAKE_ON_PATTERN_MATCH_ENABLE 0x00000002
+#define NDIS_DEVICE_WAKE_ON_MAGIC_PACKET_ENABLE 0x00000004
+
+struct NDIS_PNP_CAPABILITIES {
+ __le32 Flags;
+ struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities;
+};
+
+struct NDIS_PM_PACKET_PATTERN {
+ __le32 Priority;
+ __le32 Reserved;
+ __le32 MaskSize;
+ __le32 PatternOffset;
+ __le32 PatternSize;
+ __le32 PatternFlags;
+};
+
+
+/* Required Object IDs (OIDs) */
+#define OID_GEN_SUPPORTED_LIST 0x00010101
+#define OID_GEN_HARDWARE_STATUS 0x00010102
+#define OID_GEN_MEDIA_SUPPORTED 0x00010103
+#define OID_GEN_MEDIA_IN_USE 0x00010104
+#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105
+#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106
+#define OID_GEN_LINK_SPEED 0x00010107
+#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108
+#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109
+#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A
+#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B
+#define OID_GEN_VENDOR_ID 0x0001010C
+#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D
+#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E
+#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F
+#define OID_GEN_DRIVER_VERSION 0x00010110
+#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111
+#define OID_GEN_PROTOCOL_OPTIONS 0x00010112
+#define OID_GEN_MAC_OPTIONS 0x00010113
+#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114
+#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115
+#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116
+#define OID_GEN_SUPPORTED_GUIDS 0x00010117
+#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118
+#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119
+#define OID_GEN_MACHINE_NAME 0x0001021A
+#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B
+#define OID_GEN_VLAN_ID 0x0001021C
+
+/* Optional OIDs */
+#define OID_GEN_MEDIA_CAPABILITIES 0x00010201
+#define OID_GEN_PHYSICAL_MEDIUM 0x00010202
+
+/* Required statistics OIDs */
+#define OID_GEN_XMIT_OK 0x00020101
+#define OID_GEN_RCV_OK 0x00020102
+#define OID_GEN_XMIT_ERROR 0x00020103
+#define OID_GEN_RCV_ERROR 0x00020104
+#define OID_GEN_RCV_NO_BUFFER 0x00020105
+
+/* Optional statistics OIDs */
+#define OID_GEN_DIRECTED_BYTES_XMIT 0x00020201
+#define OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202
+#define OID_GEN_MULTICAST_BYTES_XMIT 0x00020203
+#define OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204
+#define OID_GEN_BROADCAST_BYTES_XMIT 0x00020205
+#define OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206
+#define OID_GEN_DIRECTED_BYTES_RCV 0x00020207
+#define OID_GEN_DIRECTED_FRAMES_RCV 0x00020208
+#define OID_GEN_MULTICAST_BYTES_RCV 0x00020209
+#define OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A
+#define OID_GEN_BROADCAST_BYTES_RCV 0x0002020B
+#define OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C
+#define OID_GEN_RCV_CRC_ERROR 0x0002020D
+#define OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E
+#define OID_GEN_GET_TIME_CAPS 0x0002020F
+#define OID_GEN_GET_NETCARD_TIME 0x00020210
+#define OID_GEN_NETCARD_LOAD 0x00020211
+#define OID_GEN_DEVICE_PROFILE 0x00020212
+#define OID_GEN_INIT_TIME_MS 0x00020213
+#define OID_GEN_RESET_COUNTS 0x00020214
+#define OID_GEN_MEDIA_SENSE_COUNTS 0x00020215
+#define OID_GEN_FRIENDLY_NAME 0x00020216
+#define OID_GEN_MINIPORT_INFO 0x00020217
+#define OID_GEN_RESET_VERIFY_PARAMETERS 0x00020218
+
+/* IEEE 802.3 (Ethernet) OIDs */
+#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001
+
+#define OID_802_3_PERMANENT_ADDRESS 0x01010101
+#define OID_802_3_CURRENT_ADDRESS 0x01010102
+#define OID_802_3_MULTICAST_LIST 0x01010103
+#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104
+#define OID_802_3_MAC_OPTIONS 0x01010105
+#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101
+#define OID_802_3_XMIT_ONE_COLLISION 0x01020102
+#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103
+#define OID_802_3_XMIT_DEFERRED 0x01020201
+#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202
+#define OID_802_3_RCV_OVERRUN 0x01020203
+#define OID_802_3_XMIT_UNDERRUN 0x01020204
+#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205
+#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206
+#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207
+
+/* OID_GEN_MINIPORT_INFO constants */
+#define NDIS_MINIPORT_BUS_MASTER 0x00000001
+#define NDIS_MINIPORT_WDM_DRIVER 0x00000002
+#define NDIS_MINIPORT_SG_LIST 0x00000004
+#define NDIS_MINIPORT_SUPPORTS_MEDIA_QUERY 0x00000008
+#define NDIS_MINIPORT_INDICATES_PACKETS 0x00000010
+#define NDIS_MINIPORT_IGNORE_PACKET_QUEUE 0x00000020
+#define NDIS_MINIPORT_IGNORE_REQUEST_QUEUE 0x00000040
+#define NDIS_MINIPORT_IGNORE_TOKEN_RING_ERRORS 0x00000080
+#define NDIS_MINIPORT_INTERMEDIATE_DRIVER 0x00000100
+#define NDIS_MINIPORT_IS_NDIS_5 0x00000200
+#define NDIS_MINIPORT_IS_CO 0x00000400
+#define NDIS_MINIPORT_DESERIALIZE 0x00000800
+#define NDIS_MINIPORT_REQUIRES_MEDIA_POLLING 0x00001000
+#define NDIS_MINIPORT_SUPPORTS_MEDIA_SENSE 0x00002000
+#define NDIS_MINIPORT_NETBOOT_CARD 0x00004000
+#define NDIS_MINIPORT_PM_SUPPORTED 0x00008000
+#define NDIS_MINIPORT_SUPPORTS_MAC_ADDRESS_OVERWRITE 0x00010000
+#define NDIS_MINIPORT_USES_SAFE_BUFFER_APIS 0x00020000
+#define NDIS_MINIPORT_HIDDEN 0x00040000
+#define NDIS_MINIPORT_SWENUM 0x00080000
+#define NDIS_MINIPORT_SURPRISE_REMOVE_OK 0x00100000
+#define NDIS_MINIPORT_NO_HALT_ON_SUSPEND 0x00200000
+#define NDIS_MINIPORT_HARDWARE_DEVICE 0x00400000
+#define NDIS_MINIPORT_SUPPORTS_CANCEL_SEND_PACKETS 0x00800000
+#define NDIS_MINIPORT_64BITS_DMA 0x01000000
+
+#define NDIS_MEDIUM_802_3 0x00000000
+#define NDIS_MEDIUM_802_5 0x00000001
+#define NDIS_MEDIUM_FDDI 0x00000002
+#define NDIS_MEDIUM_WAN 0x00000003
+#define NDIS_MEDIUM_LOCAL_TALK 0x00000004
+#define NDIS_MEDIUM_DIX 0x00000005
+#define NDIS_MEDIUM_ARCENT_RAW 0x00000006
+#define NDIS_MEDIUM_ARCENT_878_2 0x00000007
+#define NDIS_MEDIUM_ATM 0x00000008
+#define NDIS_MEDIUM_WIRELESS_LAN 0x00000009
+#define NDIS_MEDIUM_IRDA 0x0000000A
+#define NDIS_MEDIUM_BPC 0x0000000B
+#define NDIS_MEDIUM_CO_WAN 0x0000000C
+#define NDIS_MEDIUM_1394 0x0000000D
+
+#define NDIS_PACKET_TYPE_DIRECTED 0x00000001
+#define NDIS_PACKET_TYPE_MULTICAST 0x00000002
+#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004
+#define NDIS_PACKET_TYPE_BROADCAST 0x00000008
+#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010
+#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020
+#define NDIS_PACKET_TYPE_SMT 0x00000040
+#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080
+#define NDIS_PACKET_TYPE_GROUP 0x00000100
+#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200
+#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400
+#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800
+
+#define NDIS_MEDIA_STATE_CONNECTED 0x00000000
+#define NDIS_MEDIA_STATE_DISCONNECTED 0x00000001
+
+#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA 0x00000001
+#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED 0x00000002
+#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND 0x00000004
+#define NDIS_MAC_OPTION_NO_LOOPBACK 0x00000008
+#define NDIS_MAC_OPTION_FULL_DUPLEX 0x00000010
+#define NDIS_MAC_OPTION_EOTX_INDICATION 0x00000020
+#define NDIS_MAC_OPTION_8021P_PRIORITY 0x00000040
+#define NDIS_MAC_OPTION_RESERVED 0x80000000
+
+#endif /* _LINUX_NDIS_H */
diff --git a/drivers/usb/gadget/gadget_gbhc/rndis.c b/drivers/usb/gadget/gadget_gbhc/rndis.c
new file mode 100644
index 0000000..6cea2e1
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/rndis.c
@@ -0,0 +1,1214 @@
+/*
+ * RNDIS MSG parser
+ *
+ * Authors: Benedikt Spranger, Pengutronix
+ * Robert Schwebel, Pengutronix
+ *
+ * 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.
+ *
+ * This software was originally developed in conformance with
+ * Microsoft's Remote NDIS Specification License Agreement.
+ *
+ * 03/12/2004 Kai-Uwe Bloem <linux-development@auerswald.de>
+ * Fixed message length bug in init_response
+ *
+ * 03/25/2004 Kai-Uwe Bloem <linux-development@auerswald.de>
+ * Fixed rndis_rm_hdr length bug.
+ *
+ * Copyright (C) 2004 by David Brownell
+ * updates to merge with Linux 2.6, better match RNDIS spec
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/netdevice.h>
+
+#include <asm/io.h>
+#include <asm/byteorder.h>
+#include <asm/system.h>
+#include <asm/unaligned.h>
+
+
+#undef VERBOSE_DEBUG
+
+#include "rndis.h"
+
+
+/* The driver for your USB chip needs to support ep0 OUT to work with
+ * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional).
+ *
+ * Windows hosts need an INF file like Documentation/usb/linux.inf
+ * and will be happier if you provide the host_addr module parameter.
+ */
+
+#if 0
+static int rndis_debug = 0;
+module_param (rndis_debug, int, 0);
+MODULE_PARM_DESC (rndis_debug, "enable debugging");
+#else
+#define rndis_debug 0
+#endif
+
+#define RNDIS_MAX_CONFIGS 1
+
+
+static rndis_params rndis_per_dev_params[RNDIS_MAX_CONFIGS];
+
+/* Driver Version */
+static const __le32 rndis_driver_version = cpu_to_le32(1);
+
+/* Function Prototypes */
+static rndis_resp_t *rndis_add_response(int configNr, u32 length);
+
+
+/* supported OIDs */
+static const u32 oid_supported_list[] =
+{
+ /* the general stuff */
+ OID_GEN_SUPPORTED_LIST,
+ OID_GEN_HARDWARE_STATUS,
+ OID_GEN_MEDIA_SUPPORTED,
+ OID_GEN_MEDIA_IN_USE,
+ OID_GEN_MAXIMUM_FRAME_SIZE,
+ OID_GEN_LINK_SPEED,
+ OID_GEN_TRANSMIT_BLOCK_SIZE,
+ OID_GEN_RECEIVE_BLOCK_SIZE,
+ OID_GEN_VENDOR_ID,
+ OID_GEN_VENDOR_DESCRIPTION,
+ OID_GEN_VENDOR_DRIVER_VERSION,
+ OID_GEN_CURRENT_PACKET_FILTER,
+ OID_GEN_MAXIMUM_TOTAL_SIZE,
+ OID_GEN_MEDIA_CONNECT_STATUS,
+ OID_GEN_PHYSICAL_MEDIUM,
+
+ /* the statistical stuff */
+ OID_GEN_XMIT_OK,
+ OID_GEN_RCV_OK,
+ OID_GEN_XMIT_ERROR,
+ OID_GEN_RCV_ERROR,
+ OID_GEN_RCV_NO_BUFFER,
+#ifdef RNDIS_OPTIONAL_STATS
+ OID_GEN_DIRECTED_BYTES_XMIT,
+ OID_GEN_DIRECTED_FRAMES_XMIT,
+ OID_GEN_MULTICAST_BYTES_XMIT,
+ OID_GEN_MULTICAST_FRAMES_XMIT,
+ OID_GEN_BROADCAST_BYTES_XMIT,
+ OID_GEN_BROADCAST_FRAMES_XMIT,
+ OID_GEN_DIRECTED_BYTES_RCV,
+ OID_GEN_DIRECTED_FRAMES_RCV,
+ OID_GEN_MULTICAST_BYTES_RCV,
+ OID_GEN_MULTICAST_FRAMES_RCV,
+ OID_GEN_BROADCAST_BYTES_RCV,
+ OID_GEN_BROADCAST_FRAMES_RCV,
+ OID_GEN_RCV_CRC_ERROR,
+ OID_GEN_TRANSMIT_QUEUE_LENGTH,
+#endif /* RNDIS_OPTIONAL_STATS */
+
+ /* mandatory 802.3 */
+ /* the general stuff */
+ OID_802_3_PERMANENT_ADDRESS,
+ OID_802_3_CURRENT_ADDRESS,
+ OID_802_3_MULTICAST_LIST,
+ OID_802_3_MAC_OPTIONS,
+ OID_802_3_MAXIMUM_LIST_SIZE,
+
+ /* the statistical stuff */
+ OID_802_3_RCV_ERROR_ALIGNMENT,
+ OID_802_3_XMIT_ONE_COLLISION,
+ OID_802_3_XMIT_MORE_COLLISIONS,
+#ifdef RNDIS_OPTIONAL_STATS
+ OID_802_3_XMIT_DEFERRED,
+ OID_802_3_XMIT_MAX_COLLISIONS,
+ OID_802_3_RCV_OVERRUN,
+ OID_802_3_XMIT_UNDERRUN,
+ OID_802_3_XMIT_HEARTBEAT_FAILURE,
+ OID_802_3_XMIT_TIMES_CRS_LOST,
+ OID_802_3_XMIT_LATE_COLLISIONS,
+#endif /* RNDIS_OPTIONAL_STATS */
+
+#ifdef RNDIS_PM
+ /* PM and wakeup are "mandatory" for USB, but the RNDIS specs
+ * don't say what they mean ... and the NDIS specs are often
+ * confusing and/or ambiguous in this context. (That is, more
+ * so than their specs for the other OIDs.)
+ *
+ * FIXME someone who knows what these should do, please
+ * implement them!
+ */
+
+ /* power management */
+ OID_PNP_CAPABILITIES,
+ OID_PNP_QUERY_POWER,
+ OID_PNP_SET_POWER,
+
+#ifdef RNDIS_WAKEUP
+ /* wake up host */
+ OID_PNP_ENABLE_WAKE_UP,
+ OID_PNP_ADD_WAKE_UP_PATTERN,
+ OID_PNP_REMOVE_WAKE_UP_PATTERN,
+#endif /* RNDIS_WAKEUP */
+#endif /* RNDIS_PM */
+};
+
+/* HACK: copied from net/core/dev.c to replace dev_get_stats since
+ * dev_get_stats cannot be called from atomic context */
+static void netdev_stats_to_stats64(struct rtnl_link_stats64 *stats64,
+ const struct net_device_stats *netdev_stats)
+{
+#if BITS_PER_LONG == 64
+ BUILD_BUG_ON(sizeof(*stats64) != sizeof(*netdev_stats));
+ memcpy(stats64, netdev_stats, sizeof(*stats64));
+#else
+ size_t i, n = sizeof(*stats64) / sizeof(u64);
+ const unsigned long *src = (const unsigned long *)netdev_stats;
+ u64 *dst = (u64 *)stats64;
+
+ BUILD_BUG_ON(sizeof(*netdev_stats) / sizeof(unsigned long) !=
+ sizeof(*stats64) / sizeof(u64));
+ for (i = 0; i < n; i++)
+ dst[i] = src[i];
+#endif
+}
+
+/* NDIS Functions */
+static int gen_ndis_query_resp(int configNr, u32 OID, u8 *buf,
+ unsigned buf_len, rndis_resp_t *r)
+{
+ int retval = -ENOTSUPP;
+ u32 length = 4; /* usually */
+ __le32 *outbuf;
+ int i, count;
+ rndis_query_cmplt_type *resp;
+ struct net_device *net;
+ struct rtnl_link_stats64 temp;
+ struct rtnl_link_stats64 *stats = &temp;
+
+ if (!r) return -ENOMEM;
+ resp = (rndis_query_cmplt_type *)r->buf;
+
+ if (!resp) return -ENOMEM;
+
+ if (buf_len && rndis_debug > 1) {
+ pr_debug("query OID %08x value, len %d:\n", OID, buf_len);
+ for (i = 0; i < buf_len; i += 16) {
+ pr_debug("%03d: %08x %08x %08x %08x\n", i,
+ get_unaligned_le32(&buf[i]),
+ get_unaligned_le32(&buf[i + 4]),
+ get_unaligned_le32(&buf[i + 8]),
+ get_unaligned_le32(&buf[i + 12]));
+ }
+ }
+
+ /* response goes here, right after the header */
+ outbuf = (__le32 *)&resp[1];
+ resp->InformationBufferOffset = cpu_to_le32(16);
+
+ net = rndis_per_dev_params[configNr].dev;
+ netdev_stats_to_stats64(stats, &net->stats);
+
+ switch (OID) {
+
+ /* general oids (table 4-1) */
+
+ /* mandatory */
+ case OID_GEN_SUPPORTED_LIST:
+ pr_debug("%s: OID_GEN_SUPPORTED_LIST\n", __func__);
+ length = sizeof(oid_supported_list);
+ count = length / sizeof(u32);
+ for (i = 0; i < count; i++)
+ outbuf[i] = cpu_to_le32(oid_supported_list[i]);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_HARDWARE_STATUS:
+ pr_debug("%s: OID_GEN_HARDWARE_STATUS\n", __func__);
+ /* Bogus question!
+ * Hardware must be ready to receive high level protocols.
+ * BTW:
+ * reddite ergo quae sunt Caesaris Caesari
+ * et quae sunt Dei Deo!
+ */
+ *outbuf = cpu_to_le32(0);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_MEDIA_SUPPORTED:
+ pr_debug("%s: OID_GEN_MEDIA_SUPPORTED\n", __func__);
+ *outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_MEDIA_IN_USE:
+ pr_debug("%s: OID_GEN_MEDIA_IN_USE\n", __func__);
+ /* one medium, one transport... (maybe you do it better) */
+ *outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_MAXIMUM_FRAME_SIZE:
+ pr_debug("%s: OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__);
+ if (rndis_per_dev_params[configNr].dev) {
+ *outbuf = cpu_to_le32(
+ rndis_per_dev_params[configNr].dev->mtu);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_LINK_SPEED:
+ if (rndis_debug > 1)
+ pr_debug("%s: OID_GEN_LINK_SPEED\n", __func__);
+ if (rndis_per_dev_params[configNr].media_state
+ == NDIS_MEDIA_STATE_DISCONNECTED)
+ *outbuf = cpu_to_le32(0);
+ else
+ *outbuf = cpu_to_le32(
+ rndis_per_dev_params[configNr].speed);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_TRANSMIT_BLOCK_SIZE:
+ pr_debug("%s: OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__);
+ if (rndis_per_dev_params[configNr].dev) {
+ *outbuf = cpu_to_le32(
+ rndis_per_dev_params[configNr].dev->mtu);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_RECEIVE_BLOCK_SIZE:
+ pr_debug("%s: OID_GEN_RECEIVE_BLOCK_SIZE\n", __func__);
+ if (rndis_per_dev_params[configNr].dev) {
+ *outbuf = cpu_to_le32(
+ rndis_per_dev_params[configNr].dev->mtu);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_VENDOR_ID:
+ pr_debug("%s: OID_GEN_VENDOR_ID\n", __func__);
+ *outbuf = cpu_to_le32(
+ rndis_per_dev_params[configNr].vendorID);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_VENDOR_DESCRIPTION:
+ pr_debug("%s: OID_GEN_VENDOR_DESCRIPTION\n", __func__);
+ if (rndis_per_dev_params[configNr].vendorDescr) {
+ length = strlen(rndis_per_dev_params[configNr].
+ vendorDescr);
+ memcpy(outbuf,
+ rndis_per_dev_params[configNr].vendorDescr,
+ length);
+ } else {
+ outbuf[0] = 0;
+ }
+ retval = 0;
+ break;
+
+ case OID_GEN_VENDOR_DRIVER_VERSION:
+ pr_debug("%s: OID_GEN_VENDOR_DRIVER_VERSION\n", __func__);
+ /* Created as LE */
+ *outbuf = rndis_driver_version;
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_CURRENT_PACKET_FILTER:
+ pr_debug("%s: OID_GEN_CURRENT_PACKET_FILTER\n", __func__);
+ *outbuf = cpu_to_le32(*rndis_per_dev_params[configNr].filter);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_MAXIMUM_TOTAL_SIZE:
+ pr_debug("%s: OID_GEN_MAXIMUM_TOTAL_SIZE\n", __func__);
+ *outbuf = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_GEN_MEDIA_CONNECT_STATUS:
+ if (rndis_debug > 1)
+ pr_debug("%s: OID_GEN_MEDIA_CONNECT_STATUS\n", __func__);
+ *outbuf = cpu_to_le32(rndis_per_dev_params[configNr]
+ .media_state);
+ retval = 0;
+ break;
+
+ case OID_GEN_PHYSICAL_MEDIUM:
+ pr_debug("%s: OID_GEN_PHYSICAL_MEDIUM\n", __func__);
+ *outbuf = cpu_to_le32(0);
+ retval = 0;
+ break;
+
+ /* The RNDIS specification is incomplete/wrong. Some versions
+ * of MS-Windows expect OIDs that aren't specified there. Other
+ * versions emit undefined RNDIS messages. DOCUMENT ALL THESE!
+ */
+ case OID_GEN_MAC_OPTIONS: /* from WinME */
+ pr_debug("%s: OID_GEN_MAC_OPTIONS\n", __func__);
+ *outbuf = cpu_to_le32(
+ NDIS_MAC_OPTION_RECEIVE_SERIALIZED
+ | NDIS_MAC_OPTION_FULL_DUPLEX);
+ retval = 0;
+ break;
+
+ /* statistics OIDs (table 4-2) */
+
+ /* mandatory */
+ case OID_GEN_XMIT_OK:
+ if (rndis_debug > 1)
+ pr_debug("%s: OID_GEN_XMIT_OK\n", __func__);
+ if (stats) {
+ *outbuf = cpu_to_le32(stats->tx_packets
+ - stats->tx_errors - stats->tx_dropped);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_RCV_OK:
+ if (rndis_debug > 1)
+ pr_debug("%s: OID_GEN_RCV_OK\n", __func__);
+ if (stats) {
+ *outbuf = cpu_to_le32(stats->rx_packets
+ - stats->rx_errors - stats->rx_dropped);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_XMIT_ERROR:
+ if (rndis_debug > 1)
+ pr_debug("%s: OID_GEN_XMIT_ERROR\n", __func__);
+ if (stats) {
+ *outbuf = cpu_to_le32(stats->tx_errors);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_RCV_ERROR:
+ if (rndis_debug > 1)
+ pr_debug("%s: OID_GEN_RCV_ERROR\n", __func__);
+ if (stats) {
+ *outbuf = cpu_to_le32(stats->rx_errors);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_GEN_RCV_NO_BUFFER:
+ pr_debug("%s: OID_GEN_RCV_NO_BUFFER\n", __func__);
+ if (stats) {
+ *outbuf = cpu_to_le32(stats->rx_dropped);
+ retval = 0;
+ }
+ break;
+
+ /* ieee802.3 OIDs (table 4-3) */
+
+ /* mandatory */
+ case OID_802_3_PERMANENT_ADDRESS:
+ pr_debug("%s: OID_802_3_PERMANENT_ADDRESS\n", __func__);
+ if (rndis_per_dev_params[configNr].dev) {
+ length = ETH_ALEN;
+ memcpy(outbuf,
+ rndis_per_dev_params[configNr].host_mac,
+ length);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_802_3_CURRENT_ADDRESS:
+ pr_debug("%s: OID_802_3_CURRENT_ADDRESS\n", __func__);
+ if (rndis_per_dev_params[configNr].dev) {
+ length = ETH_ALEN;
+ memcpy(outbuf,
+ rndis_per_dev_params [configNr].host_mac,
+ length);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_802_3_MULTICAST_LIST:
+ pr_debug("%s: OID_802_3_MULTICAST_LIST\n", __func__);
+ /* Multicast base address only */
+ *outbuf = cpu_to_le32(0xE0000000);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_802_3_MAXIMUM_LIST_SIZE:
+ pr_debug("%s: OID_802_3_MAXIMUM_LIST_SIZE\n", __func__);
+ /* Multicast base address only */
+ *outbuf = cpu_to_le32(1);
+ retval = 0;
+ break;
+
+ case OID_802_3_MAC_OPTIONS:
+ pr_debug("%s: OID_802_3_MAC_OPTIONS\n", __func__);
+ *outbuf = cpu_to_le32(0);
+ retval = 0;
+ break;
+
+ /* ieee802.3 statistics OIDs (table 4-4) */
+
+ /* mandatory */
+ case OID_802_3_RCV_ERROR_ALIGNMENT:
+ pr_debug("%s: OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__);
+ if (stats) {
+ *outbuf = cpu_to_le32(stats->rx_frame_errors);
+ retval = 0;
+ }
+ break;
+
+ /* mandatory */
+ case OID_802_3_XMIT_ONE_COLLISION:
+ pr_debug("%s: OID_802_3_XMIT_ONE_COLLISION\n", __func__);
+ *outbuf = cpu_to_le32(0);
+ retval = 0;
+ break;
+
+ /* mandatory */
+ case OID_802_3_XMIT_MORE_COLLISIONS:
+ pr_debug("%s: OID_802_3_XMIT_MORE_COLLISIONS\n", __func__);
+ *outbuf = cpu_to_le32(0);
+ retval = 0;
+ break;
+
+ default:
+ pr_warning("%s: query unknown OID 0x%08X\n",
+ __func__, OID);
+ }
+ if (retval < 0)
+ length = 0;
+
+ resp->InformationBufferLength = cpu_to_le32(length);
+ r->length = length + sizeof(*resp);
+ resp->MessageLength = cpu_to_le32(r->length);
+ return retval;
+}
+
+static int gen_ndis_set_resp(u8 configNr, u32 OID, u8 *buf, u32 buf_len,
+ rndis_resp_t *r)
+{
+ rndis_set_cmplt_type *resp;
+ int i, retval = -ENOTSUPP;
+ struct rndis_params *params;
+
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_set_cmplt_type *)r->buf;
+ if (!resp)
+ return -ENOMEM;
+
+ if (buf_len && rndis_debug > 1) {
+ pr_debug("set OID %08x value, len %d:\n", OID, buf_len);
+ for (i = 0; i < buf_len; i += 16) {
+ pr_debug("%03d: %08x %08x %08x %08x\n", i,
+ get_unaligned_le32(&buf[i]),
+ get_unaligned_le32(&buf[i + 4]),
+ get_unaligned_le32(&buf[i + 8]),
+ get_unaligned_le32(&buf[i + 12]));
+ }
+ }
+
+ params = &rndis_per_dev_params[configNr];
+ switch (OID) {
+ case OID_GEN_CURRENT_PACKET_FILTER:
+
+ /* these NDIS_PACKET_TYPE_* bitflags are shared with
+ * cdc_filter; it's not RNDIS-specific
+ * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in:
+ * PROMISCUOUS, DIRECTED,
+ * MULTICAST, ALL_MULTICAST, BROADCAST
+ */
+ *params->filter = (u16)get_unaligned_le32(buf);
+ pr_debug("%s: OID_GEN_CURRENT_PACKET_FILTER %08x\n",
+ __func__, *params->filter);
+
+ /* this call has a significant side effect: it's
+ * what makes the packet flow start and stop, like
+ * activating the CDC Ethernet altsetting.
+ */
+ retval = 0;
+ if (*params->filter) {
+ params->state = RNDIS_DATA_INITIALIZED;
+ netif_carrier_on(params->dev);
+ if (netif_running(params->dev))
+ netif_wake_queue(params->dev);
+ } else {
+ params->state = RNDIS_INITIALIZED;
+ netif_carrier_off(params->dev);
+ netif_stop_queue(params->dev);
+ }
+ break;
+
+ case OID_802_3_MULTICAST_LIST:
+ /* I think we can ignore this */
+ pr_debug("%s: OID_802_3_MULTICAST_LIST\n", __func__);
+ retval = 0;
+ break;
+
+ default:
+ pr_warning("%s: set unknown OID 0x%08X, size %d\n",
+ __func__, OID, buf_len);
+ }
+
+ return retval;
+}
+
+/*
+ * Response Functions
+ */
+
+static int rndis_init_response(int configNr, rndis_init_msg_type *buf)
+{
+ rndis_init_cmplt_type *resp;
+ rndis_resp_t *r;
+ struct rndis_params *params = rndis_per_dev_params + configNr;
+
+ if (!params->dev)
+ return -ENOTSUPP;
+
+ r = rndis_add_response(configNr, sizeof(rndis_init_cmplt_type));
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_init_cmplt_type *)r->buf;
+
+ resp->MessageType = cpu_to_le32(REMOTE_NDIS_INITIALIZE_CMPLT);
+ resp->MessageLength = cpu_to_le32(52);
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+ resp->MajorVersion = cpu_to_le32(RNDIS_MAJOR_VERSION);
+ resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION);
+ resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS);
+ resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3);
+ resp->MaxPacketsPerTransfer = cpu_to_le32(1);
+ resp->MaxTransferSize = cpu_to_le32(
+ params->dev->mtu
+ + sizeof(struct ethhdr)
+ + sizeof(struct rndis_packet_msg_type)
+ + 22);
+ resp->PacketAlignmentFactor = cpu_to_le32(0);
+ resp->AFListOffset = cpu_to_le32(0);
+ resp->AFListSize = cpu_to_le32(0);
+
+ params->resp_avail(params->v);
+ return 0;
+}
+
+static int rndis_query_response(int configNr, rndis_query_msg_type *buf)
+{
+ rndis_query_cmplt_type *resp;
+ rndis_resp_t *r;
+ struct rndis_params *params = rndis_per_dev_params + configNr;
+
+ /* pr_debug("%s: OID = %08X\n", __func__, cpu_to_le32(buf->OID)); */
+ if (!params->dev)
+ return -ENOTSUPP;
+
+ /*
+ * we need more memory:
+ * gen_ndis_query_resp expects enough space for
+ * rndis_query_cmplt_type followed by data.
+ * oid_supported_list is the largest data reply
+ */
+ r = rndis_add_response(configNr,
+ sizeof(oid_supported_list) + sizeof(rndis_query_cmplt_type));
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_query_cmplt_type *)r->buf;
+
+ resp->MessageType = cpu_to_le32(REMOTE_NDIS_QUERY_CMPLT);
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+
+ if (gen_ndis_query_resp(configNr, le32_to_cpu(buf->OID),
+ le32_to_cpu(buf->InformationBufferOffset)
+ + 8 + (u8 *)buf,
+ le32_to_cpu(buf->InformationBufferLength),
+ r)) {
+ /* OID not supported */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
+ resp->MessageLength = cpu_to_le32(sizeof *resp);
+ resp->InformationBufferLength = cpu_to_le32(0);
+ resp->InformationBufferOffset = cpu_to_le32(0);
+ } else
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+ params->resp_avail(params->v);
+ return 0;
+}
+
+static int rndis_set_response(int configNr, rndis_set_msg_type *buf)
+{
+ u32 BufLength, BufOffset;
+ rndis_set_cmplt_type *resp;
+ rndis_resp_t *r;
+ struct rndis_params *params = rndis_per_dev_params + configNr;
+
+ r = rndis_add_response(configNr, sizeof(rndis_set_cmplt_type));
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_set_cmplt_type *)r->buf;
+
+ BufLength = le32_to_cpu(buf->InformationBufferLength);
+ BufOffset = le32_to_cpu(buf->InformationBufferOffset);
+
+#ifdef VERBOSE_DEBUG
+ pr_debug("%s: Length: %d\n", __func__, BufLength);
+ pr_debug("%s: Offset: %d\n", __func__, BufOffset);
+ pr_debug("%s: InfoBuffer: ", __func__);
+
+ for (i = 0; i < BufLength; i++) {
+ pr_debug("%02x ", *(((u8 *) buf) + i + 8 + BufOffset));
+ }
+
+ pr_debug("\n");
+#endif
+
+ resp->MessageType = cpu_to_le32(REMOTE_NDIS_SET_CMPLT);
+ resp->MessageLength = cpu_to_le32(16);
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ if (gen_ndis_set_resp(configNr, le32_to_cpu(buf->OID),
+ ((u8 *)buf) + 8 + BufOffset, BufLength, r))
+ resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED);
+ else
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+ params->resp_avail(params->v);
+ return 0;
+}
+
+static int rndis_reset_response(int configNr, rndis_reset_msg_type *buf)
+{
+ rndis_reset_cmplt_type *resp;
+ rndis_resp_t *r;
+ struct rndis_params *params = rndis_per_dev_params + configNr;
+
+ r = rndis_add_response(configNr, sizeof(rndis_reset_cmplt_type));
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_reset_cmplt_type *)r->buf;
+
+ resp->MessageType = cpu_to_le32(REMOTE_NDIS_RESET_CMPLT);
+ resp->MessageLength = cpu_to_le32(16);
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+ /* resent information */
+ resp->AddressingReset = cpu_to_le32(1);
+
+ params->resp_avail(params->v);
+ return 0;
+}
+
+static int rndis_keepalive_response(int configNr,
+ rndis_keepalive_msg_type *buf)
+{
+ rndis_keepalive_cmplt_type *resp;
+ rndis_resp_t *r;
+ struct rndis_params *params = rndis_per_dev_params + configNr;
+
+ /* host "should" check only in RNDIS_DATA_INITIALIZED state */
+
+ r = rndis_add_response(configNr, sizeof(rndis_keepalive_cmplt_type));
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_keepalive_cmplt_type *)r->buf;
+
+ resp->MessageType = cpu_to_le32(
+ REMOTE_NDIS_KEEPALIVE_CMPLT);
+ resp->MessageLength = cpu_to_le32(16);
+ resp->RequestID = buf->RequestID; /* Still LE in msg buffer */
+ resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS);
+
+ params->resp_avail(params->v);
+ return 0;
+}
+
+
+/*
+ * Device to Host Comunication
+ */
+static int rndis_indicate_status_msg(int configNr, u32 status)
+{
+ rndis_indicate_status_msg_type *resp;
+ rndis_resp_t *r;
+ struct rndis_params *params = rndis_per_dev_params + configNr;
+
+ if (params->state == RNDIS_UNINITIALIZED)
+ return -ENOTSUPP;
+
+ r = rndis_add_response(configNr,
+ sizeof(rndis_indicate_status_msg_type));
+ if (!r)
+ return -ENOMEM;
+ resp = (rndis_indicate_status_msg_type *)r->buf;
+
+ resp->MessageType = cpu_to_le32(REMOTE_NDIS_INDICATE_STATUS_MSG);
+ resp->MessageLength = cpu_to_le32(20);
+ resp->Status = cpu_to_le32(status);
+ resp->StatusBufferLength = cpu_to_le32(0);
+ resp->StatusBufferOffset = cpu_to_le32(0);
+
+ params->resp_avail(params->v);
+ return 0;
+}
+
+int rndis_signal_connect(int configNr)
+{
+ rndis_per_dev_params[configNr].media_state
+ = NDIS_MEDIA_STATE_CONNECTED;
+ return rndis_indicate_status_msg(configNr,
+ RNDIS_STATUS_MEDIA_CONNECT);
+}
+
+int rndis_signal_disconnect(int configNr)
+{
+ rndis_per_dev_params[configNr].media_state
+ = NDIS_MEDIA_STATE_DISCONNECTED;
+ return rndis_indicate_status_msg(configNr,
+ RNDIS_STATUS_MEDIA_DISCONNECT);
+}
+
+void rndis_uninit(int configNr)
+{
+ u8 *buf;
+ u32 length;
+
+ if (configNr >= RNDIS_MAX_CONFIGS)
+ return;
+ rndis_per_dev_params[configNr].state = RNDIS_UNINITIALIZED;
+
+ /* drain the response queue */
+ while ((buf = rndis_get_next_response(configNr, &length)))
+ rndis_free_response(configNr, buf);
+}
+
+void rndis_set_host_mac(int configNr, const u8 *addr)
+{
+ rndis_per_dev_params[configNr].host_mac = addr;
+}
+
+/*
+ * Message Parser
+ */
+int rndis_msg_parser(u8 configNr, u8 *buf)
+{
+ u32 MsgType, MsgLength;
+ __le32 *tmp;
+ struct rndis_params *params;
+
+ if (!buf)
+ return -ENOMEM;
+
+ tmp = (__le32 *)buf;
+ MsgType = get_unaligned_le32(tmp++);
+ MsgLength = get_unaligned_le32(tmp++);
+
+ if (configNr >= RNDIS_MAX_CONFIGS)
+ return -ENOTSUPP;
+ params = &rndis_per_dev_params[configNr];
+
+ /* NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for
+ * rx/tx statistics and link status, in addition to KEEPALIVE traffic
+ * and normal HC level polling to see if there's any IN traffic.
+ */
+
+ /* For USB: responses may take up to 10 seconds */
+ switch (MsgType) {
+ case REMOTE_NDIS_INITIALIZE_MSG:
+ pr_debug("%s: REMOTE_NDIS_INITIALIZE_MSG\n",
+ __func__);
+ params->state = RNDIS_INITIALIZED;
+ return rndis_init_response(configNr,
+ (rndis_init_msg_type *)buf);
+
+ case REMOTE_NDIS_HALT_MSG:
+ pr_debug("%s: REMOTE_NDIS_HALT_MSG\n",
+ __func__);
+ params->state = RNDIS_UNINITIALIZED;
+ if (params->dev) {
+ netif_carrier_off(params->dev);
+ netif_stop_queue(params->dev);
+ }
+ return 0;
+
+ case REMOTE_NDIS_QUERY_MSG:
+ return rndis_query_response(configNr,
+ (rndis_query_msg_type *)buf);
+
+ case REMOTE_NDIS_SET_MSG:
+ return rndis_set_response(configNr,
+ (rndis_set_msg_type *)buf);
+
+ case REMOTE_NDIS_RESET_MSG:
+ pr_debug("%s: REMOTE_NDIS_RESET_MSG\n",
+ __func__);
+ return rndis_reset_response(configNr,
+ (rndis_reset_msg_type *)buf);
+
+ case REMOTE_NDIS_KEEPALIVE_MSG:
+ /* For USB: host does this every 5 seconds */
+ if (rndis_debug > 1)
+ pr_debug("%s: REMOTE_NDIS_KEEPALIVE_MSG\n",
+ __func__);
+ return rndis_keepalive_response(configNr,
+ (rndis_keepalive_msg_type *)
+ buf);
+
+ default:
+ /* At least Windows XP emits some undefined RNDIS messages.
+ * In one case those messages seemed to relate to the host
+ * suspending itself.
+ */
+ pr_warning("%s: unknown RNDIS message 0x%08X len %d\n",
+ __func__, MsgType, MsgLength);
+ {
+ unsigned i;
+ for (i = 0; i < MsgLength; i += 16) {
+ pr_debug("%03d: "
+ " %02x %02x %02x %02x"
+ " %02x %02x %02x %02x"
+ " %02x %02x %02x %02x"
+ " %02x %02x %02x %02x"
+ "\n",
+ i,
+ buf[i], buf [i+1],
+ buf[i+2], buf[i+3],
+ buf[i+4], buf [i+5],
+ buf[i+6], buf[i+7],
+ buf[i+8], buf [i+9],
+ buf[i+10], buf[i+11],
+ buf[i+12], buf [i+13],
+ buf[i+14], buf[i+15]);
+ }
+ }
+ break;
+ }
+
+ return -ENOTSUPP;
+}
+
+int rndis_register(void (*resp_avail)(void *v), void *v)
+{
+ u8 i;
+
+ if (!resp_avail)
+ return -EINVAL;
+
+ for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
+ if (!rndis_per_dev_params[i].used) {
+ rndis_per_dev_params[i].used = 1;
+ rndis_per_dev_params[i].resp_avail = resp_avail;
+ rndis_per_dev_params[i].v = v;
+ pr_debug("%s: configNr = %d\n", __func__, i);
+ return i;
+ }
+ }
+ pr_debug("failed\n");
+
+ return -ENODEV;
+}
+
+void rndis_deregister(int configNr)
+{
+ pr_debug("%s:\n", __func__);
+
+ if (configNr >= RNDIS_MAX_CONFIGS) return;
+ rndis_per_dev_params[configNr].used = 0;
+}
+
+int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter)
+{
+ pr_debug("%s:\n", __func__);
+ if (!dev)
+ return -EINVAL;
+ if (configNr >= RNDIS_MAX_CONFIGS) return -1;
+
+ rndis_per_dev_params[configNr].dev = dev;
+ rndis_per_dev_params[configNr].filter = cdc_filter;
+
+ return 0;
+}
+
+int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr)
+{
+ pr_debug("%s:\n", __func__);
+ if (!vendorDescr) return -1;
+ if (configNr >= RNDIS_MAX_CONFIGS) return -1;
+
+ rndis_per_dev_params[configNr].vendorID = vendorID;
+ rndis_per_dev_params[configNr].vendorDescr = vendorDescr;
+
+ return 0;
+}
+
+int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed)
+{
+ pr_debug("%s: %u %u\n", __func__, medium, speed);
+ if (configNr >= RNDIS_MAX_CONFIGS) return -1;
+
+ rndis_per_dev_params[configNr].medium = medium;
+ rndis_per_dev_params[configNr].speed = speed;
+
+ return 0;
+}
+
+void rndis_add_hdr(struct sk_buff *skb)
+{
+ struct rndis_packet_msg_type *header;
+
+ if (!skb)
+ return;
+ header = (void *)skb_push(skb, sizeof(*header));
+ memset(header, 0, sizeof *header);
+ header->MessageType = cpu_to_le32(REMOTE_NDIS_PACKET_MSG);
+ header->MessageLength = cpu_to_le32(skb->len);
+ header->DataOffset = cpu_to_le32(36);
+ header->DataLength = cpu_to_le32(skb->len - sizeof(*header));
+}
+
+void rndis_free_response(int configNr, u8 *buf)
+{
+ rndis_resp_t *r;
+ struct list_head *act, *tmp;
+
+ list_for_each_safe(act, tmp,
+ &(rndis_per_dev_params[configNr].resp_queue))
+ {
+ r = list_entry(act, rndis_resp_t, list);
+ if (r && r->buf == buf) {
+ list_del(&r->list);
+ kfree(r);
+ }
+ }
+}
+
+u8 *rndis_get_next_response(int configNr, u32 *length)
+{
+ rndis_resp_t *r;
+ struct list_head *act, *tmp;
+
+ if (!length) return NULL;
+
+ list_for_each_safe(act, tmp,
+ &(rndis_per_dev_params[configNr].resp_queue))
+ {
+ r = list_entry(act, rndis_resp_t, list);
+ if (!r->send) {
+ r->send = 1;
+ *length = r->length;
+ return r->buf;
+ }
+ }
+
+ return NULL;
+}
+
+static rndis_resp_t *rndis_add_response(int configNr, u32 length)
+{
+ rndis_resp_t *r;
+
+ /* NOTE: this gets copied into ether.c USB_BUFSIZ bytes ... */
+ r = kmalloc(sizeof(rndis_resp_t) + length, GFP_ATOMIC);
+ if (!r) return NULL;
+
+ r->buf = (u8 *)(r + 1);
+ r->length = length;
+ r->send = 0;
+
+ list_add_tail(&r->list,
+ &(rndis_per_dev_params[configNr].resp_queue));
+ return r;
+}
+
+int rndis_rm_hdr(struct gether *port,
+ struct sk_buff *skb,
+ struct sk_buff_head *list)
+{
+ /* tmp points to a struct rndis_packet_msg_type */
+ __le32 *tmp = (void *)skb->data;
+
+ /* MessageType, MessageLength */
+ if (cpu_to_le32(REMOTE_NDIS_PACKET_MSG)
+ != get_unaligned(tmp++)) {
+ dev_kfree_skb_any(skb);
+ return -EINVAL;
+ }
+ tmp++;
+
+ /* DataOffset, DataLength */
+ if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) {
+ dev_kfree_skb_any(skb);
+ return -EOVERFLOW;
+ }
+ skb_trim(skb, get_unaligned_le32(tmp++));
+
+ skb_queue_tail(list, skb);
+ return 0;
+}
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+
+static int rndis_proc_show(struct seq_file *m, void *v)
+{
+ rndis_params *param = m->private;
+
+ seq_printf(m,
+ "Config Nr. %d\n"
+ "used : %s\n"
+ "state : %s\n"
+ "medium : 0x%08X\n"
+ "speed : %d\n"
+ "cable : %s\n"
+ "vendor ID : 0x%08X\n"
+ "vendor : %s\n",
+ param->confignr, (param->used) ? "y" : "n",
+ ({ char *s = "?";
+ switch (param->state) {
+ case RNDIS_UNINITIALIZED:
+ s = "RNDIS_UNINITIALIZED"; break;
+ case RNDIS_INITIALIZED:
+ s = "RNDIS_INITIALIZED"; break;
+ case RNDIS_DATA_INITIALIZED:
+ s = "RNDIS_DATA_INITIALIZED"; break;
+ }; s; }),
+ param->medium,
+ (param->media_state) ? 0 : param->speed*100,
+ (param->media_state) ? "disconnected" : "connected",
+ param->vendorID, param->vendorDescr);
+ return 0;
+}
+
+static ssize_t rndis_proc_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ rndis_params *p = PDE(file->f_path.dentry->d_inode)->data;
+ u32 speed = 0;
+ int i, fl_speed = 0;
+
+ for (i = 0; i < count; i++) {
+ char c;
+ if (get_user(c, buffer))
+ return -EFAULT;
+ switch (c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ fl_speed = 1;
+ speed = speed * 10 + c - '0';
+ break;
+ case 'C':
+ case 'c':
+ rndis_signal_connect(p->confignr);
+ break;
+ case 'D':
+ case 'd':
+ rndis_signal_disconnect(p->confignr);
+ break;
+ default:
+ if (fl_speed) p->speed = speed;
+ else pr_debug("%c is not valid\n", c);
+ break;
+ }
+
+ buffer++;
+ }
+
+ return count;
+}
+
+static int rndis_proc_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rndis_proc_show, PDE(inode)->data);
+}
+
+static const struct file_operations rndis_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = rndis_proc_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = rndis_proc_write,
+};
+
+#define NAME_TEMPLATE "driver/rndis-%03d"
+
+static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS];
+
+#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
+
+
+int rndis_init(void)
+{
+ u8 i;
+
+ for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+ char name [20];
+
+ sprintf(name, NAME_TEMPLATE, i);
+ rndis_connect_state[i] = proc_create_data(name, 0660, NULL,
+ &rndis_proc_fops,
+ (void *)(rndis_per_dev_params + i));
+ if (!rndis_connect_state[i]) {
+ pr_debug("%s: remove entries", __func__);
+ while (i) {
+ sprintf(name, NAME_TEMPLATE, --i);
+ remove_proc_entry(name, NULL);
+ }
+ pr_debug("\n");
+ return -EIO;
+ }
+#endif
+ rndis_per_dev_params[i].confignr = i;
+ rndis_per_dev_params[i].used = 0;
+ rndis_per_dev_params[i].state = RNDIS_UNINITIALIZED;
+ rndis_per_dev_params[i].media_state
+ = NDIS_MEDIA_STATE_DISCONNECTED;
+ INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue));
+ }
+
+ return 0;
+}
+
+void rndis_exit(void)
+{
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+ u8 i;
+ char name[20];
+
+ for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
+ sprintf(name, NAME_TEMPLATE, i);
+ remove_proc_entry(name, NULL);
+ }
+#endif
+}
diff --git a/drivers/usb/gadget/gadget_gbhc/rndis.h b/drivers/usb/gadget/gadget_gbhc/rndis.h
new file mode 100644
index 0000000..907c330
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/rndis.h
@@ -0,0 +1,268 @@
+/*
+ * RNDIS Definitions for Remote NDIS
+ *
+ * Authors: Benedikt Spranger, Pengutronix
+ * Robert Schwebel, Pengutronix
+ *
+ * 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.
+ *
+ * This software was originally developed in conformance with
+ * Microsoft's Remote NDIS Specification License Agreement.
+ */
+
+#ifndef _LINUX_RNDIS_H
+#define _LINUX_RNDIS_H
+
+#include "ndis.h"
+
+#define RNDIS_MAXIMUM_FRAME_SIZE 1518
+#define RNDIS_MAX_TOTAL_SIZE 1558
+
+/* Remote NDIS Versions */
+#define RNDIS_MAJOR_VERSION 1
+#define RNDIS_MINOR_VERSION 0
+
+/* Status Values */
+#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */
+#define RNDIS_STATUS_FAILURE 0xC0000001U /* Unspecified error */
+#define RNDIS_STATUS_INVALID_DATA 0xC0010015U /* Invalid data */
+#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBU /* Unsupported request */
+#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BU /* Device connected */
+#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CU /* Device disconnected */
+/* For all not specified status messages:
+ * RNDIS_STATUS_Xxx -> NDIS_STATUS_Xxx
+ */
+
+/* Message Set for Connectionless (802.3) Devices */
+#define REMOTE_NDIS_PACKET_MSG 0x00000001U
+#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002U /* Initialize device */
+#define REMOTE_NDIS_HALT_MSG 0x00000003U
+#define REMOTE_NDIS_QUERY_MSG 0x00000004U
+#define REMOTE_NDIS_SET_MSG 0x00000005U
+#define REMOTE_NDIS_RESET_MSG 0x00000006U
+#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007U
+#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008U
+
+/* Message completion */
+#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002U
+#define REMOTE_NDIS_QUERY_CMPLT 0x80000004U
+#define REMOTE_NDIS_SET_CMPLT 0x80000005U
+#define REMOTE_NDIS_RESET_CMPLT 0x80000006U
+#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008U
+
+/* Device Flags */
+#define RNDIS_DF_CONNECTIONLESS 0x00000001U
+#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002U
+
+#define RNDIS_MEDIUM_802_3 0x00000000U
+
+/* from drivers/net/sk98lin/h/skgepnmi.h */
+#define OID_PNP_CAPABILITIES 0xFD010100
+#define OID_PNP_SET_POWER 0xFD010101
+#define OID_PNP_QUERY_POWER 0xFD010102
+#define OID_PNP_ADD_WAKE_UP_PATTERN 0xFD010103
+#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xFD010104
+#define OID_PNP_ENABLE_WAKE_UP 0xFD010106
+
+
+typedef struct rndis_init_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 MajorVersion;
+ __le32 MinorVersion;
+ __le32 MaxTransferSize;
+} rndis_init_msg_type;
+
+typedef struct rndis_init_cmplt_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 Status;
+ __le32 MajorVersion;
+ __le32 MinorVersion;
+ __le32 DeviceFlags;
+ __le32 Medium;
+ __le32 MaxPacketsPerTransfer;
+ __le32 MaxTransferSize;
+ __le32 PacketAlignmentFactor;
+ __le32 AFListOffset;
+ __le32 AFListSize;
+} rndis_init_cmplt_type;
+
+typedef struct rndis_halt_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+} rndis_halt_msg_type;
+
+typedef struct rndis_query_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 OID;
+ __le32 InformationBufferLength;
+ __le32 InformationBufferOffset;
+ __le32 DeviceVcHandle;
+} rndis_query_msg_type;
+
+typedef struct rndis_query_cmplt_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 Status;
+ __le32 InformationBufferLength;
+ __le32 InformationBufferOffset;
+} rndis_query_cmplt_type;
+
+typedef struct rndis_set_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 OID;
+ __le32 InformationBufferLength;
+ __le32 InformationBufferOffset;
+ __le32 DeviceVcHandle;
+} rndis_set_msg_type;
+
+typedef struct rndis_set_cmplt_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 Status;
+} rndis_set_cmplt_type;
+
+typedef struct rndis_reset_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 Reserved;
+} rndis_reset_msg_type;
+
+typedef struct rndis_reset_cmplt_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 Status;
+ __le32 AddressingReset;
+} rndis_reset_cmplt_type;
+
+typedef struct rndis_indicate_status_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 Status;
+ __le32 StatusBufferLength;
+ __le32 StatusBufferOffset;
+} rndis_indicate_status_msg_type;
+
+typedef struct rndis_keepalive_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+} rndis_keepalive_msg_type;
+
+typedef struct rndis_keepalive_cmplt_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 RequestID;
+ __le32 Status;
+} rndis_keepalive_cmplt_type;
+
+struct rndis_packet_msg_type
+{
+ __le32 MessageType;
+ __le32 MessageLength;
+ __le32 DataOffset;
+ __le32 DataLength;
+ __le32 OOBDataOffset;
+ __le32 OOBDataLength;
+ __le32 NumOOBDataElements;
+ __le32 PerPacketInfoOffset;
+ __le32 PerPacketInfoLength;
+ __le32 VcHandle;
+ __le32 Reserved;
+} __attribute__ ((packed));
+
+struct rndis_config_parameter
+{
+ __le32 ParameterNameOffset;
+ __le32 ParameterNameLength;
+ __le32 ParameterType;
+ __le32 ParameterValueOffset;
+ __le32 ParameterValueLength;
+};
+
+/* implementation specific */
+enum rndis_state
+{
+ RNDIS_UNINITIALIZED,
+ RNDIS_INITIALIZED,
+ RNDIS_DATA_INITIALIZED,
+};
+
+typedef struct rndis_resp_t
+{
+ struct list_head list;
+ u8 *buf;
+ u32 length;
+ int send;
+} rndis_resp_t;
+
+typedef struct rndis_params
+{
+ u8 confignr;
+ u8 used;
+ u16 saved_filter;
+ enum rndis_state state;
+ u32 medium;
+ u32 speed;
+ u32 media_state;
+
+ const u8 *host_mac;
+ u16 *filter;
+ struct net_device *dev;
+
+ u32 vendorID;
+ const char *vendorDescr;
+ void (*resp_avail)(void *v);
+ void *v;
+ struct list_head resp_queue;
+} rndis_params;
+
+/* RNDIS Message parser and other useless functions */
+int rndis_msg_parser (u8 configNr, u8 *buf);
+int rndis_register(void (*resp_avail)(void *v), void *v);
+void rndis_deregister (int configNr);
+int rndis_set_param_dev (u8 configNr, struct net_device *dev,
+ u16 *cdc_filter);
+int rndis_set_param_vendor (u8 configNr, u32 vendorID,
+ const char *vendorDescr);
+int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed);
+void rndis_add_hdr (struct sk_buff *skb);
+int rndis_rm_hdr(struct gether *port, struct sk_buff *skb,
+ struct sk_buff_head *list);
+u8 *rndis_get_next_response (int configNr, u32 *length);
+void rndis_free_response (int configNr, u8 *buf);
+
+void rndis_uninit (int configNr);
+int rndis_signal_connect (int configNr);
+int rndis_signal_disconnect (int configNr);
+int rndis_state (int configNr);
+extern void rndis_set_host_mac (int configNr, const u8 *addr);
+
+int rndis_init(void);
+void rndis_exit (void);
+
+#endif /* _LINUX_RNDIS_H */
diff --git a/drivers/usb/gadget/gadget_gbhc/storage_common.c b/drivers/usb/gadget/gadget_gbhc/storage_common.c
new file mode 100644
index 0000000..a872248
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/storage_common.c
@@ -0,0 +1,797 @@
+/*
+ * storage_common.c -- Common definitions for mass storage functionality
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * Copyeight (C) 2009 Samsung Electronics
+ * Author: Michal Nazarewicz (m.nazarewicz@samsung.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+/*
+ * This file requires the following identifiers used in USB strings to
+ * be defined (each of type pointer to char):
+ * - fsg_string_manufacturer -- name of the manufacturer
+ * - fsg_string_product -- name of the product
+ * - fsg_string_config -- name of the configuration
+ * - fsg_string_interface -- name of the interface
+ * The first four are only needed when FSG_DESCRIPTORS_DEVICE_STRINGS
+ * macro is defined prior to including this file.
+ */
+
+/*
+ * When FSG_NO_INTR_EP is defined fsg_fs_intr_in_desc and
+ * fsg_hs_intr_in_desc objects as well as
+ * FSG_FS_FUNCTION_PRE_EP_ENTRIES and FSG_HS_FUNCTION_PRE_EP_ENTRIES
+ * macros are not defined.
+ *
+ * When FSG_NO_DEVICE_STRINGS is defined FSG_STRING_MANUFACTURER,
+ * FSG_STRING_PRODUCT, FSG_STRING_SERIAL and FSG_STRING_CONFIG are not
+ * defined (as well as corresponding entries in string tables are
+ * missing) and FSG_STRING_INTERFACE has value of zero.
+ *
+ * When FSG_NO_OTG is defined fsg_otg_desc won't be defined.
+ */
+
+/*
+ * When FSG_BUFFHD_STATIC_BUFFER is defined when this file is included
+ * the fsg_buffhd structure's buf field will be an array of FSG_BUFLEN
+ * characters rather then a pointer to void.
+ */
+
+
+#include <linux/usb/storage.h>
+#include <scsi/scsi.h>
+#include <asm/unaligned.h>
+
+
+/*
+ * Thanks to NetChip Technologies for donating this product ID.
+ *
+ * DO NOT REUSE THESE IDs with any other driver!! Ever!!
+ * Instead: allocate your own, using normal USB-IF procedures.
+ */
+#define FSG_VENDOR_ID 0x0525 /* NetChip */
+#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */
+
+
+/*-------------------------------------------------------------------------*/
+
+
+#ifndef DEBUG
+#undef VERBOSE_DEBUG
+#undef DUMP_MSGS
+#endif /* !DEBUG */
+
+#ifdef VERBOSE_DEBUG
+#define VLDBG LDBG
+#else
+#define VLDBG(lun, fmt, args...) do { } while (0)
+#endif /* VERBOSE_DEBUG */
+
+#define LDBG(lun, fmt, args...) dev_dbg (&(lun)->dev, fmt, ## args)
+#define LERROR(lun, fmt, args...) dev_err (&(lun)->dev, fmt, ## args)
+#define LWARN(lun, fmt, args...) dev_warn(&(lun)->dev, fmt, ## args)
+#define LINFO(lun, fmt, args...) dev_info(&(lun)->dev, fmt, ## args)
+
+/*
+ * Keep those macros in sync with those in
+ * include/linux/usb/composite.h or else GCC will complain. If they
+ * are identical (the same names of arguments, white spaces in the
+ * same places) GCC will allow redefinition otherwise (even if some
+ * white space is removed or added) warning will be issued.
+ *
+ * Those macros are needed here because File Storage Gadget does not
+ * include the composite.h header. For composite gadgets those macros
+ * are redundant since composite.h is included any way.
+ *
+ * One could check whether those macros are already defined (which
+ * would indicate composite.h had been included) or not (which would
+ * indicate we were in FSG) but this is not done because a warning is
+ * desired if definitions here differ from the ones in composite.h.
+ *
+ * We want the definitions to match and be the same in File Storage
+ * Gadget as well as Mass Storage Function (and so composite gadgets
+ * using MSF). If someone changes them in composite.h it will produce
+ * a warning in this file when building MSF.
+ */
+#define DBG(d, fmt, args...) dev_dbg(&(d)->gadget->dev , fmt , ## args)
+#define VDBG(d, fmt, args...) dev_vdbg(&(d)->gadget->dev , fmt , ## args)
+#define ERROR(d, fmt, args...) dev_err(&(d)->gadget->dev , fmt , ## args)
+#define WARNING(d, fmt, args...) dev_warn(&(d)->gadget->dev , fmt , ## args)
+#define INFO(d, fmt, args...) dev_info(&(d)->gadget->dev , fmt , ## args)
+
+
+
+#ifdef DUMP_MSGS
+
+# define dump_msg(fsg, /* const char * */ label, \
+ /* const u8 * */ buf, /* unsigned */ length) do { \
+ if (length < 512) { \
+ DBG(fsg, "%s, length %u:\n", label, length); \
+ print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, \
+ 16, 1, buf, length, 0); \
+ } \
+} while (0)
+
+# define dump_cdb(fsg) do { } while (0)
+
+#else
+
+# define dump_msg(fsg, /* const char * */ label, \
+ /* const u8 * */ buf, /* unsigned */ length) do { } while (0)
+
+# ifdef VERBOSE_DEBUG
+
+# define dump_cdb(fsg) \
+ print_hex_dump(KERN_DEBUG, "SCSI CDB: ", DUMP_PREFIX_NONE, \
+ 16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0) \
+
+# else
+
+# define dump_cdb(fsg) do { } while (0)
+
+# endif /* VERBOSE_DEBUG */
+
+#endif /* DUMP_MSGS */
+
+
+
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Bulk-only data structures */
+
+/* Command Block Wrapper */
+struct fsg_bulk_cb_wrap {
+ __le32 Signature; /* Contains 'USBC' */
+ u32 Tag; /* Unique per command id */
+ __le32 DataTransferLength; /* Size of the data */
+ u8 Flags; /* Direction in bit 7 */
+ u8 Lun; /* LUN (normally 0) */
+ u8 Length; /* Of the CDB, <= MAX_COMMAND_SIZE */
+ u8 CDB[16]; /* Command Data Block */
+};
+
+#define USB_BULK_CB_WRAP_LEN 31
+#define USB_BULK_CB_SIG 0x43425355 /* Spells out USBC */
+#define USB_BULK_IN_FLAG 0x80
+
+/* Command Status Wrapper */
+struct bulk_cs_wrap {
+ __le32 Signature; /* Should = 'USBS' */
+ u32 Tag; /* Same as original command */
+ __le32 Residue; /* Amount not transferred */
+ u8 Status; /* See below */
+};
+
+#define USB_BULK_CS_WRAP_LEN 13
+#define USB_BULK_CS_SIG 0x53425355 /* Spells out 'USBS' */
+#define USB_STATUS_PASS 0
+#define USB_STATUS_FAIL 1
+#define USB_STATUS_PHASE_ERROR 2
+
+/* Bulk-only class specific requests */
+#define USB_BULK_RESET_REQUEST 0xff
+#define USB_BULK_GET_MAX_LUN_REQUEST 0xfe
+
+
+/* CBI Interrupt data structure */
+struct interrupt_data {
+ u8 bType;
+ u8 bValue;
+};
+
+#define CBI_INTERRUPT_DATA_LEN 2
+
+/* CBI Accept Device-Specific Command request */
+#define USB_CBI_ADSC_REQUEST 0x00
+
+
+/* Length of a SCSI Command Data Block */
+#define MAX_COMMAND_SIZE 16
+
+/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */
+#define SS_NO_SENSE 0
+#define SS_COMMUNICATION_FAILURE 0x040800
+#define SS_INVALID_COMMAND 0x052000
+#define SS_INVALID_FIELD_IN_CDB 0x052400
+#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100
+#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500
+#define SS_MEDIUM_NOT_PRESENT 0x023a00
+#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302
+#define SS_NOT_READY_TO_READY_TRANSITION 0x062800
+#define SS_RESET_OCCURRED 0x062900
+#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900
+#define SS_UNRECOVERED_READ_ERROR 0x031100
+#define SS_WRITE_ERROR 0x030c02
+#define SS_WRITE_PROTECTED 0x072700
+
+#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */
+#define ASC(x) ((u8) ((x) >> 8))
+#define ASCQ(x) ((u8) (x))
+
+
+/*-------------------------------------------------------------------------*/
+
+
+struct fsg_lun {
+ struct file *filp;
+ loff_t file_length;
+ loff_t num_sectors;
+
+ unsigned int initially_ro:1;
+ unsigned int ro:1;
+ unsigned int removable:1;
+ unsigned int cdrom:1;
+ unsigned int prevent_medium_removal:1;
+ unsigned int registered:1;
+ unsigned int info_valid:1;
+ unsigned int nofua:1;
+
+ u32 sense_data;
+ u32 sense_data_info;
+ u32 unit_attention_data;
+
+ struct device dev;
+};
+
+#define fsg_lun_is_open(curlun) ((curlun)->filp != NULL)
+
+static struct fsg_lun *fsg_lun_from_dev(struct device *dev)
+{
+ return container_of(dev, struct fsg_lun, dev);
+}
+
+
+/* Big enough to hold our biggest descriptor */
+#define EP0_BUFSIZE 256
+#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */
+
+/* Number of buffers we will use. 2 is enough for double-buffering */
+#define FSG_NUM_BUFFERS 2
+
+/* Default size of buffer length. */
+#define FSG_BUFLEN ((u32)16384)
+
+/* Maximal number of LUNs supported in mass storage function */
+#define FSG_MAX_LUNS 8
+
+enum fsg_buffer_state {
+ BUF_STATE_EMPTY = 0,
+ BUF_STATE_FULL,
+ BUF_STATE_BUSY
+};
+
+struct fsg_buffhd {
+#ifdef FSG_BUFFHD_STATIC_BUFFER
+ char buf[FSG_BUFLEN];
+#else
+ void *buf;
+#endif
+ enum fsg_buffer_state state;
+ struct fsg_buffhd *next;
+
+ /*
+ * The NetChip 2280 is faster, and handles some protocol faults
+ * better, if we don't submit any short bulk-out read requests.
+ * So we will record the intended request length here.
+ */
+ unsigned int bulk_out_intended_length;
+
+ struct usb_request *inreq;
+ int inreq_busy;
+ struct usb_request *outreq;
+ int outreq_busy;
+};
+
+enum fsg_state {
+ /* This one isn't used anywhere */
+ FSG_STATE_COMMAND_PHASE = -10,
+ FSG_STATE_DATA_PHASE,
+ FSG_STATE_STATUS_PHASE,
+
+ FSG_STATE_IDLE = 0,
+ FSG_STATE_ABORT_BULK_OUT,
+ FSG_STATE_RESET,
+ FSG_STATE_INTERFACE_CHANGE,
+ FSG_STATE_CONFIG_CHANGE,
+ FSG_STATE_DISCONNECT,
+ FSG_STATE_EXIT,
+ FSG_STATE_TERMINATED
+};
+
+enum data_direction {
+ DATA_DIR_UNKNOWN = 0,
+ DATA_DIR_FROM_HOST,
+ DATA_DIR_TO_HOST,
+ DATA_DIR_NONE
+};
+
+
+/*-------------------------------------------------------------------------*/
+
+
+static inline u32 get_unaligned_be24(u8 *buf)
+{
+ return 0xffffff & (u32) get_unaligned_be32(buf - 1);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+
+enum {
+#ifndef FSG_NO_DEVICE_STRINGS
+ FSG_STRING_MANUFACTURER = 1,
+ FSG_STRING_PRODUCT,
+ FSG_STRING_SERIAL,
+ FSG_STRING_CONFIG,
+#endif
+ FSG_STRING_INTERFACE
+};
+
+
+#ifndef FSG_NO_OTG
+static struct usb_otg_descriptor
+fsg_otg_desc = {
+ .bLength = sizeof fsg_otg_desc,
+ .bDescriptorType = USB_DT_OTG,
+
+ .bmAttributes = USB_OTG_SRP,
+};
+#endif
+
+/* There is only one interface. */
+
+static struct usb_interface_descriptor
+fsg_intf_desc = {
+ .bLength = sizeof fsg_intf_desc,
+ .bDescriptorType = USB_DT_INTERFACE,
+
+ .bNumEndpoints = 2, /* Adjusted during fsg_bind() */
+ .bInterfaceClass = USB_CLASS_MASS_STORAGE,
+ .bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */
+ .bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */
+ .iInterface = FSG_STRING_INTERFACE,
+};
+
+/*
+ * Three full-speed endpoint descriptors: bulk-in, bulk-out, and
+ * interrupt-in.
+ */
+
+static struct usb_endpoint_descriptor
+fsg_fs_bulk_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* wMaxPacketSize set by autoconfiguration */
+};
+
+static struct usb_endpoint_descriptor
+fsg_fs_bulk_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* wMaxPacketSize set by autoconfiguration */
+};
+
+#ifndef FSG_NO_INTR_EP
+
+static struct usb_endpoint_descriptor
+fsg_fs_intr_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(2),
+ .bInterval = 32, /* frames -> 32 ms */
+};
+
+#ifndef FSG_NO_OTG
+# define FSG_FS_FUNCTION_PRE_EP_ENTRIES 2
+#else
+# define FSG_FS_FUNCTION_PRE_EP_ENTRIES 1
+#endif
+
+#endif
+
+static struct usb_descriptor_header *fsg_fs_function[] = {
+#ifndef FSG_NO_OTG
+ (struct usb_descriptor_header *) &fsg_otg_desc,
+#endif
+ (struct usb_descriptor_header *) &fsg_intf_desc,
+ (struct usb_descriptor_header *) &fsg_fs_bulk_in_desc,
+ (struct usb_descriptor_header *) &fsg_fs_bulk_out_desc,
+#ifndef FSG_NO_INTR_EP
+ (struct usb_descriptor_header *) &fsg_fs_intr_in_desc,
+#endif
+ NULL,
+};
+
+
+/*
+ * USB 2.0 devices need to expose both high speed and full speed
+ * descriptors, unless they only run at full speed.
+ *
+ * That means alternate endpoint descriptors (bigger packets)
+ * and a "device qualifier" ... plus more construction options
+ * for the configuration descriptor.
+ */
+static struct usb_endpoint_descriptor
+fsg_hs_bulk_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor
+fsg_hs_bulk_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+ .bInterval = 1, /* NAK every 1 uframe */
+};
+
+#ifndef FSG_NO_INTR_EP
+
+static struct usb_endpoint_descriptor
+fsg_hs_intr_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ /* bEndpointAddress copied from fs_intr_in_desc during fsg_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(2),
+ .bInterval = 9, /* 2**(9-1) = 256 uframes -> 32 ms */
+};
+
+#ifndef FSG_NO_OTG
+# define FSG_HS_FUNCTION_PRE_EP_ENTRIES 2
+#else
+# define FSG_HS_FUNCTION_PRE_EP_ENTRIES 1
+#endif
+
+#endif
+
+static struct usb_descriptor_header *fsg_hs_function[] = {
+#ifndef FSG_NO_OTG
+ (struct usb_descriptor_header *) &fsg_otg_desc,
+#endif
+ (struct usb_descriptor_header *) &fsg_intf_desc,
+ (struct usb_descriptor_header *) &fsg_hs_bulk_in_desc,
+ (struct usb_descriptor_header *) &fsg_hs_bulk_out_desc,
+#ifndef FSG_NO_INTR_EP
+ (struct usb_descriptor_header *) &fsg_hs_intr_in_desc,
+#endif
+ NULL,
+};
+
+/* Maxpacket and other transfer characteristics vary by speed. */
+static struct usb_endpoint_descriptor *
+fsg_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs,
+ struct usb_endpoint_descriptor *hs)
+{
+ if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH)
+ return hs;
+ return fs;
+}
+
+
+/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */
+static struct usb_string fsg_strings[] = {
+#ifndef FSG_NO_DEVICE_STRINGS
+ {FSG_STRING_MANUFACTURER, fsg_string_manufacturer},
+ {FSG_STRING_PRODUCT, fsg_string_product},
+ {FSG_STRING_SERIAL, ""},
+ {FSG_STRING_CONFIG, fsg_string_config},
+#endif
+ {FSG_STRING_INTERFACE, fsg_string_interface},
+ {}
+};
+
+static struct usb_gadget_strings fsg_stringtab = {
+ .language = 0x0409, /* en-us */
+ .strings = fsg_strings,
+};
+
+
+ /*-------------------------------------------------------------------------*/
+
+/*
+ * If the next two routines are called while the gadget is registered,
+ * the caller must own fsg->filesem for writing.
+ */
+
+static int fsg_lun_open(struct fsg_lun *curlun, const char *filename)
+{
+ int ro;
+ struct file *filp = NULL;
+ int rc = -EINVAL;
+ struct inode *inode = NULL;
+ loff_t size;
+ loff_t num_sectors;
+ loff_t min_sectors;
+
+ /* R/W if we can, R/O if we must */
+ ro = curlun->initially_ro;
+ if (!ro) {
+ filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0);
+ if (PTR_ERR(filp) == -EROFS || PTR_ERR(filp) == -EACCES)
+ ro = 1;
+ }
+ if (ro)
+ filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0);
+ if (IS_ERR(filp)) {
+ LINFO(curlun, "unable to open backing file: %s\n", filename);
+ return PTR_ERR(filp);
+ }
+
+ if (!(filp->f_mode & FMODE_WRITE))
+ ro = 1;
+
+ if (filp->f_path.dentry)
+ inode = filp->f_path.dentry->d_inode;
+ if (!inode || (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) {
+ LINFO(curlun, "invalid file type: %s\n", filename);
+ goto out;
+ }
+
+ /*
+ * If we can't read the file, it's no good.
+ * If we can't write the file, use it read-only.
+ */
+ if (!filp->f_op || !(filp->f_op->read || filp->f_op->aio_read)) {
+ LINFO(curlun, "file not readable: %s\n", filename);
+ goto out;
+ }
+ if (!(filp->f_op->write || filp->f_op->aio_write))
+ ro = 1;
+
+ size = i_size_read(inode->i_mapping->host);
+ if (size < 0) {
+ LINFO(curlun, "unable to find file size: %s\n", filename);
+ rc = (int) size;
+ goto out;
+ }
+ num_sectors = size >> 9; /* File size in 512-byte blocks */
+ min_sectors = 1;
+ if (curlun->cdrom) {
+ num_sectors &= ~3; /* Reduce to a multiple of 2048 */
+ min_sectors = 300*4; /* Smallest track is 300 frames */
+ if (num_sectors >= 256*60*75*4) {
+ num_sectors = (256*60*75 - 1) * 4;
+ LINFO(curlun, "file too big: %s\n", filename);
+ LINFO(curlun, "using only first %d blocks\n",
+ (int) num_sectors);
+ }
+ }
+ if (num_sectors < min_sectors) {
+ LINFO(curlun, "file too small: %s\n", filename);
+ rc = -ETOOSMALL;
+ goto out;
+ }
+
+ get_file(filp);
+ curlun->ro = ro;
+ curlun->filp = filp;
+ curlun->file_length = size;
+ curlun->num_sectors = num_sectors;
+ LDBG(curlun, "open backing file: %s\n", filename);
+ rc = 0;
+
+out:
+ filp_close(filp, current->files);
+ return rc;
+}
+
+
+static void fsg_lun_close(struct fsg_lun *curlun)
+{
+ if (curlun->filp) {
+ LDBG(curlun, "close backing file\n");
+ fput(curlun->filp);
+ curlun->filp = NULL;
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * Sync the file data, don't bother with the metadata.
+ * This code was copied from fs/buffer.c:sys_fdatasync().
+ */
+static int fsg_lun_fsync_sub(struct fsg_lun *curlun)
+{
+ struct file *filp = curlun->filp;
+
+ if (curlun->ro || !filp)
+ return 0;
+ return vfs_fsync(filp, 1);
+}
+
+static void store_cdrom_address(u8 *dest, int msf, u32 addr)
+{
+ if (msf) {
+ /* Convert to Minutes-Seconds-Frames */
+ addr >>= 2; /* Convert to 2048-byte frames */
+ addr += 2*75; /* Lead-in occupies 2 seconds */
+ dest[3] = addr % 75; /* Frames */
+ addr /= 75;
+ dest[2] = addr % 60; /* Seconds */
+ addr /= 60;
+ dest[1] = addr; /* Minutes */
+ dest[0] = 0; /* Reserved */
+ } else {
+ /* Absolute sector */
+ put_unaligned_be32(addr, dest);
+ }
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+
+static ssize_t fsg_show_ro(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fsg_lun *curlun = fsg_lun_from_dev(dev);
+
+ return sprintf(buf, "%d\n", fsg_lun_is_open(curlun)
+ ? curlun->ro
+ : curlun->initially_ro);
+}
+
+static ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fsg_lun *curlun = fsg_lun_from_dev(dev);
+
+ return sprintf(buf, "%u\n", curlun->nofua);
+}
+
+static ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct fsg_lun *curlun = fsg_lun_from_dev(dev);
+ struct rw_semaphore *filesem = dev_get_drvdata(dev);
+ char *p;
+ ssize_t rc;
+
+ down_read(filesem);
+ if (fsg_lun_is_open(curlun)) { /* Get the complete pathname */
+ p = d_path(&curlun->filp->f_path, buf, PAGE_SIZE - 1);
+ if (IS_ERR(p))
+ rc = PTR_ERR(p);
+ else {
+ rc = strlen(p);
+ memmove(buf, p, rc);
+ buf[rc] = '\n'; /* Add a newline */
+ buf[++rc] = 0;
+ }
+ } else { /* No file, return 0 bytes */
+ *buf = 0;
+ rc = 0;
+ }
+ up_read(filesem);
+ return rc;
+}
+
+
+static ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ ssize_t rc;
+ struct fsg_lun *curlun = fsg_lun_from_dev(dev);
+ struct rw_semaphore *filesem = dev_get_drvdata(dev);
+ unsigned ro;
+
+ rc = kstrtouint(buf, 2, &ro);
+ if (rc)
+ return rc;
+
+ /*
+ * Allow the write-enable status to change only while the
+ * backing file is closed.
+ */
+ down_read(filesem);
+ if (fsg_lun_is_open(curlun)) {
+ LDBG(curlun, "read-only status change prevented\n");
+ rc = -EBUSY;
+ } else {
+ curlun->ro = ro;
+ curlun->initially_ro = ro;
+ LDBG(curlun, "read-only status set to %d\n", curlun->ro);
+ rc = count;
+ }
+ up_read(filesem);
+ return rc;
+}
+
+static ssize_t fsg_store_nofua(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fsg_lun *curlun = fsg_lun_from_dev(dev);
+ unsigned nofua;
+ int ret;
+
+ ret = kstrtouint(buf, 2, &nofua);
+ if (ret)
+ return ret;
+
+ /* Sync data when switching from async mode to sync */
+ if (!nofua && curlun->nofua)
+ fsg_lun_fsync_sub(curlun);
+
+ curlun->nofua = nofua;
+
+ return count;
+}
+
+static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct fsg_lun *curlun = fsg_lun_from_dev(dev);
+ struct rw_semaphore *filesem = dev_get_drvdata(dev);
+ int rc = 0;
+
+
+#ifndef CONFIG_USB_ANDROID_MASS_STORAGE
+ /* disabled in android because we need to allow closing the backing file
+ * if the media was removed
+ */
+ if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) {
+ LDBG(curlun, "eject attempt prevented\n");
+ return -EBUSY; /* "Door is locked" */
+ }
+#endif
+
+ /* Remove a trailing newline */
+ if (count > 0 && buf[count-1] == '\n')
+ ((char *) buf)[count-1] = 0; /* Ugh! */
+
+ /* Eject current medium */
+ down_write(filesem);
+ if (fsg_lun_is_open(curlun)) {
+ fsg_lun_close(curlun);
+ curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
+ }
+
+ /* Load new medium */
+ if (count > 0 && buf[0]) {
+ rc = fsg_lun_open(curlun, buf);
+ if (rc == 0)
+ curlun->unit_attention_data =
+ SS_NOT_READY_TO_READY_TRANSITION;
+ }
+ up_write(filesem);
+ return (rc < 0 ? rc : count);
+}
diff --git a/drivers/usb/gadget/gadget_gbhc/u_ether.c b/drivers/usb/gadget/gadget_gbhc/u_ether.c
new file mode 100644
index 0000000..73e9d4b
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/u_ether.c
@@ -0,0 +1,1024 @@
+/*
+ * u_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/gfp.h>
+#include <linux/device.h>
+#include <linux/ctype.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+
+#include "u_ether.h"
+
+
+/*
+ * This component encapsulates the Ethernet link glue needed to provide
+ * one (!) network link through the USB gadget stack, normally "usb0".
+ *
+ * The control and data models are handled by the function driver which
+ * connects to this code; such as CDC Ethernet (ECM or EEM),
+ * "CDC Subset", or RNDIS. That includes all descriptor and endpoint
+ * management.
+ *
+ * Link level addressing is handled by this component using module
+ * parameters; if no such parameters are provided, random link level
+ * addresses are used. Each end of the link uses one address. The
+ * host end address is exported in various ways, and is often recorded
+ * in configuration databases.
+ *
+ * The driver which assembles each configuration using such a link is
+ * responsible for ensuring that each configuration includes at most one
+ * instance of is network link. (The network layer provides ways for
+ * this single "physical" link to be used by multiple virtual links.)
+ */
+
+#define UETH__VERSION "29-May-2008"
+
+struct eth_dev {
+ /* lock is held while accessing port_usb
+ * or updating its backlink port_usb->ioport
+ */
+ spinlock_t lock;
+ struct gether *port_usb;
+
+ struct net_device *net;
+ struct usb_gadget *gadget;
+
+ spinlock_t req_lock; /* guard {rx,tx}_reqs */
+ struct list_head tx_reqs, rx_reqs;
+ atomic_t tx_qlen;
+
+ struct sk_buff_head rx_frames;
+
+ unsigned header_len;
+ struct sk_buff *(*wrap)(struct gether *, struct sk_buff *skb);
+ int (*unwrap)(struct gether *,
+ struct sk_buff *skb,
+ struct sk_buff_head *list);
+
+ struct work_struct work;
+
+ unsigned long todo;
+#define WORK_RX_MEMORY 0
+
+ bool zlp;
+ u8 host_mac[ETH_ALEN];
+};
+
+/*-------------------------------------------------------------------------*/
+
+#define RX_EXTRA 20 /* bytes guarding against rx overflows */
+
+#define DEFAULT_QLEN 2 /* double buffering by default */
+
+
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+
+static unsigned qmult = 5;
+module_param(qmult, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(qmult, "queue length multiplier at high speed");
+
+#else /* full speed (low speed doesn't do bulk) */
+#define qmult 1
+#endif
+
+/* for dual-speed hardware, use deeper queues at highspeed */
+static inline int qlen(struct usb_gadget *gadget)
+{
+ if (gadget_is_dualspeed(gadget) && gadget->speed == USB_SPEED_HIGH)
+ return qmult * DEFAULT_QLEN;
+ else
+ return DEFAULT_QLEN;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* REVISIT there must be a better way than having two sets
+ * of debug calls ...
+ */
+
+#undef DBG
+#undef VDBG
+#undef ERROR
+#undef INFO
+
+#define xprintk(d, level, fmt, args...) \
+ printk(level "%s: " fmt , (d)->net->name , ## args)
+
+#ifdef DEBUG
+#undef DEBUG
+#define DBG(dev, fmt, args...) \
+ xprintk(dev , KERN_DEBUG , fmt , ## args)
+#else
+#define DBG(dev, fmt, args...) \
+ do { } while (0)
+#endif /* DEBUG */
+
+#ifdef VERBOSE_DEBUG
+#define VDBG DBG
+#else
+#define VDBG(dev, fmt, args...) \
+ do { } while (0)
+#endif /* DEBUG */
+
+#define ERROR(dev, fmt, args...) \
+ xprintk(dev , KERN_ERR , fmt , ## args)
+#define INFO(dev, fmt, args...) \
+ xprintk(dev , KERN_INFO , fmt , ## args)
+
+/*-------------------------------------------------------------------------*/
+
+/* NETWORK DRIVER HOOKUP (to the layer above this driver) */
+
+static int ueth_change_mtu(struct net_device *net, int new_mtu)
+{
+ struct eth_dev *dev = netdev_priv(net);
+ unsigned long flags;
+ int status = 0;
+
+ /* don't change MTU on "live" link (peer won't know) */
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->port_usb)
+ status = -EBUSY;
+ else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN)
+ status = -ERANGE;
+ else
+ net->mtu = new_mtu;
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return status;
+}
+
+static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p)
+{
+ struct eth_dev *dev = netdev_priv(net);
+
+ strlcpy(p->driver, "g_ether", sizeof p->driver);
+ strlcpy(p->version, UETH__VERSION, sizeof p->version);
+ strlcpy(p->fw_version, dev->gadget->name, sizeof p->fw_version);
+ strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof p->bus_info);
+}
+
+/* REVISIT can also support:
+ * - WOL (by tracking suspends and issuing remote wakeup)
+ * - msglevel (implies updated messaging)
+ * - ... probably more ethtool ops
+ */
+
+static const struct ethtool_ops ops = {
+ .get_drvinfo = eth_get_drvinfo,
+ .get_link = ethtool_op_get_link,
+};
+
+static void defer_kevent(struct eth_dev *dev, int flag)
+{
+ if (test_and_set_bit(flag, &dev->todo))
+ return;
+ if (!schedule_work(&dev->work))
+ ERROR(dev, "kevent %d may have been dropped\n", flag);
+ else
+ DBG(dev, "kevent %d scheduled\n", flag);
+}
+
+static void rx_complete(struct usb_ep *ep, struct usb_request *req);
+
+static int
+rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
+{
+ struct sk_buff *skb;
+ int retval = -ENOMEM;
+ size_t size = 0;
+ struct usb_ep *out;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->port_usb)
+ out = dev->port_usb->out_ep;
+ else
+ out = NULL;
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (!out)
+ return -ENOTCONN;
+
+
+ /* Padding up to RX_EXTRA handles minor disagreements with host.
+ * Normally we use the USB "terminate on short read" convention;
+ * so allow up to (N*maxpacket), since that memory is normally
+ * already allocated. Some hardware doesn't deal well with short
+ * reads (e.g. DMA must be N*maxpacket), so for now don't trim a
+ * byte off the end (to force hardware errors on overflow).
+ *
+ * RNDIS uses internal framing, and explicitly allows senders to
+ * pad to end-of-packet. That's potentially nice for speed, but
+ * means receivers can't recover lost synch on their own (because
+ * new packets don't only start after a short RX).
+ */
+ size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA;
+ size += dev->port_usb->header_len;
+ size += out->maxpacket - 1;
+ size -= size % out->maxpacket;
+
+ if (dev->port_usb->is_fixed)
+ size = max_t(size_t, size, dev->port_usb->fixed_out_len);
+#ifdef CONFIG_USB_S3C_OTGD
+ skb = alloc_skb(size + NET_IP_ALIGN + 6, gfp_flags);
+#else
+ skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags);
+#endif
+ if (skb == NULL) {
+ DBG(dev, "no rx skb\n");
+ goto enomem;
+ }
+
+ /* Some platforms perform better when IP packets are aligned,
+ * but on at least one, checksumming fails otherwise. Note:
+ * RNDIS headers involve variable numbers of LE32 values.
+ */
+#ifdef CONFIG_USB_S3C_OTGD
+ skb_reserve(skb, NET_IP_ALIGN + 6);
+#else
+ skb_reserve(skb, NET_IP_ALIGN);
+#endif
+ req->buf = skb->data;
+ req->length = size;
+ req->complete = rx_complete;
+ req->context = skb;
+
+ retval = usb_ep_queue(out, req, gfp_flags);
+ if (retval == -ENOMEM)
+enomem:
+ defer_kevent(dev, WORK_RX_MEMORY);
+ if (retval) {
+ DBG(dev, "rx submit --> %d\n", retval);
+ if (skb)
+ dev_kfree_skb_any(skb);
+ spin_lock_irqsave(&dev->req_lock, flags);
+ list_add(&req->list, &dev->rx_reqs);
+ spin_unlock_irqrestore(&dev->req_lock, flags);
+ }
+ return retval;
+}
+
+static void rx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct sk_buff *skb = req->context, *skb2;
+ struct eth_dev *dev = ep->driver_data;
+ int status = req->status;
+
+ switch (status) {
+
+ /* normal completion */
+ case 0:
+ skb_put(skb, req->actual);
+
+ if (dev->unwrap) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->port_usb) {
+ status = dev->unwrap(dev->port_usb,
+ skb,
+ &dev->rx_frames);
+ } else {
+ dev_kfree_skb_any(skb);
+ status = -ENOTCONN;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ } else {
+ skb_queue_tail(&dev->rx_frames, skb);
+ }
+ skb = NULL;
+
+ skb2 = skb_dequeue(&dev->rx_frames);
+ while (skb2) {
+ if (status < 0
+ || ETH_HLEN > skb2->len
+ || skb2->len > ETH_FRAME_LEN) {
+ dev->net->stats.rx_errors++;
+ dev->net->stats.rx_length_errors++;
+ DBG(dev, "rx length %d\n", skb2->len);
+ dev_kfree_skb_any(skb2);
+ goto next_frame;
+ }
+ skb2->protocol = eth_type_trans(skb2, dev->net);
+ dev->net->stats.rx_packets++;
+ dev->net->stats.rx_bytes += skb2->len;
+
+ /* no buffer copies needed, unless hardware can't
+ * use skb buffers.
+ */
+ status = netif_rx(skb2);
+next_frame:
+ skb2 = skb_dequeue(&dev->rx_frames);
+ }
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: /* unlink */
+ case -ESHUTDOWN: /* disconnect etc */
+ VDBG(dev, "rx shutdown, code %d\n", status);
+ goto quiesce;
+
+ /* for hardware automagic (such as pxa) */
+ case -ECONNABORTED: /* endpoint reset */
+ DBG(dev, "rx %s reset\n", ep->name);
+ defer_kevent(dev, WORK_RX_MEMORY);
+quiesce:
+ dev_kfree_skb_any(skb);
+ goto clean;
+
+ /* data overrun */
+ case -EOVERFLOW:
+ dev->net->stats.rx_over_errors++;
+ /* FALLTHROUGH */
+
+ default:
+ dev->net->stats.rx_errors++;
+ DBG(dev, "rx status %d\n", status);
+ break;
+ }
+
+ if (skb)
+ dev_kfree_skb_any(skb);
+ if (!netif_running(dev->net)) {
+clean:
+ spin_lock(&dev->req_lock);
+ list_add(&req->list, &dev->rx_reqs);
+ spin_unlock(&dev->req_lock);
+ req = NULL;
+ }
+ if (req)
+ rx_submit(dev, req, GFP_ATOMIC);
+}
+
+static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n)
+{
+ unsigned i;
+ struct usb_request *req;
+
+ if (!n)
+ return -ENOMEM;
+
+ /* queue/recycle up to N requests */
+ i = n;
+ list_for_each_entry(req, list, list) {
+ if (i-- == 0)
+ goto extra;
+ }
+ while (i--) {
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (!req)
+ return list_empty(list) ? -ENOMEM : 0;
+ list_add(&req->list, list);
+ }
+ return 0;
+
+extra:
+ /* free extras */
+ for (;;) {
+ struct list_head *next;
+
+ next = req->list.next;
+ list_del(&req->list);
+ usb_ep_free_request(ep, req);
+
+ if (next == list)
+ break;
+
+ req = container_of(next, struct usb_request, list);
+ }
+ return 0;
+}
+
+static int alloc_requests(struct eth_dev *dev, struct gether *link, unsigned n)
+{
+ int status;
+
+ spin_lock(&dev->req_lock);
+ status = prealloc(&dev->tx_reqs, link->in_ep, n);
+ if (status < 0)
+ goto fail;
+ status = prealloc(&dev->rx_reqs, link->out_ep, n);
+ if (status < 0)
+ goto fail;
+ goto done;
+fail:
+ DBG(dev, "can't alloc requests\n");
+done:
+ spin_unlock(&dev->req_lock);
+ return status;
+}
+
+static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags)
+{
+ struct usb_request *req;
+ unsigned long flags;
+
+ /* fill unused rxq slots with some skb */
+ spin_lock_irqsave(&dev->req_lock, flags);
+ while (!list_empty(&dev->rx_reqs)) {
+ req = container_of(dev->rx_reqs.next,
+ struct usb_request, list);
+ list_del_init(&req->list);
+ spin_unlock_irqrestore(&dev->req_lock, flags);
+
+ if (rx_submit(dev, req, gfp_flags) < 0) {
+ defer_kevent(dev, WORK_RX_MEMORY);
+ return;
+ }
+
+ spin_lock_irqsave(&dev->req_lock, flags);
+ }
+ spin_unlock_irqrestore(&dev->req_lock, flags);
+}
+
+static void eth_work(struct work_struct *work)
+{
+ struct eth_dev *dev = container_of(work, struct eth_dev, work);
+
+ if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) {
+ if (netif_running(dev->net))
+ rx_fill(dev, GFP_KERNEL);
+ }
+
+ if (dev->todo)
+ DBG(dev, "work done, flags = 0x%lx\n", dev->todo);
+}
+
+static void tx_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct sk_buff *skb = req->context;
+ struct eth_dev *dev = ep->driver_data;
+
+ switch (req->status) {
+ default:
+ dev->net->stats.tx_errors++;
+ VDBG(dev, "tx err %d\n", req->status);
+ /* FALLTHROUGH */
+ case -ECONNRESET: /* unlink */
+ case -ESHUTDOWN: /* disconnect etc */
+ break;
+ case 0:
+ dev->net->stats.tx_bytes += skb->len;
+ }
+ dev->net->stats.tx_packets++;
+
+ spin_lock(&dev->req_lock);
+ list_add(&req->list, &dev->tx_reqs);
+ spin_unlock(&dev->req_lock);
+ dev_kfree_skb_any(skb);
+
+#ifdef CONFIG_USB_S3C_OTGD
+ if (req->buf != skb->data)
+ kfree(req->buf);
+#endif
+ atomic_dec(&dev->tx_qlen);
+ if (netif_carrier_ok(dev->net))
+ netif_wake_queue(dev->net);
+}
+
+static inline int is_promisc(u16 cdc_filter)
+{
+ return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS;
+}
+
+static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
+ struct net_device *net)
+{
+ struct eth_dev *dev = netdev_priv(net);
+ int length = skb->len;
+ int retval;
+ struct usb_request *req = NULL;
+ unsigned long flags;
+ struct usb_ep *in;
+ u16 cdc_filter;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->port_usb) {
+ in = dev->port_usb->in_ep;
+ cdc_filter = dev->port_usb->cdc_filter;
+ } else {
+ in = NULL;
+ cdc_filter = 0;
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ if (!in) {
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+
+ /* apply outgoing CDC or RNDIS filters */
+ if (!is_promisc(cdc_filter)) {
+ u8 *dest = skb->data;
+
+ if (is_multicast_ether_addr(dest)) {
+ u16 type;
+
+ /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host
+ * SET_ETHERNET_MULTICAST_FILTERS requests
+ */
+ if (is_broadcast_ether_addr(dest))
+ type = USB_CDC_PACKET_TYPE_BROADCAST;
+ else
+ type = USB_CDC_PACKET_TYPE_ALL_MULTICAST;
+ if (!(cdc_filter & type)) {
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+ }
+ }
+ /* ignores USB_CDC_PACKET_TYPE_DIRECTED */
+ }
+
+ spin_lock_irqsave(&dev->req_lock, flags);
+ /*
+ * this freelist can be empty if an interrupt triggered disconnect()
+ * and reconfigured the gadget (shutting down this queue) after the
+ * network stack decided to xmit but before we got the spinlock.
+ */
+ if (list_empty(&dev->tx_reqs)) {
+ spin_unlock_irqrestore(&dev->req_lock, flags);
+ return NETDEV_TX_BUSY;
+ }
+
+ req = container_of(dev->tx_reqs.next, struct usb_request, list);
+ list_del(&req->list);
+
+ /* temporarily stop TX queue when the freelist empties */
+ if (list_empty(&dev->tx_reqs))
+ netif_stop_queue(net);
+ spin_unlock_irqrestore(&dev->req_lock, flags);
+
+ /* no buffer copies needed, unless the network stack did it
+ * or the hardware can't use skb buffers.
+ * or there's not enough space for extra headers we need
+ */
+ if (dev->wrap) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->port_usb)
+ skb = dev->wrap(dev->port_usb, skb);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ if (!skb)
+ goto drop;
+
+ length = skb->len;
+ }
+#ifdef CONFIG_USB_S3C_OTGD
+ /* for double word align */
+ req->buf = kmalloc(skb->len, GFP_ATOMIC | GFP_DMA);
+
+ if (!req->buf) {
+ req->buf = skb->data;
+ ERROR(dev, "fail to kmalloc[req->buf = skb->data]\n");
+ } else {
+ memcpy((void *)req->buf, (void *)skb->data, skb->len);
+ }
+#else
+ req->buf = skb->data;
+#endif
+ req->context = skb;
+ req->complete = tx_complete;
+
+ /* NCM requires no zlp if transfer is dwNtbInMaxSize */
+ if (dev->port_usb->is_fixed &&
+ length == dev->port_usb->fixed_in_len &&
+ (length % in->maxpacket) == 0)
+ req->zero = 0;
+ else
+ req->zero = 1;
+
+ /* use zlp framing on tx for strict CDC-Ether conformance,
+ * though any robust network rx path ignores extra padding.
+ * and some hardware doesn't like to write zlps.
+ */
+ if (req->zero && !dev->zlp && (length % in->maxpacket) == 0)
+ length++;
+
+ req->length = length;
+
+ /* throttle highspeed IRQ rate back slightly */
+ if (gadget_is_dualspeed(dev->gadget))
+ req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH)
+ ? ((atomic_read(&dev->tx_qlen) % qmult) != 0)
+ : 0;
+
+ retval = usb_ep_queue(in, req, GFP_ATOMIC);
+ switch (retval) {
+ default:
+ DBG(dev, "tx queue err %d\n", retval);
+ break;
+ case 0:
+ net->trans_start = jiffies;
+ atomic_inc(&dev->tx_qlen);
+ }
+
+ if (retval) {
+ dev_kfree_skb_any(skb);
+#ifdef CONFIG_USB_S3C_OTGD
+ if (req->buf != skb->data)
+ kfree(req->buf);
+#endif
+drop:
+ dev->net->stats.tx_dropped++;
+
+ spin_lock_irqsave(&dev->req_lock, flags);
+ if (list_empty(&dev->tx_reqs))
+ netif_start_queue(net);
+ list_add(&req->list, &dev->tx_reqs);
+ spin_unlock_irqrestore(&dev->req_lock, flags);
+ }
+ return NETDEV_TX_OK;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
+{
+ DBG(dev, "%s\n", __func__);
+
+ /* fill the rx queue */
+ rx_fill(dev, gfp_flags);
+
+ /* and open the tx floodgates */
+ atomic_set(&dev->tx_qlen, 0);
+ netif_wake_queue(dev->net);
+}
+
+static int eth_open(struct net_device *net)
+{
+ struct eth_dev *dev = netdev_priv(net);
+ struct gether *link;
+
+ DBG(dev, "%s\n", __func__);
+ if (netif_carrier_ok(dev->net))
+ eth_start(dev, GFP_KERNEL);
+
+ spin_lock_irq(&dev->lock);
+ link = dev->port_usb;
+ if (link && link->open)
+ link->open(link);
+ spin_unlock_irq(&dev->lock);
+
+ return 0;
+}
+
+static int eth_stop(struct net_device *net)
+{
+ struct eth_dev *dev = netdev_priv(net);
+ unsigned long flags;
+
+ VDBG(dev, "%s\n", __func__);
+ netif_stop_queue(net);
+
+ DBG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n",
+ dev->net->stats.rx_packets, dev->net->stats.tx_packets,
+ dev->net->stats.rx_errors, dev->net->stats.tx_errors
+ );
+
+ /* ensure there are no more active requests */
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->port_usb) {
+ struct gether *link = dev->port_usb;
+
+ if (link->close)
+ link->close(link);
+
+ /* NOTE: we have no abort-queue primitive we could use
+ * to cancel all pending I/O. Instead, we disable then
+ * reenable the endpoints ... this idiom may leave toggle
+ * wrong, but that's a self-correcting error.
+ *
+ * REVISIT: we *COULD* just let the transfers complete at
+ * their own pace; the network stack can handle old packets.
+ * For the moment we leave this here, since it works.
+ */
+ usb_ep_disable(link->in_ep);
+ usb_ep_disable(link->out_ep);
+ if (netif_carrier_ok(net)) {
+ DBG(dev, "host still using in/out endpoints\n");
+ usb_ep_enable(link->in_ep, link->in);
+ usb_ep_enable(link->out_ep, link->out);
+ }
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */
+static char *dev_addr;
+module_param(dev_addr, charp, S_IRUGO);
+MODULE_PARM_DESC(dev_addr, "Device Ethernet Address");
+
+/* this address is invisible to ifconfig */
+static char *host_addr;
+module_param(host_addr, charp, S_IRUGO);
+MODULE_PARM_DESC(host_addr, "Host Ethernet Address");
+
+static int get_ether_addr(const char *str, u8 *dev_addr)
+{
+ if (str) {
+ unsigned i;
+
+ for (i = 0; i < 6; i++) {
+ unsigned char num;
+
+ if ((*str == '.') || (*str == ':'))
+ str++;
+ num = hex_to_bin(*str++) << 4;
+ num |= hex_to_bin(*str++);
+ dev_addr [i] = num;
+ }
+ if (is_valid_ether_addr(dev_addr))
+ return 0;
+ }
+ random_ether_addr(dev_addr);
+ return 1;
+}
+
+static struct eth_dev *the_dev;
+
+static const struct net_device_ops eth_netdev_ops = {
+ .ndo_open = eth_open,
+ .ndo_stop = eth_stop,
+ .ndo_start_xmit = eth_start_xmit,
+ .ndo_change_mtu = ueth_change_mtu,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+static struct device_type gadget_type = {
+ .name = "gadget",
+};
+
+/**
+ * gether_setup - initialize one ethernet-over-usb link
+ * @g: gadget to associated with these links
+ * @ethaddr: NULL, or a buffer in which the ethernet address of the
+ * host side of the link is recorded
+ * Context: may sleep
+ *
+ * This sets up the single network link that may be exported by a
+ * gadget driver using this framework. The link layer addresses are
+ * set up using module parameters.
+ *
+ * Returns negative errno, or zero on success
+ */
+int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN])
+{
+ struct eth_dev *dev;
+ struct net_device *net;
+ int status;
+
+ if (the_dev)
+ return -EBUSY;
+
+ net = alloc_etherdev(sizeof *dev);
+ if (!net)
+ return -ENOMEM;
+
+ dev = netdev_priv(net);
+ spin_lock_init(&dev->lock);
+ spin_lock_init(&dev->req_lock);
+ INIT_WORK(&dev->work, eth_work);
+ INIT_LIST_HEAD(&dev->tx_reqs);
+ INIT_LIST_HEAD(&dev->rx_reqs);
+
+ skb_queue_head_init(&dev->rx_frames);
+
+ /* network device setup */
+ dev->net = net;
+ strcpy(net->name, "usb%d");
+
+ if (get_ether_addr(dev_addr, net->dev_addr))
+ dev_warn(&g->dev,
+ "using random %s ethernet address\n", "self");
+ if (get_ether_addr(host_addr, dev->host_mac))
+ dev_warn(&g->dev,
+ "using random %s ethernet address\n", "host");
+
+ if (ethaddr)
+ memcpy(ethaddr, dev->host_mac, ETH_ALEN);
+
+ net->netdev_ops = &eth_netdev_ops;
+
+ SET_ETHTOOL_OPS(net, &ops);
+
+ /* two kinds of host-initiated state changes:
+ * - iff DATA transfer is active, carrier is "on"
+ * - tx queueing enabled if open *and* carrier is "on"
+ */
+ netif_carrier_off(net);
+
+ dev->gadget = g;
+ SET_NETDEV_DEV(net, &g->dev);
+ SET_NETDEV_DEVTYPE(net, &gadget_type);
+
+ status = register_netdev(net);
+ if (status < 0) {
+ dev_dbg(&g->dev, "register_netdev failed, %d\n", status);
+ free_netdev(net);
+ } else {
+ INFO(dev, "MAC %pM\n", net->dev_addr);
+ INFO(dev, "HOST MAC %pM\n", dev->host_mac);
+
+ the_dev = dev;
+ }
+
+ return status;
+}
+
+/**
+ * gether_cleanup - remove Ethernet-over-USB device
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gether_setup().
+ */
+void gether_cleanup(void)
+{
+ if (!the_dev)
+ return;
+
+ unregister_netdev(the_dev->net);
+ flush_work_sync(&the_dev->work);
+ free_netdev(the_dev->net);
+
+ the_dev = NULL;
+}
+
+
+/**
+ * gether_connect - notify network layer that USB link is active
+ * @link: the USB link, set up with endpoints, descriptors matching
+ * current device speed, and any framing wrapper(s) set up.
+ * Context: irqs blocked
+ *
+ * This is called to activate endpoints and let the network layer know
+ * the connection is active ("carrier detect"). It may cause the I/O
+ * queues to open and start letting network packets flow, but will in
+ * any case activate the endpoints so that they respond properly to the
+ * USB host.
+ *
+ * Verify net_device pointer returned using IS_ERR(). If it doesn't
+ * indicate some error code (negative errno), ep->driver_data values
+ * have been overwritten.
+ */
+struct net_device *gether_connect(struct gether *link)
+{
+ struct eth_dev *dev = the_dev;
+ int result = 0;
+
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ link->in_ep->driver_data = dev;
+ result = usb_ep_enable(link->in_ep, link->in);
+ if (result != 0) {
+ DBG(dev, "enable %s --> %d\n",
+ link->in_ep->name, result);
+ goto fail0;
+ }
+
+ link->out_ep->driver_data = dev;
+ result = usb_ep_enable(link->out_ep, link->out);
+ if (result != 0) {
+ DBG(dev, "enable %s --> %d\n",
+ link->out_ep->name, result);
+ goto fail1;
+ }
+
+ if (result == 0)
+ result = alloc_requests(dev, link, qlen(dev->gadget));
+
+ if (result == 0) {
+ dev->zlp = link->is_zlp_ok;
+ DBG(dev, "qlen %d\n", qlen(dev->gadget));
+
+ dev->header_len = link->header_len;
+ dev->unwrap = link->unwrap;
+ dev->wrap = link->wrap;
+
+ spin_lock(&dev->lock);
+ dev->port_usb = link;
+ link->ioport = dev;
+ if (netif_running(dev->net)) {
+ if (link->open)
+ link->open(link);
+ } else {
+ if (link->close)
+ link->close(link);
+ }
+ spin_unlock(&dev->lock);
+
+ netif_carrier_on(dev->net);
+ if (netif_running(dev->net))
+ eth_start(dev, GFP_ATOMIC);
+
+ /* on error, disable any endpoints */
+ } else {
+ (void) usb_ep_disable(link->out_ep);
+fail1:
+ (void) usb_ep_disable(link->in_ep);
+ }
+fail0:
+ /* caller is responsible for cleanup on error */
+ if (result < 0)
+ return ERR_PTR(result);
+ return dev->net;
+}
+
+/**
+ * gether_disconnect - notify network layer that USB link is inactive
+ * @link: the USB link, on which gether_connect() was called
+ * Context: irqs blocked
+ *
+ * This is called to deactivate endpoints and let the network layer know
+ * the connection went inactive ("no carrier").
+ *
+ * On return, the state is as if gether_connect() had never been called.
+ * The endpoints are inactive, and accordingly without active USB I/O.
+ * Pointers to endpoint descriptors and endpoint private data are nulled.
+ */
+void gether_disconnect(struct gether *link)
+{
+ struct eth_dev *dev = link->ioport;
+ struct usb_request *req;
+
+ if (!dev)
+ return;
+
+ DBG(dev, "%s\n", __func__);
+
+ netif_stop_queue(dev->net);
+ netif_carrier_off(dev->net);
+
+ /* disable endpoints, forcing (synchronous) completion
+ * of all pending i/o. then free the request objects
+ * and forget about the endpoints.
+ */
+ usb_ep_disable(link->in_ep);
+ spin_lock(&dev->req_lock);
+ while (!list_empty(&dev->tx_reqs)) {
+ req = container_of(dev->tx_reqs.next,
+ struct usb_request, list);
+ list_del(&req->list);
+
+ spin_unlock(&dev->req_lock);
+ usb_ep_free_request(link->in_ep, req);
+ spin_lock(&dev->req_lock);
+ }
+ spin_unlock(&dev->req_lock);
+ link->in_ep->driver_data = NULL;
+ link->in = NULL;
+
+ usb_ep_disable(link->out_ep);
+ spin_lock(&dev->req_lock);
+ while (!list_empty(&dev->rx_reqs)) {
+ req = container_of(dev->rx_reqs.next,
+ struct usb_request, list);
+ list_del(&req->list);
+
+ spin_unlock(&dev->req_lock);
+ usb_ep_free_request(link->out_ep, req);
+ spin_lock(&dev->req_lock);
+ }
+ spin_unlock(&dev->req_lock);
+ link->out_ep->driver_data = NULL;
+ link->out = NULL;
+
+ /* finish forgetting about this USB link episode */
+ dev->header_len = 0;
+ dev->unwrap = NULL;
+ dev->wrap = NULL;
+
+ spin_lock(&dev->lock);
+ dev->port_usb = NULL;
+ link->ioport = NULL;
+ spin_unlock(&dev->lock);
+}
diff --git a/drivers/usb/gadget/gadget_gbhc/u_ether.h b/drivers/usb/gadget/gadget_gbhc/u_ether.h
new file mode 100644
index 0000000..2779865
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/u_ether.h
@@ -0,0 +1,127 @@
+/*
+ * u_ether.h -- interface to USB gadget "ethernet link" utilities
+ *
+ * Copyright (C) 2003-2005,2008 David Brownell
+ * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __U_ETHER_H
+#define __U_ETHER_H
+
+#include <linux/err.h>
+#include <linux/if_ether.h>
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+#include "gadget_chips.h"
+
+
+/*
+ * This represents the USB side of an "ethernet" link, managed by a USB
+ * function which provides control and (maybe) framing. Two functions
+ * in different configurations could share the same ethernet link/netdev,
+ * using different host interaction models.
+ *
+ * There is a current limitation that only one instance of this link may
+ * be present in any given configuration. When that's a problem, network
+ * layer facilities can be used to package multiple logical links on this
+ * single "physical" one.
+ */
+struct gether {
+ struct usb_function func;
+
+ /* updated by gether_{connect,disconnect} */
+ struct eth_dev *ioport;
+
+ /* endpoints handle full and/or high speeds */
+ struct usb_ep *in_ep;
+ struct usb_ep *out_ep;
+
+ /* descriptors match device speed at gether_connect() time */
+ struct usb_endpoint_descriptor *in;
+ struct usb_endpoint_descriptor *out;
+
+ bool is_zlp_ok;
+
+ u16 cdc_filter;
+
+ /* hooks for added framing, as needed for RNDIS and EEM. */
+ u32 header_len;
+ /* NCM requires fixed size bundles */
+ bool is_fixed;
+ u32 fixed_out_len;
+ u32 fixed_in_len;
+ struct sk_buff *(*wrap)(struct gether *port,
+ struct sk_buff *skb);
+ int (*unwrap)(struct gether *port,
+ struct sk_buff *skb,
+ struct sk_buff_head *list);
+
+ /* called on network open/close */
+ void (*open)(struct gether *);
+ void (*close)(struct gether *);
+};
+
+#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \
+ |USB_CDC_PACKET_TYPE_ALL_MULTICAST \
+ |USB_CDC_PACKET_TYPE_PROMISCUOUS \
+ |USB_CDC_PACKET_TYPE_DIRECTED)
+
+
+/* netdev setup/teardown as directed by the gadget driver */
+int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]);
+void gether_cleanup(void);
+
+/* connect/disconnect is handled by individual functions */
+struct net_device *gether_connect(struct gether *);
+void gether_disconnect(struct gether *);
+
+/* Some controllers can't support CDC Ethernet (ECM) ... */
+static inline bool can_support_ecm(struct usb_gadget *gadget)
+{
+ if (!gadget_supports_altsettings(gadget))
+ return false;
+
+ /* Everything else is *presumably* fine ... but this is a bit
+ * chancy, so be **CERTAIN** there are no hardware issues with
+ * your controller. Add it above if it can't handle CDC.
+ */
+ return true;
+}
+
+/* each configuration may bind one instance of an ethernet link */
+int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
+int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
+int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
+int eem_bind_config(struct usb_configuration *c);
+
+#if defined(USB_ETH_RNDIS) || defined(CONFIG_USB_ANDROID_RNDIS)
+
+int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
+
+#else
+
+static inline int
+rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
+{
+ return 0;
+}
+
+#endif
+
+#endif /* __U_ETHER_H */
diff --git a/drivers/usb/gadget/gadget_gbhc/u_serial.c b/drivers/usb/gadget/gadget_gbhc/u_serial.c
new file mode 100644
index 0000000..3dcbeca
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/u_serial.c
@@ -0,0 +1,1348 @@
+/*
+ * u_serial.c - utilities for USB gadget "serial port"/TTY support
+ *
+ * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com)
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * This code also borrows from usbserial.c, which is
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2000 Peter Berger (pberger@brimson.com)
+ * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com)
+ *
+ * This software is distributed under the terms of the GNU General
+ * Public License ("GPL") as published by the Free Software Foundation,
+ * either version 2 of that License or (at your option) any later version.
+ */
+
+/* #define VERBOSE_DEBUG */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/slab.h>
+
+#include "u_serial.h"
+
+
+/*
+ * This component encapsulates the TTY layer glue needed to provide basic
+ * "serial port" functionality through the USB gadget stack. Each such
+ * port is exposed through a /dev/ttyGS* node.
+ *
+ * After initialization (gserial_setup), these TTY port devices stay
+ * available until they are removed (gserial_cleanup). Each one may be
+ * connected to a USB function (gserial_connect), or disconnected (with
+ * gserial_disconnect) when the USB host issues a config change event.
+ * Data can only flow when the port is connected to the host.
+ *
+ * A given TTY port can be made available in multiple configurations.
+ * For example, each one might expose a ttyGS0 node which provides a
+ * login application. In one case that might use CDC ACM interface 0,
+ * while another configuration might use interface 3 for that. The
+ * work to handle that (including descriptor management) is not part
+ * of this component.
+ *
+ * Configurations may expose more than one TTY port. For example, if
+ * ttyGS0 provides login service, then ttyGS1 might provide dialer access
+ * for a telephone or fax link. And ttyGS2 might be something that just
+ * needs a simple byte stream interface for some messaging protocol that
+ * is managed in userspace ... OBEX, PTP, and MTP have been mentioned.
+ */
+
+#define PREFIX "ttyGS"
+
+/*
+ * gserial is the lifecycle interface, used by USB functions
+ * gs_port is the I/O nexus, used by the tty driver
+ * tty_struct links to the tty/filesystem framework
+ *
+ * gserial <---> gs_port ... links will be null when the USB link is
+ * inactive; managed by gserial_{connect,disconnect}(). each gserial
+ * instance can wrap its own USB control protocol.
+ * gserial->ioport == usb_ep->driver_data ... gs_port
+ * gs_port->port_usb ... gserial
+ *
+ * gs_port <---> tty_struct ... links will be null when the TTY file
+ * isn't opened; managed by gs_open()/gs_close()
+ * gserial->port_tty ... tty_struct
+ * tty_struct->driver_data ... gserial
+ */
+
+/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the
+ * next layer of buffering. For TX that's a circular buffer; for RX
+ * consider it a NOP. A third layer is provided by the TTY code.
+ */
+#define QUEUE_SIZE 16
+#define WRITE_BUF_SIZE 8192 /* TX only */
+
+/* circular buffer */
+struct gs_buf {
+ unsigned buf_size;
+ char *buf_buf;
+ char *buf_get;
+ char *buf_put;
+};
+
+/*
+ * The port structure holds info for each port, one for each minor number
+ * (and thus for each /dev/ node).
+ */
+struct gs_port {
+ spinlock_t port_lock; /* guard port_* access */
+
+ struct gserial *port_usb;
+ struct tty_struct *port_tty;
+
+ unsigned open_count;
+ bool openclose; /* open/close in progress */
+ u8 port_num;
+
+ wait_queue_head_t close_wait; /* wait for last close */
+
+ struct list_head read_pool;
+ int read_started;
+ int read_allocated;
+ struct list_head read_queue;
+ unsigned n_read;
+ struct tasklet_struct push;
+
+ struct list_head write_pool;
+ int write_started;
+ int write_allocated;
+ struct gs_buf port_write_buf;
+ wait_queue_head_t drain_wait; /* wait while writes drain */
+
+ /* REVISIT this state ... */
+ struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */
+};
+
+/* increase N_PORTS if you need more */
+#define N_PORTS 8
+static struct portmaster {
+ struct mutex lock; /* protect open/close */
+ struct gs_port *port;
+} ports[N_PORTS];
+static unsigned n_ports;
+
+#define GS_CLOSE_TIMEOUT 15 /* seconds */
+
+
+
+#ifdef VERBOSE_DEBUG
+#define pr_vdebug(fmt, arg...) \
+ pr_debug(fmt, ##arg)
+#else
+#define pr_vdebug(fmt, arg...) \
+ ({ if (0) pr_debug(fmt, ##arg); })
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+/* Circular Buffer */
+
+/*
+ * gs_buf_alloc
+ *
+ * Allocate a circular buffer and all associated memory.
+ */
+static int gs_buf_alloc(struct gs_buf *gb, unsigned size)
+{
+ gb->buf_buf = kmalloc(size, GFP_KERNEL);
+ if (gb->buf_buf == NULL)
+ return -ENOMEM;
+
+ gb->buf_size = size;
+ gb->buf_put = gb->buf_buf;
+ gb->buf_get = gb->buf_buf;
+
+ return 0;
+}
+
+/*
+ * gs_buf_free
+ *
+ * Free the buffer and all associated memory.
+ */
+static void gs_buf_free(struct gs_buf *gb)
+{
+ kfree(gb->buf_buf);
+ gb->buf_buf = NULL;
+}
+
+/*
+ * gs_buf_clear
+ *
+ * Clear out all data in the circular buffer.
+ */
+static void gs_buf_clear(struct gs_buf *gb)
+{
+ gb->buf_get = gb->buf_put;
+ /* equivalent to a get of all data available */
+}
+
+/*
+ * gs_buf_data_avail
+ *
+ * Return the number of bytes of data written into the circular
+ * buffer.
+ */
+static unsigned gs_buf_data_avail(struct gs_buf *gb)
+{
+ return (gb->buf_size + gb->buf_put - gb->buf_get) % gb->buf_size;
+}
+
+/*
+ * gs_buf_space_avail
+ *
+ * Return the number of bytes of space available in the circular
+ * buffer.
+ */
+static unsigned gs_buf_space_avail(struct gs_buf *gb)
+{
+ return (gb->buf_size + gb->buf_get - gb->buf_put - 1) % gb->buf_size;
+}
+
+/*
+ * gs_buf_put
+ *
+ * Copy data data from a user buffer and put it into the circular buffer.
+ * Restrict to the amount of space available.
+ *
+ * Return the number of bytes copied.
+ */
+static unsigned
+gs_buf_put(struct gs_buf *gb, const char *buf, unsigned count)
+{
+ unsigned len;
+
+ len = gs_buf_space_avail(gb);
+ if (count > len)
+ count = len;
+
+ if (count == 0)
+ return 0;
+
+ len = gb->buf_buf + gb->buf_size - gb->buf_put;
+ if (count > len) {
+ memcpy(gb->buf_put, buf, len);
+ memcpy(gb->buf_buf, buf+len, count - len);
+ gb->buf_put = gb->buf_buf + count - len;
+ } else {
+ memcpy(gb->buf_put, buf, count);
+ if (count < len)
+ gb->buf_put += count;
+ else /* count == len */
+ gb->buf_put = gb->buf_buf;
+ }
+
+ return count;
+}
+
+/*
+ * gs_buf_get
+ *
+ * Get data from the circular buffer and copy to the given buffer.
+ * Restrict to the amount of data available.
+ *
+ * Return the number of bytes copied.
+ */
+static unsigned
+gs_buf_get(struct gs_buf *gb, char *buf, unsigned count)
+{
+ unsigned len;
+
+ len = gs_buf_data_avail(gb);
+ if (count > len)
+ count = len;
+
+ if (count == 0)
+ return 0;
+
+ len = gb->buf_buf + gb->buf_size - gb->buf_get;
+ if (count > len) {
+ memcpy(buf, gb->buf_get, len);
+ memcpy(buf+len, gb->buf_buf, count - len);
+ gb->buf_get = gb->buf_buf + count - len;
+ } else {
+ memcpy(buf, gb->buf_get, count);
+ if (count < len)
+ gb->buf_get += count;
+ else /* count == len */
+ gb->buf_get = gb->buf_buf;
+ }
+
+ return count;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* I/O glue between TTY (upper) and USB function (lower) driver layers */
+
+/*
+ * gs_alloc_req
+ *
+ * Allocate a usb_request and its buffer. Returns a pointer to the
+ * usb_request or NULL if there is an error.
+ */
+struct usb_request *
+gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags)
+{
+ struct usb_request *req;
+
+ req = usb_ep_alloc_request(ep, kmalloc_flags);
+
+ if (req != NULL) {
+ req->length = len;
+ req->buf = kmalloc(len, kmalloc_flags);
+ if (req->buf == NULL) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+ }
+
+ return req;
+}
+
+/*
+ * gs_free_req
+ *
+ * Free a usb_request and its buffer.
+ */
+void gs_free_req(struct usb_ep *ep, struct usb_request *req)
+{
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+}
+
+/*
+ * gs_send_packet
+ *
+ * If there is data to send, a packet is built in the given
+ * buffer and the size is returned. If there is no data to
+ * send, 0 is returned.
+ *
+ * Called with port_lock held.
+ */
+static unsigned
+gs_send_packet(struct gs_port *port, char *packet, unsigned size)
+{
+ unsigned len;
+
+ len = gs_buf_data_avail(&port->port_write_buf);
+ if (len < size)
+ size = len;
+ if (size != 0)
+ size = gs_buf_get(&port->port_write_buf, packet, size);
+ return size;
+}
+
+/*
+ * gs_start_tx
+ *
+ * This function finds available write requests, calls
+ * gs_send_packet to fill these packets with data, and
+ * continues until either there are no more write requests
+ * available or no more data to send. This function is
+ * run whenever data arrives or write requests are available.
+ *
+ * Context: caller owns port_lock; port_usb is non-null.
+ */
+static int gs_start_tx(struct gs_port *port)
+/*
+__releases(&port->port_lock)
+__acquires(&port->port_lock)
+*/
+{
+ struct list_head *pool = &port->write_pool;
+ struct usb_ep *in = port->port_usb->in;
+ int status = 0;
+ bool do_tty_wake = false;
+
+ while (!list_empty(pool)) {
+ struct usb_request *req;
+ int len;
+
+ if (port->write_started >= QUEUE_SIZE)
+ break;
+
+ req = list_entry(pool->next, struct usb_request, list);
+ len = gs_send_packet(port, req->buf, in->maxpacket);
+ if (len == 0) {
+ wake_up_interruptible(&port->drain_wait);
+ break;
+ }
+ do_tty_wake = true;
+
+ req->length = len;
+ list_del(&req->list);
+ req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0);
+
+ pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
+ port->port_num, len, *((u8 *)req->buf),
+ *((u8 *)req->buf+1), *((u8 *)req->buf+2));
+
+ /* Drop lock while we call out of driver; completions
+ * could be issued while we do so. Disconnection may
+ * happen too; maybe immediately before we queue this!
+ *
+ * NOTE that we may keep sending data for a while after
+ * the TTY closed (dev->ioport->port_tty is NULL).
+ */
+ spin_unlock(&port->port_lock);
+ status = usb_ep_queue(in, req, GFP_ATOMIC);
+ spin_lock(&port->port_lock);
+
+ if (status) {
+ pr_debug("%s: %s %s err %d\n",
+ __func__, "queue", in->name, status);
+ list_add(&req->list, pool);
+ break;
+ }
+
+ port->write_started++;
+
+ /* abort immediately after disconnect */
+ if (!port->port_usb)
+ break;
+ }
+
+ if (do_tty_wake && port->port_tty)
+ tty_wakeup(port->port_tty);
+ return status;
+}
+
+/*
+ * Context: caller owns port_lock, and port_usb is set
+ */
+static unsigned gs_start_rx(struct gs_port *port)
+/*
+__releases(&port->port_lock)
+__acquires(&port->port_lock)
+*/
+{
+ struct list_head *pool = &port->read_pool;
+ struct usb_ep *out = port->port_usb->out;
+
+ while (!list_empty(pool)) {
+ struct usb_request *req;
+ int status;
+ struct tty_struct *tty;
+
+ /* no more rx if closed */
+ tty = port->port_tty;
+ if (!tty)
+ break;
+
+ if (port->read_started >= QUEUE_SIZE)
+ break;
+
+ req = list_entry(pool->next, struct usb_request, list);
+ list_del(&req->list);
+ req->length = out->maxpacket;
+
+ /* drop lock while we call out; the controller driver
+ * may need to call us back (e.g. for disconnect)
+ */
+ spin_unlock(&port->port_lock);
+ status = usb_ep_queue(out, req, GFP_ATOMIC);
+ spin_lock(&port->port_lock);
+
+ if (status) {
+ pr_debug("%s: %s %s err %d\n",
+ __func__, "queue", out->name, status);
+ list_add(&req->list, pool);
+ break;
+ }
+ port->read_started++;
+
+ /* abort immediately after disconnect */
+ if (!port->port_usb)
+ break;
+ }
+ return port->read_started;
+}
+
+/*
+ * RX tasklet takes data out of the RX queue and hands it up to the TTY
+ * layer until it refuses to take any more data (or is throttled back).
+ * Then it issues reads for any further data.
+ *
+ * If the RX queue becomes full enough that no usb_request is queued,
+ * the OUT endpoint may begin NAKing as soon as its FIFO fills up.
+ * So QUEUE_SIZE packets plus however many the FIFO holds (usually two)
+ * can be buffered before the TTY layer's buffers (currently 64 KB).
+ */
+static void gs_rx_push(unsigned long _port)
+{
+ struct gs_port *port = (void *)_port;
+ struct tty_struct *tty;
+ struct list_head *queue = &port->read_queue;
+ bool disconnect = false;
+ bool do_push = false;
+
+ /* hand any queued data to the tty */
+ spin_lock_irq(&port->port_lock);
+ tty = port->port_tty;
+ while (!list_empty(queue)) {
+ struct usb_request *req;
+
+ req = list_first_entry(queue, struct usb_request, list);
+
+ /* discard data if tty was closed */
+ if (!tty)
+ goto recycle;
+
+ /* leave data queued if tty was rx throttled */
+ if (test_bit(TTY_THROTTLED, &tty->flags))
+ break;
+
+ switch (req->status) {
+ case -ESHUTDOWN:
+ disconnect = true;
+ pr_vdebug(PREFIX "%d: shutdown\n", port->port_num);
+ break;
+
+ default:
+ /* presumably a transient fault */
+ pr_warning(PREFIX "%d: unexpected RX status %d\n",
+ port->port_num, req->status);
+ /* FALLTHROUGH */
+ case 0:
+ /* normal completion */
+ break;
+ }
+
+ /* push data to (open) tty */
+ if (req->actual) {
+ char *packet = req->buf;
+ unsigned size = req->actual;
+ unsigned n;
+ int count;
+
+ /* we may have pushed part of this packet already... */
+ n = port->n_read;
+ if (n) {
+ packet += n;
+ size -= n;
+ }
+
+ count = tty_insert_flip_string(tty, packet, size);
+ if (count)
+ do_push = true;
+ if (count != size) {
+ /* stop pushing; TTY layer can't handle more */
+ port->n_read += count;
+ pr_vdebug(PREFIX "%d: rx block %d/%d\n",
+ port->port_num,
+ count, req->actual);
+ break;
+ }
+ port->n_read = 0;
+ }
+recycle:
+ list_move(&req->list, &port->read_pool);
+ port->read_started--;
+ }
+
+ /* Push from tty to ldisc; without low_latency set this is handled by
+ * a workqueue, so we won't get callbacks and can hold port_lock
+ */
+ if (tty && do_push) {
+ tty_flip_buffer_push(tty);
+ }
+
+
+ /* We want our data queue to become empty ASAP, keeping data
+ * in the tty and ldisc (not here). If we couldn't push any
+ * this time around, there may be trouble unless there's an
+ * implicit tty_unthrottle() call on its way...
+ *
+ * REVISIT we should probably add a timer to keep the tasklet
+ * from starving ... but it's not clear that case ever happens.
+ */
+ if (!list_empty(queue) && tty) {
+ if (!test_bit(TTY_THROTTLED, &tty->flags)) {
+ if (do_push)
+ tasklet_schedule(&port->push);
+ else
+ pr_warning(PREFIX "%d: RX not scheduled?\n",
+ port->port_num);
+ }
+ }
+
+ /* If we're still connected, refill the USB RX queue. */
+ if (!disconnect && port->port_usb)
+ gs_start_rx(port);
+
+ spin_unlock_irq(&port->port_lock);
+}
+
+static void gs_read_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct gs_port *port = ep->driver_data;
+
+ /* Queue all received data until the tty layer is ready for it. */
+ spin_lock(&port->port_lock);
+ list_add_tail(&req->list, &port->read_queue);
+ tasklet_schedule(&port->push);
+ spin_unlock(&port->port_lock);
+}
+
+static void gs_write_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct gs_port *port = ep->driver_data;
+
+ spin_lock(&port->port_lock);
+ list_add(&req->list, &port->write_pool);
+ port->write_started--;
+
+ switch (req->status) {
+ default:
+ /* presumably a transient fault */
+ pr_warning("%s: unexpected %s status %d\n",
+ __func__, ep->name, req->status);
+ /* FALL THROUGH */
+ case 0:
+ /* normal completion */
+ gs_start_tx(port);
+ break;
+
+ case -ESHUTDOWN:
+ /* disconnect */
+ pr_vdebug("%s: %s shutdown\n", __func__, ep->name);
+ break;
+ }
+
+ spin_unlock(&port->port_lock);
+}
+
+static void gs_free_requests(struct usb_ep *ep, struct list_head *head,
+ int *allocated)
+{
+ struct usb_request *req;
+
+ while (!list_empty(head)) {
+ req = list_entry(head->next, struct usb_request, list);
+ list_del(&req->list);
+ gs_free_req(ep, req);
+ if (allocated)
+ (*allocated)--;
+ }
+}
+
+static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head,
+ void (*fn)(struct usb_ep *, struct usb_request *),
+ int *allocated)
+{
+ int i;
+ struct usb_request *req;
+ int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE;
+
+ /* Pre-allocate up to QUEUE_SIZE transfers, but if we can't
+ * do quite that many this time, don't fail ... we just won't
+ * be as speedy as we might otherwise be.
+ */
+ for (i = 0; i < n; i++) {
+ req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC);
+ if (!req)
+ return list_empty(head) ? -ENOMEM : 0;
+ req->complete = fn;
+ list_add_tail(&req->list, head);
+ if (allocated)
+ (*allocated)++;
+ }
+ return 0;
+}
+
+/**
+ * gs_start_io - start USB I/O streams
+ * @dev: encapsulates endpoints to use
+ * Context: holding port_lock; port_tty and port_usb are non-null
+ *
+ * We only start I/O when something is connected to both sides of
+ * this port. If nothing is listening on the host side, we may
+ * be pointlessly filling up our TX buffers and FIFO.
+ */
+static int gs_start_io(struct gs_port *port)
+{
+ struct list_head *head = &port->read_pool;
+ struct usb_ep *ep = port->port_usb->out;
+ int status;
+ unsigned started;
+
+ /* Allocate RX and TX I/O buffers. We can't easily do this much
+ * earlier (with GFP_KERNEL) because the requests are coupled to
+ * endpoints, as are the packet sizes we'll be using. Different
+ * configurations may use different endpoints with a given port;
+ * and high speed vs full speed changes packet sizes too.
+ */
+ status = gs_alloc_requests(ep, head, gs_read_complete,
+ &port->read_allocated);
+ if (status)
+ return status;
+
+ status = gs_alloc_requests(port->port_usb->in, &port->write_pool,
+ gs_write_complete, &port->write_allocated);
+ if (status) {
+ gs_free_requests(ep, head, &port->read_allocated);
+ return status;
+ }
+
+ /* queue read requests */
+ port->n_read = 0;
+ started = gs_start_rx(port);
+
+ /* unblock any pending writes into our circular buffer */
+ if (started) {
+ tty_wakeup(port->port_tty);
+ } else {
+ gs_free_requests(ep, head, &port->read_allocated);
+ gs_free_requests(port->port_usb->in, &port->write_pool,
+ &port->write_allocated);
+ status = -EIO;
+ }
+
+ return status;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* TTY Driver */
+
+/*
+ * gs_open sets up the link between a gs_port and its associated TTY.
+ * That link is broken *only* by TTY close(), and all driver methods
+ * know that.
+ */
+static int gs_open(struct tty_struct *tty, struct file *file)
+{
+ int port_num = tty->index;
+ struct gs_port *port;
+ int status;
+
+ if (port_num < 0 || port_num >= n_ports)
+ return -ENXIO;
+
+ do {
+ mutex_lock(&ports[port_num].lock);
+ port = ports[port_num].port;
+ if (!port)
+ status = -ENODEV;
+ else {
+ spin_lock_irq(&port->port_lock);
+
+ /* already open? Great. */
+ if (port->open_count) {
+ status = 0;
+ port->open_count++;
+
+ /* currently opening/closing? wait ... */
+ } else if (port->openclose) {
+ status = -EBUSY;
+
+ /* ... else we do the work */
+ } else {
+ status = -EAGAIN;
+ port->openclose = true;
+ }
+ spin_unlock_irq(&port->port_lock);
+ }
+ mutex_unlock(&ports[port_num].lock);
+
+ switch (status) {
+ default:
+ /* fully handled */
+ return status;
+ case -EAGAIN:
+ /* must do the work */
+ break;
+ case -EBUSY:
+ /* wait for EAGAIN task to finish */
+ msleep(1);
+ /* REVISIT could have a waitchannel here, if
+ * concurrent open performance is important
+ */
+ break;
+ }
+ } while (status != -EAGAIN);
+
+ /* Do the "real open" */
+ spin_lock_irq(&port->port_lock);
+
+ /* allocate circular buffer on first open */
+ if (port->port_write_buf.buf_buf == NULL) {
+
+ spin_unlock_irq(&port->port_lock);
+ status = gs_buf_alloc(&port->port_write_buf, WRITE_BUF_SIZE);
+ spin_lock_irq(&port->port_lock);
+
+ if (status) {
+ pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n",
+ port->port_num, tty, file);
+ port->openclose = false;
+ goto exit_unlock_port;
+ }
+ }
+
+ /* REVISIT if REMOVED (ports[].port NULL), abort the open
+ * to let rmmod work faster (but this way isn't wrong).
+ */
+
+ /* REVISIT maybe wait for "carrier detect" */
+
+ tty->driver_data = port;
+ port->port_tty = tty;
+
+ port->open_count = 1;
+ port->openclose = false;
+
+ /* if connected, start the I/O stream */
+ if (port->port_usb) {
+ struct gserial *gser = port->port_usb;
+
+ pr_debug("gs_open: start ttyGS%d\n", port->port_num);
+ gs_start_io(port);
+
+ if (gser->connect)
+ gser->connect(gser);
+ }
+
+ pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file);
+
+ status = 0;
+
+exit_unlock_port:
+ spin_unlock_irq(&port->port_lock);
+ return status;
+}
+
+static int gs_writes_finished(struct gs_port *p)
+{
+ int cond;
+
+ /* return true on disconnect or empty buffer */
+ spin_lock_irq(&p->port_lock);
+ cond = (p->port_usb == NULL) || !gs_buf_data_avail(&p->port_write_buf);
+ spin_unlock_irq(&p->port_lock);
+
+ return cond;
+}
+
+static void gs_close(struct tty_struct *tty, struct file *file)
+{
+ struct gs_port *port = tty->driver_data;
+ struct gserial *gser;
+
+ spin_lock_irq(&port->port_lock);
+
+ if (port->open_count != 1) {
+ if (port->open_count == 0)
+ WARN_ON(1);
+ else
+ --port->open_count;
+ goto exit;
+ }
+
+ pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file);
+
+ /* mark port as closing but in use; we can drop port lock
+ * and sleep if necessary
+ */
+ port->openclose = true;
+ port->open_count = 0;
+
+ gser = port->port_usb;
+ if (gser && gser->disconnect)
+ gser->disconnect(gser);
+
+ /* wait for circular write buffer to drain, disconnect, or at
+ * most GS_CLOSE_TIMEOUT seconds; then discard the rest
+ */
+ if (gs_buf_data_avail(&port->port_write_buf) > 0 && gser) {
+ spin_unlock_irq(&port->port_lock);
+ wait_event_interruptible_timeout(port->drain_wait,
+ gs_writes_finished(port),
+ GS_CLOSE_TIMEOUT * HZ);
+ spin_lock_irq(&port->port_lock);
+ gser = port->port_usb;
+ }
+
+ /* Iff we're disconnected, there can be no I/O in flight so it's
+ * ok to free the circular buffer; else just scrub it. And don't
+ * let the push tasklet fire again until we're re-opened.
+ */
+ if (gser == NULL)
+ gs_buf_free(&port->port_write_buf);
+ else
+ gs_buf_clear(&port->port_write_buf);
+
+ tty->driver_data = NULL;
+ port->port_tty = NULL;
+
+ port->openclose = false;
+
+ pr_debug("gs_close: ttyGS%d (%p,%p) done!\n",
+ port->port_num, tty, file);
+
+ wake_up_interruptible(&port->close_wait);
+exit:
+ spin_unlock_irq(&port->port_lock);
+}
+
+static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count)
+{
+ struct gs_port *port = tty->driver_data;
+ unsigned long flags;
+ int status;
+
+ pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n",
+ port->port_num, tty, count);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (count)
+ count = gs_buf_put(&port->port_write_buf, buf, count);
+ /* treat count == 0 as flush_chars() */
+ if (port->port_usb)
+ status = gs_start_tx(port);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return count;
+}
+
+static int gs_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct gs_port *port = tty->driver_data;
+ unsigned long flags;
+ int status;
+
+ pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %p\n",
+ port->port_num, tty, ch, __builtin_return_address(0));
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ status = gs_buf_put(&port->port_write_buf, &ch, 1);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return status;
+}
+
+static void gs_flush_chars(struct tty_struct *tty)
+{
+ struct gs_port *port = tty->driver_data;
+ unsigned long flags;
+
+ pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty);
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb)
+ gs_start_tx(port);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int gs_write_room(struct tty_struct *tty)
+{
+ struct gs_port *port = tty->driver_data;
+ unsigned long flags;
+ int room = 0;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb)
+ room = gs_buf_space_avail(&port->port_write_buf);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ pr_vdebug("gs_write_room: (%d,%p) room=%d\n",
+ port->port_num, tty, room);
+
+ return room;
+}
+
+static int gs_chars_in_buffer(struct tty_struct *tty)
+{
+ struct gs_port *port = tty->driver_data;
+ unsigned long flags;
+ int chars = 0;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ chars = gs_buf_data_avail(&port->port_write_buf);
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n",
+ port->port_num, tty, chars);
+
+ return chars;
+}
+
+/* undo side effects of setting TTY_THROTTLED */
+static void gs_unthrottle(struct tty_struct *tty)
+{
+ struct gs_port *port = tty->driver_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->port_usb) {
+ /* Kickstart read queue processing. We don't do xon/xoff,
+ * rts/cts, or other handshaking with the host, but if the
+ * read queue backs up enough we'll be NAKing OUT packets.
+ */
+ tasklet_schedule(&port->push);
+ pr_vdebug(PREFIX "%d: unthrottle\n", port->port_num);
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
+
+static int gs_break_ctl(struct tty_struct *tty, int duration)
+{
+ struct gs_port *port = tty->driver_data;
+ int status = 0;
+ struct gserial *gser;
+
+ pr_vdebug("gs_break_ctl: ttyGS%d, send break (%d) \n",
+ port->port_num, duration);
+
+ spin_lock_irq(&port->port_lock);
+ gser = port->port_usb;
+ if (gser && gser->send_break)
+ status = gser->send_break(gser, duration);
+ spin_unlock_irq(&port->port_lock);
+
+ return status;
+}
+
+static const struct tty_operations gs_tty_ops = {
+ .open = gs_open,
+ .close = gs_close,
+ .write = gs_write,
+ .put_char = gs_put_char,
+ .flush_chars = gs_flush_chars,
+ .write_room = gs_write_room,
+ .chars_in_buffer = gs_chars_in_buffer,
+ .unthrottle = gs_unthrottle,
+ .break_ctl = gs_break_ctl,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static struct tty_driver *gs_tty_driver;
+
+static int __init
+gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
+{
+ struct gs_port *port;
+
+ port = kzalloc(sizeof(struct gs_port), GFP_KERNEL);
+ if (port == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&port->port_lock);
+ init_waitqueue_head(&port->close_wait);
+ init_waitqueue_head(&port->drain_wait);
+
+ tasklet_init(&port->push, gs_rx_push, (unsigned long) port);
+
+ INIT_LIST_HEAD(&port->read_pool);
+ INIT_LIST_HEAD(&port->read_queue);
+ INIT_LIST_HEAD(&port->write_pool);
+
+ port->port_num = port_num;
+ port->port_line_coding = *coding;
+
+ ports[port_num].port = port;
+
+ return 0;
+}
+
+/**
+ * gserial_setup - initialize TTY driver for one or more ports
+ * @g: gadget to associate with these ports
+ * @count: how many ports to support
+ * Context: may sleep
+ *
+ * The TTY stack needs to know in advance how many devices it should
+ * plan to manage. Use this call to set up the ports you will be
+ * exporting through USB. Later, connect them to functions based
+ * on what configuration is activated by the USB host; and disconnect
+ * them as appropriate.
+ *
+ * An example would be a two-configuration device in which both
+ * configurations expose port 0, but through different functions.
+ * One configuration could even expose port 1 while the other
+ * one doesn't.
+ *
+ * Returns negative errno or zero.
+ */
+int __init gserial_setup(struct usb_gadget *g, unsigned count)
+{
+ unsigned i;
+ struct usb_cdc_line_coding coding;
+ int status;
+
+ if (count == 0 || count > N_PORTS)
+ return -EINVAL;
+
+ gs_tty_driver = alloc_tty_driver(count);
+ if (!gs_tty_driver)
+ return -ENOMEM;
+
+ gs_tty_driver->owner = THIS_MODULE;
+ gs_tty_driver->driver_name = "g_serial";
+ gs_tty_driver->name = PREFIX;
+ /* uses dynamically assigned dev_t values */
+
+ gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL;
+ gs_tty_driver->subtype = SERIAL_TYPE_NORMAL;
+ gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
+ gs_tty_driver->init_termios = tty_std_termios;
+
+ /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on
+ * MS-Windows. Otherwise, most of these flags shouldn't affect
+ * anything unless we were to actually hook up to a serial line.
+ */
+ gs_tty_driver->init_termios.c_cflag =
+ B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ gs_tty_driver->init_termios.c_ispeed = 9600;
+ gs_tty_driver->init_termios.c_ospeed = 9600;
+
+ coding.dwDTERate = cpu_to_le32(9600);
+ coding.bCharFormat = 8;
+ coding.bParityType = USB_CDC_NO_PARITY;
+ coding.bDataBits = USB_CDC_1_STOP_BITS;
+
+ tty_set_operations(gs_tty_driver, &gs_tty_ops);
+
+ /* make devices be openable */
+ for (i = 0; i < count; i++) {
+ mutex_init(&ports[i].lock);
+ status = gs_port_alloc(i, &coding);
+ if (status) {
+ count = i;
+ goto fail;
+ }
+ }
+ n_ports = count;
+
+ /* export the driver ... */
+ status = tty_register_driver(gs_tty_driver);
+ if (status) {
+ pr_err("%s: cannot register, err %d\n",
+ __func__, status);
+ goto fail;
+ }
+
+ /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */
+ for (i = 0; i < count; i++) {
+ struct device *tty_dev;
+
+ tty_dev = tty_register_device(gs_tty_driver, i, &g->dev);
+ if (IS_ERR(tty_dev))
+ pr_warning("%s: no classdev for port %d, err %ld\n",
+ __func__, i, PTR_ERR(tty_dev));
+ }
+
+ pr_debug("%s: registered %d ttyGS* device%s\n", __func__,
+ count, (count == 1) ? "" : "s");
+
+ return status;
+fail:
+ while (count--)
+ kfree(ports[count].port);
+ put_tty_driver(gs_tty_driver);
+ gs_tty_driver = NULL;
+ return status;
+}
+
+static int gs_closed(struct gs_port *port)
+{
+ int cond;
+
+ spin_lock_irq(&port->port_lock);
+ cond = (port->open_count == 0) && !port->openclose;
+ spin_unlock_irq(&port->port_lock);
+ return cond;
+}
+
+/**
+ * gserial_cleanup - remove TTY-over-USB driver and devices
+ * Context: may sleep
+ *
+ * This is called to free all resources allocated by @gserial_setup().
+ * Accordingly, it may need to wait until some open /dev/ files have
+ * closed.
+ *
+ * The caller must have issued @gserial_disconnect() for any ports
+ * that had previously been connected, so that there is never any
+ * I/O pending when it's called.
+ */
+void gserial_cleanup(void)
+{
+ unsigned i;
+ struct gs_port *port;
+
+ if (!gs_tty_driver)
+ return;
+
+ /* start sysfs and /dev/ttyGS* node removal */
+ for (i = 0; i < n_ports; i++)
+ tty_unregister_device(gs_tty_driver, i);
+
+ for (i = 0; i < n_ports; i++) {
+ /* prevent new opens */
+ mutex_lock(&ports[i].lock);
+ port = ports[i].port;
+ ports[i].port = NULL;
+ mutex_unlock(&ports[i].lock);
+
+ tasklet_kill(&port->push);
+
+ /* wait for old opens to finish */
+ wait_event(port->close_wait, gs_closed(port));
+
+ WARN_ON(port->port_usb != NULL);
+
+ kfree(port);
+ }
+ n_ports = 0;
+
+ tty_unregister_driver(gs_tty_driver);
+ put_tty_driver(gs_tty_driver);
+ gs_tty_driver = NULL;
+
+ pr_debug("%s: cleaned up ttyGS* support\n", __func__);
+}
+
+/**
+ * gserial_connect - notify TTY I/O glue that USB link is active
+ * @gser: the function, set up with endpoints and descriptors
+ * @port_num: which port is active
+ * Context: any (usually from irq)
+ *
+ * This is called activate endpoints and let the TTY layer know that
+ * the connection is active ... not unlike "carrier detect". It won't
+ * necessarily start I/O queues; unless the TTY is held open by any
+ * task, there would be no point. However, the endpoints will be
+ * activated so the USB host can perform I/O, subject to basic USB
+ * hardware flow control.
+ *
+ * Caller needs to have set up the endpoints and USB function in @dev
+ * before calling this, as well as the appropriate (speed-specific)
+ * endpoint descriptors, and also have set up the TTY driver by calling
+ * @gserial_setup().
+ *
+ * Returns negative errno or zero.
+ * On success, ep->driver_data will be overwritten.
+ */
+int gserial_connect(struct gserial *gser, u8 port_num)
+{
+ struct gs_port *port;
+ unsigned long flags;
+ int status;
+
+ if (!gs_tty_driver || port_num >= n_ports)
+ return -ENXIO;
+
+ /* we "know" gserial_cleanup() hasn't been called */
+ port = ports[port_num].port;
+
+ /* activate the endpoints */
+ status = usb_ep_enable(gser->in, gser->in_desc);
+ if (status < 0)
+ return status;
+ gser->in->driver_data = port;
+
+ status = usb_ep_enable(gser->out, gser->out_desc);
+ if (status < 0)
+ goto fail_out;
+ gser->out->driver_data = port;
+
+ /* then tell the tty glue that I/O can work */
+ spin_lock_irqsave(&port->port_lock, flags);
+ gser->ioport = port;
+ port->port_usb = gser;
+
+ /* REVISIT unclear how best to handle this state...
+ * we don't really couple it with the Linux TTY.
+ */
+ gser->port_line_coding = port->port_line_coding;
+
+ /* REVISIT if waiting on "carrier detect", signal. */
+
+ /* if it's already open, start I/O ... and notify the serial
+ * protocol about open/close status (connect/disconnect).
+ */
+ if (port->open_count) {
+ pr_debug("gserial_connect: start ttyGS%d\n", port->port_num);
+ gs_start_io(port);
+ if (gser->connect)
+ gser->connect(gser);
+ } else {
+ if (gser->disconnect)
+ gser->disconnect(gser);
+ }
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ return status;
+
+fail_out:
+ usb_ep_disable(gser->in);
+ gser->in->driver_data = NULL;
+ return status;
+}
+
+/**
+ * gserial_disconnect - notify TTY I/O glue that USB link is inactive
+ * @gser: the function, on which gserial_connect() was called
+ * Context: any (usually from irq)
+ *
+ * This is called to deactivate endpoints and let the TTY layer know
+ * that the connection went inactive ... not unlike "hangup".
+ *
+ * On return, the state is as if gserial_connect() had never been called;
+ * there is no active USB I/O on these endpoints.
+ */
+void gserial_disconnect(struct gserial *gser)
+{
+ struct gs_port *port = gser->ioport;
+ unsigned long flags;
+
+ if (!port)
+ return;
+
+ /* tell the TTY glue not to do I/O here any more */
+ spin_lock_irqsave(&port->port_lock, flags);
+
+ /* REVISIT as above: how best to track this? */
+ port->port_line_coding = gser->port_line_coding;
+
+ port->port_usb = NULL;
+ gser->ioport = NULL;
+ if (port->open_count > 0 || port->openclose) {
+ wake_up_interruptible(&port->drain_wait);
+ if (port->port_tty)
+ tty_hangup(port->port_tty);
+ }
+ spin_unlock_irqrestore(&port->port_lock, flags);
+
+ /* disable endpoints, aborting down any active I/O */
+ usb_ep_disable(gser->out);
+ gser->out->driver_data = NULL;
+
+ usb_ep_disable(gser->in);
+ gser->in->driver_data = NULL;
+
+ /* finally, free any unused/unusable I/O buffers */
+ spin_lock_irqsave(&port->port_lock, flags);
+ if (port->open_count == 0 && !port->openclose)
+ gs_buf_free(&port->port_write_buf);
+ gs_free_requests(gser->out, &port->read_pool, NULL);
+ gs_free_requests(gser->out, &port->read_queue, NULL);
+ gs_free_requests(gser->in, &port->write_pool, NULL);
+
+ port->read_allocated = port->read_started =
+ port->write_allocated = port->write_started = 0;
+
+ spin_unlock_irqrestore(&port->port_lock, flags);
+}
diff --git a/drivers/usb/gadget/gadget_gbhc/u_serial.h b/drivers/usb/gadget/gadget_gbhc/u_serial.h
new file mode 100644
index 0000000..300f0ed
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/u_serial.h
@@ -0,0 +1,67 @@
+/*
+ * u_serial.h - interface to USB gadget "serial port"/TTY utilities
+ *
+ * Copyright (C) 2008 David Brownell
+ * Copyright (C) 2008 by Nokia Corporation
+ *
+ * This software is distributed under the terms of the GNU General
+ * Public License ("GPL") as published by the Free Software Foundation,
+ * either version 2 of that License or (at your option) any later version.
+ */
+
+#ifndef __U_SERIAL_H
+#define __U_SERIAL_H
+
+#include <linux/usb/composite.h>
+#include <linux/usb/cdc.h>
+
+/*
+ * One non-multiplexed "serial" I/O port ... there can be several of these
+ * on any given USB peripheral device, if it provides enough endpoints.
+ *
+ * The "u_serial" utility component exists to do one thing: manage TTY
+ * style I/O using the USB peripheral endpoints listed here, including
+ * hookups to sysfs and /dev for each logical "tty" device.
+ *
+ * REVISIT at least ACM could support tiocmget() if needed.
+ *
+ * REVISIT someday, allow multiplexing several TTYs over these endpoints.
+ */
+struct gserial {
+ struct usb_function func;
+
+ /* port is managed by gserial_{connect,disconnect} */
+ struct gs_port *ioport;
+
+ struct usb_ep *in;
+ struct usb_ep *out;
+ struct usb_endpoint_descriptor *in_desc;
+ struct usb_endpoint_descriptor *out_desc;
+
+ /* REVISIT avoid this CDC-ACM support harder ... */
+ struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */
+
+ /* notification callbacks */
+ void (*connect)(struct gserial *p);
+ void (*disconnect)(struct gserial *p);
+ int (*send_break)(struct gserial *p, int duration);
+};
+
+/* utilities to allocate/free request and buffer */
+struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags);
+void gs_free_req(struct usb_ep *, struct usb_request *req);
+
+/* port setup/teardown is handled by gadget driver */
+int gserial_setup(struct usb_gadget *g, unsigned n_ports);
+void gserial_cleanup(void);
+
+/* connect/disconnect is handled by individual functions */
+int gserial_connect(struct gserial *, u8 port_num);
+void gserial_disconnect(struct gserial *);
+
+/* functions are bound to configurations by a config or gadget driver */
+int acm_bind_config(struct usb_configuration *c, u8 port_num);
+int gser_bind_config(struct usb_configuration *c, u8 port_num);
+int obex_bind_config(struct usb_configuration *c, u8 port_num);
+
+#endif /* __U_SERIAL_H */
diff --git a/drivers/usb/gadget/gadget_gbhc/usbstring.c b/drivers/usb/gadget/gadget_gbhc/usbstring.c
new file mode 100644
index 0000000..58c4d37
--- /dev/null
+++ b/drivers/usb/gadget/gadget_gbhc/usbstring.c
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2003 David Brownell
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/string.h>
+#include <linux/device.h>
+#include <linux/init.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include <asm/unaligned.h>
+
+
+static int utf8_to_utf16le(const char *s, __le16 *cp, unsigned len)
+{
+ int count = 0;
+ u8 c;
+ u16 uchar;
+
+ /* this insists on correct encodings, though not minimal ones.
+ * BUT it currently rejects legit 4-byte UTF-8 code points,
+ * which need surrogate pairs. (Unicode 3.1 can use them.)
+ */
+ while (len != 0 && (c = (u8) *s++) != 0) {
+ if (unlikely(c & 0x80)) {
+ // 2-byte sequence:
+ // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
+ if ((c & 0xe0) == 0xc0) {
+ uchar = (c & 0x1f) << 6;
+
+ c = (u8) *s++;
+ if ((c & 0xc0) != 0x80)
+ goto fail;
+ c &= 0x3f;
+ uchar |= c;
+
+ // 3-byte sequence (most CJKV characters):
+ // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
+ } else if ((c & 0xf0) == 0xe0) {
+ uchar = (c & 0x0f) << 12;
+
+ c = (u8) *s++;
+ if ((c & 0xc0) != 0x80)
+ goto fail;
+ c &= 0x3f;
+ uchar |= c << 6;
+
+ c = (u8) *s++;
+ if ((c & 0xc0) != 0x80)
+ goto fail;
+ c &= 0x3f;
+ uchar |= c;
+
+ /* no bogus surrogates */
+ if (0xd800 <= uchar && uchar <= 0xdfff)
+ goto fail;
+
+ // 4-byte sequence (surrogate pairs, currently rare):
+ // 11101110wwwwzzzzyy + 110111yyyyxxxxxx
+ // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
+ // (uuuuu = wwww + 1)
+ // FIXME accept the surrogate code points (only)
+
+ } else
+ goto fail;
+ } else
+ uchar = c;
+ put_unaligned_le16(uchar, cp++);
+ count++;
+ len--;
+ }
+ return count;
+fail:
+ return -1;
+}
+
+
+/**
+ * usb_gadget_get_string - fill out a string descriptor
+ * @table: of c strings encoded using UTF-8
+ * @id: string id, from low byte of wValue in get string descriptor
+ * @buf: at least 256 bytes
+ *
+ * Finds the UTF-8 string matching the ID, and converts it into a
+ * string descriptor in utf16-le.
+ * Returns length of descriptor (always even) or negative errno
+ *
+ * If your driver needs stings in multiple languages, you'll probably
+ * "switch (wIndex) { ... }" in your ep0 string descriptor logic,
+ * using this routine after choosing which set of UTF-8 strings to use.
+ * Note that US-ASCII is a strict subset of UTF-8; any string bytes with
+ * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1
+ * characters (which are also widely used in C strings).
+ */
+int
+usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf)
+{
+ struct usb_string *s;
+ int len;
+
+ /* descriptor 0 has the language id */
+ if (id == 0) {
+ buf [0] = 4;
+ buf [1] = USB_DT_STRING;
+ buf [2] = (u8) table->language;
+ buf [3] = (u8) (table->language >> 8);
+ return 4;
+ }
+ for (s = table->strings; s && s->s; s++)
+ if (s->id == id)
+ break;
+
+ /* unrecognized: stall. */
+ if (!s || !s->s)
+ return -EINVAL;
+
+ /* string descriptors have length, tag, then UTF16-LE text */
+ len = min ((size_t) 126, strlen (s->s));
+ memset (buf + 2, 0, 2 * len); /* zero all the bytes */
+ len = utf8_to_utf16le(s->s, (__le16 *)&buf[2], len);
+ if (len < 0)
+ return -EINVAL;
+ buf [0] = (len + 1) * 2;
+ buf [1] = USB_DT_STRING;
+ return buf [0];
+}
+
diff --git a/drivers/usb/gadget/multi_config.c b/drivers/usb/gadget/multi_config.c
new file mode 100644
index 0000000..2e4f52e
--- /dev/null
+++ b/drivers/usb/gadget/multi_config.c
@@ -0,0 +1,260 @@
+/*
+ * File Name : multi_config.c
+ *
+ * Virtual multi configuration utilities for composite USB gadgets.
+ * This utilitie can support variable interface for variable Host PC.
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Author: SoonYong, Cho <soonyong.cho@samsung.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 "multi_config.h"
+
+static int multi; /* current configuration */
+static int is_multi; /* Is multi configuration available ? */
+static int stringMode = OTHER_REQUEST;
+static int interfaceCount;
+
+/* Description : Set configuration number
+ * Parameter : unsigned num (host request)
+ * Return value : always return 0 (It's virtual multiconfiguration)
+ */
+unsigned set_config_number(unsigned num)
+{
+ if (num != 0)
+ USB_DBG_ESS("multi config_num=%d(zero base)\n", num);
+ else
+ USB_DBG_ESS("single config\n");
+
+ multi = num; /* save config number from Host request */
+ return 0; /* always return 0 config */
+}
+
+/* Description : Get configuration number
+ * Return value : virtual multiconfiguration number (zero base)
+ */
+int get_config_number(void)
+{
+ USB_DBG("multi=%d\n", multi);
+
+ return multi;
+}
+
+/* Description : Check configuration number
+ * Parameter : unsigned num (host request)
+ * Return value : 1 (true : virtual multi configuraiton)
+ 0 (false : normal configuration)
+ */
+int check_config(unsigned num)
+{
+ USB_DBG("num=%d, multi=%d\n", num, multi);
+ /* multi is zero base, but num is 1 ase */
+ if (num && num == multi + 1) {
+ USB_DBG("Use virtual multi configuration\n");
+ return 1;
+ } else {
+ USB_DBG("normal configuration\n");
+ return 0;
+ }
+}
+
+/* Description : Search number of configuration including virtual configuration
+ * Parameter : usb_configuration *c (referenced configuration)
+ unsigned count (real number of configuration)
+ * Return value : virtual or real number of configuration
+ */
+unsigned count_multi_config(struct usb_configuration *c, unsigned count)
+{
+ int f_first = 0;
+ int f_second = 0;
+ int f_exception = 0;
+ struct usb_function *f;
+ is_multi = 0;
+
+ if (!c) {
+ USB_DBG("usb_configuration is not valid\n");
+ return 0;
+ }
+
+ list_for_each_entry(f, &c->functions, list) {
+ if (!strcmp(f->name, MULTI_FUNCTION_1)) {
+ USB_DBG("%s +\n", MULTI_FUNCTION_1);
+ f_first = 1;
+ } else if (!strcmp(f->name, MULTI_FUNCTION_2)) {
+ USB_DBG("%s +\n", MULTI_FUNCTION_2);
+ f_second = 1;
+ } else if (!strcmp(f->name, MULTI_EXCEPTION_FUNCTION)) {
+ USB_DBG("exception %s +\n", MULTI_EXCEPTION_FUNCTION);
+ f_exception = 1;
+ }
+ }
+
+ if (f_first && f_second && !f_exception) {
+ USB_DBG_ESS("ready multi\n");
+ is_multi = 1;
+ return 2;
+ }
+ return count;
+}
+
+/* Description : Is multi configuration available ?
+ * Return value : 1 (true), 0 (false)
+ */
+int is_multi_configuration(void)
+{
+ USB_DBG("= %d\n", is_multi);
+ return is_multi;
+}
+
+/* Description : Check function to skip for multi configuration
+ * Parameter : char* name (function name)
+ * Return value : 0 (not available), 1 (available)
+ */
+int is_available_function(const char *name)
+{
+ if (is_multi_configuration()) {
+ USB_DBG("multi case\n");
+ if (!multi) {
+ if (!strcmp(name, MAIN_FUNCTION)) {
+ USB_DBG("%s is available.\n",
+ MAIN_FUNCTION);
+ return 1;
+ }
+ return 0; /* anothor function is not available */
+ } else {
+ USB_DBG("multi=%d all available\n", multi);
+ }
+ }
+ return 1; /* if single configuration, every function is available */
+}
+
+/* Description : Change configuration using virtual multi configuration.
+ * Parameter : struct usb_funciton f (to be changed function interface)
+ void *next (next means usb req->buf)
+ int len (length for to fill buffer)
+ struct usb_configuration *config
+ (To reference interface array of current config)
+ enum usb_device_speed speed (usb speed)
+ * Return value : "ret < 0" means fillbuffer function is failed.
+ */
+int change_conf(struct usb_function *f,
+ void *next, int len,
+ struct usb_configuration *config,
+ enum usb_device_speed speed)
+{
+ u8 *dest;
+ int status = 0;
+ struct usb_descriptor_header *descriptor;
+ struct usb_interface_descriptor *intf;
+ int index_intf = 0;
+ int change_intf = 0;
+ struct usb_descriptor_header **descriptors;
+
+ USB_DBG("f->%s process multi\n", f->name);
+
+ if (!f || !config || !next) {
+ USB_DBG_ESS("one of f, config, next is not valid\n");
+ return -EFAULT;
+ }
+ if (speed == USB_SPEED_HIGH)
+ descriptors = f->hs_descriptors;
+ else
+ descriptors = f->descriptors;
+ if (!descriptors) {
+ USB_DBG_ESS("descriptor is not available\n");
+ return -EFAULT;
+ }
+
+ if (f->set_config_desc)
+ f->set_config_desc(stringMode);
+
+ /* set interface numbers dynamically */
+ dest = next;
+
+ while ((descriptor = *descriptors++) != NULL) {
+ intf = (struct usb_interface_descriptor *)dest;
+ if (intf->bDescriptorType == USB_DT_INTERFACE) {
+ if (intf->bAlternateSetting == 0) {
+ intf->bInterfaceNumber = interfaceCount++;
+ USB_DBG("a=0 intf->bInterfaceNumber=%d\n",
+ intf->bInterfaceNumber);
+ } else {
+ intf->bInterfaceNumber = interfaceCount - 1;
+ USB_DBG("a!=0 intf->bInterfaceNumber=%d\n",
+ intf->bInterfaceNumber);
+ }
+ config->interface
+ [intf->bInterfaceNumber]
+ = f;
+ if (f->set_intf_num) {
+ change_intf = 1;
+ f->set_intf_num(f,
+ intf->bInterfaceNumber,
+ index_intf++);
+ }
+ }
+ dest += intf->bLength;
+ }
+
+ if (change_intf) {
+ if (speed == USB_SPEED_HIGH)
+ descriptors = f->hs_descriptors;
+ else
+ descriptors = f->descriptors;
+ status = usb_descriptor_fillbuf(
+ next, len,
+ (const struct usb_descriptor_header **)
+ descriptors);
+ if (status < 0) {
+ USB_DBG_ESS("usb_descriptor_fillbuf failed\n");
+ return status;
+ }
+ }
+
+ return status;
+}
+
+/* Description : Set interface count
+ * Parameter : struct usb_configuration *config
+ * (To reference interface array of current config)
+ * struct usb_config_descriptor *c
+ * (number of interfaces)
+ * Return value : void
+ */
+void set_interface_count(struct usb_configuration *config,
+ struct usb_config_descriptor *c)
+{
+ USB_DBG_ESS("next_interface_id=%d\n", interfaceCount);
+ config->next_interface_id = interfaceCount;
+ config->interface[interfaceCount] = 0;
+ c->bNumInterfaces = interfaceCount;
+ interfaceCount = 0;
+ return ;
+}
+
+/* Description : Set string mode
+ * This mode will be used for deciding other interface.
+ * Parameter : u16 w_length
+ * - 2 means MAC request.
+ * - Windows and Linux PC always request 255 size.
+ */
+void set_string_mode(u16 w_length)
+{
+ if (w_length == 2) {
+ USB_DBG("mac request\n");
+ stringMode = MAC_REQUEST;
+ } else if (w_length == 0) {
+ USB_DBG("initialize string mode\n");
+ stringMode = OTHER_REQUEST;
+ }
+}
diff --git a/drivers/usb/gadget/multi_config.h b/drivers/usb/gadget/multi_config.h
new file mode 100644
index 0000000..792bbae
--- /dev/null
+++ b/drivers/usb/gadget/multi_config.h
@@ -0,0 +1,135 @@
+/*
+ * File Name : multi_config.h
+ *
+ * Virtual multi configuration utilities for composite USB gadgets.
+ * This utilitie can support variable interface for variable Host PC.
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Author: SoonYong, Cho <soonyong.cho@samsung.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 __MULTI_CONFIG_H
+#define __MULTI_CONFIG_H
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/usb/composite.h>
+
+/*
+ * Debugging macro and defines
+ */
+/*#define USB_DEBUG
+ *#define USB_MORE_DEBUG
+ */
+#define USB_DEBUG_ESS
+
+#ifdef USB_DEBUG
+# ifdef USB_MORE_DEBUG
+# define USB_DBG(fmt, args...) \
+ printk(KERN_INFO "usb: %s "fmt, __func__, ##args)
+# else
+# define USB_DBG(fmt, args...) \
+ printk(KERN_DEBUG "usb: %s "fmt, __func__, ##args)
+# endif
+#else /* DO NOT PRINT LOG */
+# define USB_DBG(fmt, args...) do { } while (0)
+#endif /* USB_DEBUG */
+
+#ifdef USB_DEBUG_ESS
+# ifdef USB_MORE_DEBUG
+# define USB_DBG_ESS(fmt, args...) \
+ printk(KERN_INFO "usb: %s "fmt, __func__, ##args)
+# else
+# define USB_DBG_ESS(fmt, args...) \
+ printk(KERN_DEBUG "usb: %s "fmt, __func__, ##args)
+# endif
+#else /* DO NOT PRINT LOG */
+# define USB_DBG_ESS(fmt, args...) do { } while (0)
+#endif /* USB_DEBUG_ESS */
+
+#define MAIN_FUNCTION "mtp"
+#define MULTI_FUNCTION_1 "mtp"
+#define MULTI_FUNCTION_2 "acm0"
+#define MULTI_EXCEPTION_FUNCTION "adb"
+
+#define MAC_REQUEST 0
+#define OTHER_REQUEST 1
+
+/* Description : Set configuration number
+ * Parameter : unsigned num (host request)
+ * Return value : always return 0 (It's virtual multiconfiguration)
+ */
+unsigned set_config_number(unsigned num);
+
+/* Description : Get configuration number
+ * Return value : virtual multiconfiguration number (zero base)
+ */
+int get_config_number(void);
+
+/* Description : Check configuration number
+ * Parameter : unsigned num (host request)
+ * Return value : 1 (true : virtual multi configuraiton)
+ 0 (false : normal configuration)
+ */
+int check_config(unsigned num);
+
+/* Description : Search number of configuration including virtual configuration
+ * Parameter : usb_configuration *c (referenced configuration)
+ unsigned count (real number of configuration)
+ * Return value : virtual or real number of configuration
+ */
+unsigned count_multi_config(struct usb_configuration *c, unsigned count);
+
+/* Description : Is multi configuration available ?
+ * Return value : 1 (true), 0 (false)
+ */
+int is_multi_configuration(void);
+
+/* Description : Check function to skip for multi configuration
+ * Parameter : char* name (function name)
+ * Return value : 0 (not available), 1 (available)
+ */
+int is_available_function(const char *name);
+
+/* Description : Change configuration using virtual multi configuration.
+ * Parameter : struct usb_funciton f (to be changed function interface)
+ void *next (next means usb req->buf)
+ int len (length for to fill buffer)
+ struct usb_configuration *config
+ (To reference interface array of current config)
+ enum usb_device_speed speed (usb speed)
+ * Return value : "ret < 0" means fillbuffer function is failed.
+ */
+int change_conf(struct usb_function *f,
+ void *next, int len,
+ struct usb_configuration *config,
+ enum usb_device_speed speed);
+
+/* Description : Set interface count
+ * Parameter : struct usb_configuration *config
+ (To reference interface array of current config)
+ struct usb_config_descriptor *c
+ (number of interfaces)
+ * Return value : void
+ */
+void set_interface_count(struct usb_configuration *config,
+ struct usb_config_descriptor *c);
+
+/* Description : Set string mode
+ * This mode will be used for deciding other interface.
+ * Parameter : u16 w_length
+ * - 2 means MAC request.
+ * - Windows and Linux PC always request 255 size.
+ */
+void set_string_mode(u16 w_length);
+#endif /* __MULTI_CONFIG_H */
diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h
new file mode 100644
index 0000000..a75f8f2
--- /dev/null
+++ b/drivers/usb/gadget/s3c_udc.h
@@ -0,0 +1,156 @@
+/*
+ * drivers/usb/gadget/s3c_udc.h
+ * Samsung S3C on-chip full/high speed USB device controllers
+ * Copyright (C) 2005 for Samsung Electronics
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#ifndef __S3C_USB_GADGET
+#define __S3C_USB_GADGET
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/mm.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include <asm/byteorder.h>
+#include <asm/dma.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/unaligned.h>
+#include <linux/wakelock.h>
+
+/* Max packet size */
+#if defined(CONFIG_USB_GADGET_S3C_FS)
+#define EP0_FIFO_SIZE 8
+#define EP_FIFO_SIZE 64
+#define S3C_MAX_ENDPOINTS 5
+#elif defined(CONFIG_USB_GADGET_S3C_HS) || defined(CONFIG_PLAT_S5P64XX)\
+ || defined(CONFIG_PLAT_S5PC11X) || defined(CONFIG_CPU_S5P6442)\
+ || defined(CONFIG_CPU_S5P6450) || defined(CONFIG_CPU_S5PV310)\
+ || defined(CONFIG_ARCH_EXYNOS)
+
+#define EP0_FIFO_SIZE 64
+#define EP_FIFO_SIZE 512
+#define EP_FIFO_SIZE2 1024
+#define S3C_MAX_ENDPOINTS 16
+#define DED_TX_FIFO 1 /* Dedicated NPTx fifo for s5p6440 */
+#else
+#define EP0_FIFO_SIZE 64
+#define EP_FIFO_SIZE 512
+#define EP_FIFO_SIZE2 1024
+#define S3C_MAX_ENDPOINTS 16
+#endif
+
+#define WAIT_FOR_SETUP 0
+#define DATA_STATE_XMIT 1
+#define DATA_STATE_NEED_ZLP 2
+#define WAIT_FOR_OUT_STATUS 3
+#define DATA_STATE_RECV 4
+#define RegReadErr 5
+#define FAIL_TO_SETUP 6
+
+#define TEST_J_SEL 0x1
+#define TEST_K_SEL 0x2
+#define TEST_SE0_NAK_SEL 0x3
+#define TEST_PACKET_SEL 0x4
+#define TEST_FORCE_ENABLE_SEL 0x5
+
+/* ************************************************************************* */
+/* IO
+ */
+
+typedef enum ep_type {
+ ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt
+} ep_type_t;
+
+struct s3c_ep {
+ struct usb_ep ep;
+ struct s3c_udc *dev;
+
+ const struct usb_endpoint_descriptor *desc;
+ struct list_head queue;
+ unsigned long pio_irqs;
+
+ u8 stopped;
+ u8 bEndpointAddress;
+ u8 bmAttributes;
+
+ ep_type_t ep_type;
+ u32 fifo;
+#ifdef CONFIG_USB_GADGET_S3C_FS
+ u32 csr1;
+ u32 csr2;
+#endif
+};
+
+struct s3c_request {
+ struct usb_request req;
+ struct list_head queue;
+ unsigned char mapped;
+ unsigned written_bytes;
+};
+
+struct s3c_udc {
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+ struct platform_device *dev;
+ struct clk *clk;
+ spinlock_t lock;
+
+ int ep0state;
+ struct s3c_ep ep[S3C_MAX_ENDPOINTS];
+
+ unsigned char usb_address;
+ struct usb_ctrlrequest *usb_ctrl;
+ dma_addr_t usb_ctrl_dma;
+
+ void __iomem *regs;
+ struct resource *regs_res;
+ unsigned int irq;
+ unsigned req_pending:1, req_std:1, req_config:1;
+ struct wake_lock usbd_wake_lock;
+ struct wake_lock usb_cb_wake_lock;
+ int udc_enabled;
+};
+
+extern struct s3c_udc *the_controller;
+extern void samsung_cable_check_status(int flag);
+
+#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN) == USB_DIR_IN)
+
+#define ep_index(EP) ((EP)->bEndpointAddress&0xF)
+#define ep_maxpacket(EP) ((EP)->ep.maxpacket)
+
+#endif
diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c
new file mode 100644
index 0000000..6703973
--- /dev/null
+++ b/drivers/usb/gadget/s3c_udc_otg.c
@@ -0,0 +1,1402 @@
+/*
+ * drivers/usb/gadget/s3c_udc_otg.c
+ * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers
+ *
+ * Copyright (C) 2008 for Samsung Electronics
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "s3c_udc.h"
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <mach/map.h>
+#include <plat/regs-otg.h>
+#include <plat/usb-phy.h>
+#include <plat/usbgadget.h>
+#include <plat/cpu.h>
+
+#if 1
+#undef DEBUG_S3C_UDC_SETUP
+#undef DEBUG_S3C_UDC_EP0
+#undef DEBUG_S3C_UDC_ISR
+#undef DEBUG_S3C_UDC_OUT_EP
+#undef DEBUG_S3C_UDC_IN_EP
+#undef DEBUG_S3C_UDC
+#else
+#define DEBUG_S3C_UDC_SETUP
+#define DEBUG_S3C_UDC_EP0
+#define DEBUG_S3C_UDC_ISR
+#define DEBUG_S3C_UDC_OUT_EP
+#define DEBUG_S3C_UDC_IN_EP
+#define DEBUG_S3C_UDC
+#endif
+
+#define EP0_CON 0
+#define EP1_OUT 1
+#define EP2_IN 2
+#define EP3_IN 3
+#define EP_MASK 0xF
+
+#define BACK2BACK_SIZE 4
+
+#if defined(DEBUG_S3C_UDC_SETUP) || defined(DEBUG_S3C_UDC_ISR)\
+ || defined(DEBUG_S3C_UDC_OUT_EP)
+
+static char *state_names[] = {
+ "WAIT_FOR_SETUP",
+ "DATA_STATE_XMIT",
+ "DATA_STATE_NEED_ZLP",
+ "WAIT_FOR_OUT_STATUS",
+ "DATA_STATE_RECV",
+ };
+#endif
+
+#ifdef DEBUG_S3C_UDC_SETUP
+#define DEBUG_SETUP(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_SETUP(fmt, args...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S3C_UDC_EP0
+#define DEBUG_EP0(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_EP0(fmt, args...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S3C_UDC
+#define DEBUG(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG(fmt, args...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S3C_UDC_ISR
+#define DEBUG_ISR(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_ISR(fmt, args...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S3C_UDC_OUT_EP
+#define DEBUG_OUT_EP(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_OUT_EP(fmt, args...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S3C_UDC_IN_EP
+#define DEBUG_IN_EP(fmt, args...) printk(fmt, ##args)
+#else
+#define DEBUG_IN_EP(fmt, args...) do {} while (0)
+#endif
+
+#define DRIVER_DESC "S3C HS USB OTG Device Driver,"\
+ "(c) 2008-2009 Samsung Electronics"
+#define DRIVER_VERSION "15 March 2009"
+
+struct s3c_udc *the_controller;
+
+static const char driver_name[] = "s3c-udc";
+static const char driver_desc[] = DRIVER_DESC;
+static const char ep0name[] = "ep0-control";
+
+/* Max packet size*/
+static unsigned int ep0_fifo_size = 64;
+static unsigned int ep_fifo_size = 512;
+static unsigned int ep_fifo_size2 = 1024;
+static int reset_available = 1;
+
+/*
+ Local declarations.
+*/
+static int s3c_ep_enable(struct usb_ep *ep,
+ const struct usb_endpoint_descriptor *);
+static int s3c_ep_disable(struct usb_ep *ep);
+static struct usb_request *s3c_alloc_request(struct usb_ep *ep,
+ gfp_t gfp_flags);
+static void s3c_free_request(struct usb_ep *ep, struct usb_request *);
+
+static int s3c_queue(struct usb_ep *ep, struct usb_request *, gfp_t gfp_flags);
+static int s3c_dequeue(struct usb_ep *ep, struct usb_request *);
+static int s3c_fifo_status(struct usb_ep *ep);
+static void s3c_fifo_flush(struct usb_ep *ep);
+static void s3c_ep0_read(struct s3c_udc *dev);
+static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep);
+static void s3c_handle_ep0(struct s3c_udc *dev);
+static int s3c_ep0_write(struct s3c_udc *dev);
+static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req);
+static void done(struct s3c_ep *ep,
+ struct s3c_request *req, int status);
+static void stop_activity(struct s3c_udc *dev,
+ struct usb_gadget_driver *driver);
+static int udc_enable(struct s3c_udc *dev);
+static void udc_disable(struct s3c_udc *dev);
+static void udc_set_address(struct s3c_udc *dev, unsigned char address);
+static void reset_usbd(void);
+static void reconfig_usbd(void);
+static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed);
+static void nuke(struct s3c_ep *ep, int status);
+static int s3c_udc_set_halt(struct usb_ep *_ep, int value);
+static void s3c_udc_soft_connect(void);
+static void s3c_udc_soft_disconnect(void);
+
+static struct usb_ep_ops s3c_ep_ops = {
+ .enable = s3c_ep_enable,
+ .disable = s3c_ep_disable,
+
+ .alloc_request = s3c_alloc_request,
+ .free_request = s3c_free_request,
+
+ .queue = s3c_queue,
+ .dequeue = s3c_dequeue,
+
+ .set_halt = s3c_udc_set_halt,
+ .fifo_status = s3c_fifo_status,
+ .fifo_flush = s3c_fifo_flush,
+};
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
+
+static const char proc_node_name[] = "driver/udc";
+
+static int
+udc_proc_read(char *page, char **start, off_t off, int count,
+ int *eof, void *_dev)
+{
+ char *buf = page;
+ struct s3c_udc *dev = _dev;
+ char *next = buf;
+ unsigned size = count;
+ unsigned long flags;
+ int t;
+
+ if (off != 0)
+ return 0;
+
+ local_irq_save(flags);
+
+ /* basic device status */
+ t = scnprintf(next, size,
+ DRIVER_DESC "\n"
+ "%s version: %s\n"
+ "Gadget driver: %s\n"
+ "\n",
+ driver_name, DRIVER_VERSION,
+ dev->driver ? dev->driver->driver.name : "(none)");
+ size -= t;
+ next += t;
+
+ local_irq_restore(flags);
+ *eof = 1;
+ return count - size;
+}
+
+#define create_proc_files() \
+ create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev)
+#define remove_proc_files() \
+ remove_proc_entry(proc_node_name, NULL)
+
+#else /* !CONFIG_USB_GADGET_DEBUG_FILES */
+
+#define create_proc_files() do {} while (0)
+#define remove_proc_files() do {} while (0)
+
+#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
+
+#include "s3c_udc_otg_xfer_dma.c"
+
+/*
+ * udc_disable - disable USB device controller
+ */
+static void udc_disable(struct s3c_udc *dev)
+{
+ struct platform_device *pdev = dev->dev;
+ struct s5p_usbgadget_platdata *pdata = pdev->dev.platform_data;
+ u32 utemp;
+ DEBUG_SETUP("%s: %p\n", __func__, dev);
+
+ disable_irq(dev->irq);
+ udc_set_address(dev, 0);
+
+ dev->ep0state = WAIT_FOR_SETUP;
+ dev->gadget.speed = USB_SPEED_UNKNOWN;
+ dev->usb_address = 0;
+
+ /* Mask the core interrupt */
+ __raw_writel(0, dev->regs + S3C_UDC_OTG_GINTMSK);
+
+ /* Put the OTG device core in the disconnected state.*/
+ utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ utemp |= SOFT_DISCONNECT;
+ __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL);
+ udelay(20);
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, S5P_USB_PHY_DEVICE);
+ clk_disable(dev->clk);
+}
+
+/*
+ * udc_reinit - initialize software state
+ */
+static void udc_reinit(struct s3c_udc *dev)
+{
+ unsigned int i;
+
+ DEBUG_SETUP("%s: %p\n", __func__, dev);
+
+ /* device/ep0 records init */
+ INIT_LIST_HEAD(&dev->gadget.ep_list);
+ INIT_LIST_HEAD(&dev->gadget.ep0->ep_list);
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ /* basic endpoint records init */
+ for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
+ struct s3c_ep *ep = &dev->ep[i];
+
+ if (i != 0)
+ list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list);
+
+ ep->desc = 0;
+ ep->stopped = 0;
+ INIT_LIST_HEAD(&ep->queue);
+ ep->pio_irqs = 0;
+ }
+
+ /* the rest was statically initialized, and is read-only */
+}
+
+#define BYTES2MAXP(x) (x / 8)
+#define MAXP2BYTES(x) (x * 8)
+
+/* until it's enabled, this UDC should be completely invisible
+ * to any USB host.
+ */
+static int udc_enable(struct s3c_udc *dev)
+{
+ struct platform_device *pdev = dev->dev;
+ struct s5p_usbgadget_platdata *pdata = pdev->dev.platform_data;
+ DEBUG_SETUP("%s: %p\n", __func__, dev);
+
+ enable_irq(dev->irq);
+ clk_enable(dev->clk);
+ if (pdata->phy_init)
+ pdata->phy_init(pdev, S5P_USB_PHY_DEVICE);
+ reconfig_usbd();
+
+ DEBUG_SETUP("S3C USB 2.0 OTG Controller Core Initialized : 0x%x\n",
+ __raw_readl(dev->regs + S3C_UDC_OTG_GINTMSK));
+
+ dev->gadget.speed = USB_SPEED_UNKNOWN;
+
+ return 0;
+}
+
+int s3c_vbus_enable(struct usb_gadget *gadget, int is_active)
+{
+ unsigned long flags;
+ struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget);
+
+ if (dev->udc_enabled != is_active) {
+ dev->udc_enabled = is_active;
+
+ if (!is_active) {
+ printk(KERN_DEBUG "usb: %s is_active=%d(udc_disable)\n",
+ __func__, is_active);
+ spin_lock_irqsave(&dev->lock, flags);
+ stop_activity(dev, dev->driver);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ udc_disable(dev);
+#if defined(CONFIG_BATTERY_SAMSUNG)
+ s3c_udc_cable_disconnect(dev);
+#endif
+ wake_lock_timeout(&dev->usbd_wake_lock, HZ * 5);
+ wake_lock_timeout(&dev->usb_cb_wake_lock, HZ * 5);
+ } else {
+ printk(KERN_DEBUG "usb: %s is_active=%d(udc_enable)\n",
+ __func__, is_active);
+ wake_lock(&dev->usb_cb_wake_lock);
+ udc_reinit(dev);
+ udc_enable(dev);
+ }
+ } else {
+ printk(KERN_DEBUG "usb: %s, udc_enabled : %d, is_active : %d\n",
+ __func__, dev->udc_enabled, is_active);
+
+ }
+
+
+ return 0;
+}
+
+/*
+ Register entry point for the peripheral controller driver.
+*/
+int usb_gadget_probe_driver(struct usb_gadget_driver *driver,
+ int (*bind)(struct usb_gadget *))
+{
+ struct s3c_udc *dev = the_controller;
+ int retval;
+
+ DEBUG_SETUP("%s: %s\n", __func__, driver->driver.name);
+
+ if (!driver
+ || (driver->speed != USB_SPEED_FULL
+ && driver->speed != USB_SPEED_HIGH)
+ || !bind || !driver->disconnect || !driver->setup)
+ return -EINVAL;
+ if (!dev)
+ return -ENODEV;
+ if (dev->driver)
+ return -EBUSY;
+
+ /* first hook up the driver ... */
+ dev->driver = driver;
+ dev->gadget.dev.driver = &driver->driver;
+ retval = device_add(&dev->gadget.dev);
+
+ if (retval) { /* TODO */
+ printk(KERN_ERR "target device_add failed, error %d\n", retval);
+ return retval;
+ }
+
+ retval = bind(&dev->gadget);
+ if (retval) {
+ printk(KERN_ERR "%s: bind to driver %s --> error %d\n",
+ dev->gadget.name, driver->driver.name, retval);
+ device_del(&dev->gadget.dev);
+
+ dev->driver = 0;
+ dev->gadget.dev.driver = 0;
+ return retval;
+ }
+#if defined(CONFIG_USB_EXYNOS_SWITCH) || defined(CONFIG_MFD_MAX77693)\
+ || defined(CONFIG_MFD_MAX8997) || defined(CONFIG_MFD_MAX77686)
+ printk(KERN_INFO "usb: Skip udc_enable\n");
+#else
+ printk(KERN_INFO "usb: udc_enable\n");
+ udc_enable(dev);
+ dev->udc_enabled = 1;
+#endif
+ return 0;
+}
+EXPORT_SYMBOL(usb_gadget_probe_driver);
+
+/*
+ Unregister entry point for the peripheral controller driver.
+*/
+int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
+{
+ struct s3c_udc *dev = the_controller;
+ unsigned long flags;
+
+ if (!dev)
+ return -ENODEV;
+ if (!driver || driver != dev->driver)
+ return -EINVAL;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->driver = 0;
+ stop_activity(dev, driver);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ driver->unbind(&dev->gadget);
+ device_del(&dev->gadget.dev);
+
+ printk(KERN_INFO "Unregistered gadget driver '%s'\n",
+ driver->driver.name);
+
+ udc_disable(dev);
+ return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+/*
+ * done - retire a request; caller blocked irqs
+ */
+static void done(struct s3c_ep *ep, struct s3c_request *req, int status)
+{
+ unsigned int stopped = ep->stopped;
+ struct device *dev = &the_controller->dev->dev;
+
+ DEBUG("%s: %s %p, req = %p, stopped = %d\n",
+ __func__, ep->ep.name, ep, &req->req, stopped);
+
+ list_del_init(&req->queue);
+
+ if (likely(req->req.status == -EINPROGRESS))
+ req->req.status = status;
+ else
+ status = req->req.status;
+
+ if (req->mapped) {
+ dma_unmap_single(dev, req->req.dma, req->req.length,
+ (ep->bEndpointAddress & USB_DIR_IN) ?
+ DMA_TO_DEVICE : DMA_FROM_DEVICE);
+ req->req.dma = DMA_ADDR_INVALID;
+ req->mapped = 0;
+ }
+
+ if (status && status != -ESHUTDOWN) {
+ DEBUG("complete %s req %p stat %d len %u/%u\n",
+ ep->ep.name, &req->req, status,
+ req->req.actual, req->req.length);
+ }
+
+ /* don't modify queue heads during completion callback */
+ ep->stopped = 1;
+
+ spin_unlock(&ep->dev->lock);
+ req->req.complete(&ep->ep, &req->req);
+ spin_lock(&ep->dev->lock);
+
+ ep->stopped = stopped;
+}
+
+/*
+ * nuke - dequeue ALL requests
+ */
+static void nuke(struct s3c_ep *ep, int status)
+{
+ struct s3c_request *req;
+
+ DEBUG("%s: %s %p\n", __func__, ep->ep.name, ep);
+
+ /* called with irqs blocked */
+ while (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+ done(ep, req, status);
+ }
+}
+
+static void stop_activity(struct s3c_udc *dev,
+ struct usb_gadget_driver *driver)
+{
+ int i;
+
+ /* don't disconnect drivers more than once */
+ if (dev->gadget.speed == USB_SPEED_UNKNOWN)
+ driver = 0;
+ dev->gadget.speed = USB_SPEED_UNKNOWN;
+
+ /* prevent new request submissions, kill any outstanding requests */
+ for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
+ struct s3c_ep *ep = &dev->ep[i];
+ ep->stopped = 1;
+ nuke(ep, -ESHUTDOWN);
+ }
+
+ /* report disconnect; the driver is already quiesced */
+ if (driver) {
+ spin_unlock(&dev->lock);
+ driver->disconnect(&dev->gadget);
+ spin_lock(&dev->lock);
+ }
+
+ /* re-init driver-visible data structures */
+ udc_reinit(dev);
+}
+
+static void reset_usbd(void)
+{
+ struct s3c_udc *dev = the_controller;
+ unsigned int utemp;
+#ifdef DED_TX_FIFO
+ int i;
+
+ for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
+ utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(i));
+
+ if (utemp & DEPCTL_EPENA)
+ __raw_writel(utemp|DEPCTL_EPDIS|DEPCTL_SNAK,
+ dev->regs + S3C_UDC_OTG_DIEPCTL(i));
+
+ /* OUT EP0 cannot be disabled */
+ if (i != 0) {
+ utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(i));
+
+ if (utemp & DEPCTL_EPENA)
+ __raw_writel(utemp|DEPCTL_EPDIS|DEPCTL_SNAK,
+ dev->regs + S3C_UDC_OTG_DOEPCTL(i));
+ }
+ }
+#endif
+
+ /* 5. Configure OTG Core to initial settings of device mode.*/
+ __raw_writel(1<<18|0x0<<0,
+ dev->regs + S3C_UDC_OTG_DCFG);
+
+ mdelay(1);
+
+ /* 6. Unmask the core interrupts*/
+ __raw_writel(GINTMSK_INIT, dev->regs + S3C_UDC_OTG_GINTMSK);
+
+ /* 7. Set NAK bit of OUT EP0*/
+ __raw_writel(DEPCTL_SNAK,
+ dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+
+ /* 8. Unmask EPO interrupts*/
+ __raw_writel(((1<<EP0_CON)<<DAINT_OUT_BIT)|(1<<EP0_CON),
+ dev->regs + S3C_UDC_OTG_DAINTMSK);
+
+ /* 9. Unmask device OUT EP common interrupts*/
+ __raw_writel(DOEPMSK_INIT, dev->regs + S3C_UDC_OTG_DOEPMSK);
+
+ /* 10. Unmask device IN EP common interrupts*/
+ __raw_writel(DIEPMSK_INIT, dev->regs + S3C_UDC_OTG_DIEPMSK);
+
+ /* 11. Set Rx FIFO Size (in 32-bit words) */
+ __raw_writel(RX_FIFO_SIZE, dev->regs + S3C_UDC_OTG_GRXFSIZ);
+
+ /* 12. Set Non Periodic Tx FIFO Size*/
+ __raw_writel((NPTX_FIFO_SIZE) << 16 | (NPTX_FIFO_START_ADDR) << 0,
+ dev->regs + S3C_UDC_OTG_GNPTXFSIZ);
+
+#ifdef DED_TX_FIFO
+ for (i = 1; i < S3C_MAX_ENDPOINTS; i++)
+ __raw_writel((PTX_FIFO_SIZE) << 16 |
+ (NPTX_FIFO_START_ADDR + NPTX_FIFO_SIZE
+ + PTX_FIFO_SIZE*(i-1)) << 0,
+ dev->regs + S3C_UDC_OTG_DIEPTXF(i));
+#endif
+
+ /* Flush the RX FIFO */
+ __raw_writel(0x10, dev->regs + S3C_UDC_OTG_GRSTCTL);
+ while (__raw_readl(dev->regs + S3C_UDC_OTG_GRSTCTL) & 0x10)
+ ;
+
+ /* Flush all the Tx FIFO's */
+ __raw_writel(0x10<<6, dev->regs + S3C_UDC_OTG_GRSTCTL);
+ __raw_writel((0x10<<6)|0x20, dev->regs + S3C_UDC_OTG_GRSTCTL);
+ while (__raw_readl(dev->regs + S3C_UDC_OTG_GRSTCTL) & 0x20)
+ ;
+
+ /* 13. Clear NAK bit of OUT EP0*/
+ /* For Slave mode*/
+ __raw_writel(DEPCTL_CNAK,
+ dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+
+ /* 14. Initialize OTG Link Core.*/
+ __raw_writel(GAHBCFG_INIT, dev->regs + S3C_UDC_OTG_GAHBCFG);
+}
+
+static void reconfig_usbd(void)
+{
+ struct s3c_udc *dev = the_controller;
+ unsigned int utemp;
+
+ /* 2. Soft-reset OTG Core and then unreset again. */
+ __raw_writel(CORE_SOFT_RESET, dev->regs + S3C_UDC_OTG_GRSTCTL);
+
+ utemp = (0<<15 /* PHY Low Power Clock sel */
+ |1<<14 /* Non-Periodic TxFIFO Rewind Enable */
+ |0<<9|0<<8 /* [0:HNP disable, 1:HNP enable][ 0:SRP disable, 1:SRP enable] H1= 1,1 */
+ |0<<7 /* Ulpi DDR sel */
+ |0<<6 /* 0: high speed utmi+, 1: full speed serial */
+ |0<<4 /* 0: utmi+, 1:ulpi */
+ |0x7<<0); /* HS/FS Timeout */
+ /* [13:10] Turnaround time 0x5:16-bit 0x9:8-bit UTMI+ */
+ /* [3] phy i/f 0:8bit, 1:16bit */
+ if (soc_is_exynos4210())
+ utemp |= (0x5<<10 | 1<<3);
+ else
+ utemp |= (0x9<<10 | 0<<3);
+ __raw_writel(utemp, dev->regs + S3C_UDC_OTG_GUSBCFG);
+
+ /* 3. Put the OTG device core in the disconnected state.*/
+ utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ utemp |= SOFT_DISCONNECT;
+ __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL);
+
+ udelay(20);
+
+ /* 4. Make the OTG device core exit from the disconnected state.*/
+ utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ utemp = utemp & ~SOFT_DISCONNECT;
+ __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL);
+
+ reset_usbd();
+}
+
+static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed)
+{
+ unsigned int ep_ctrl;
+ int i;
+
+ if (speed == USB_SPEED_HIGH) {
+ ep0_fifo_size = 64;
+ ep_fifo_size = 512;
+ ep_fifo_size2 = 1024;
+ dev->gadget.speed = USB_SPEED_HIGH;
+ } else {
+ ep0_fifo_size = 64;
+ ep_fifo_size = 64;
+ ep_fifo_size2 = 64;
+ dev->gadget.speed = USB_SPEED_FULL;
+ }
+
+ dev->ep[0].ep.maxpacket = ep0_fifo_size;
+ for (i = 1; i < S3C_MAX_ENDPOINTS; i++)
+ dev->ep[i].ep.maxpacket = ep_fifo_size;
+
+ /* EP0 - Control IN (64 bytes)*/
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ __raw_writel(ep_ctrl|(0<<0), dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ /* EP0 - Control OUT (64 bytes)*/
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ __raw_writel(ep_ctrl|(0<<0), dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+}
+
+static int s3c_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct s3c_ep *ep;
+ struct s3c_udc *dev;
+ unsigned long flags;
+
+ DEBUG("%s: %p\n", __func__, _ep);
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep || !desc || ep->desc || _ep->name == ep0name
+ || desc->bDescriptorType != USB_DT_ENDPOINT
+ || ep->bEndpointAddress != desc->bEndpointAddress
+ || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) {
+
+ DEBUG("%s: bad ep or descriptor\n", __func__);
+ return -EINVAL;
+ }
+
+ /* xfer types must match, except that interrupt ~= bulk */
+ if (ep->bmAttributes != desc->bmAttributes
+ && ep->bmAttributes != USB_ENDPOINT_XFER_BULK
+ && desc->bmAttributes != USB_ENDPOINT_XFER_INT) {
+
+ DEBUG("%s: %s type mismatch\n", __func__, _ep->name);
+ return -EINVAL;
+ }
+
+ /* hardware _could_ do smaller, but driver doesn't */
+ if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK
+ && le16_to_cpu(desc->wMaxPacketSize) != ep_maxpacket(ep))
+ || !desc->wMaxPacketSize) {
+
+ DEBUG("%s: bad %s maxpacket\n", __func__, _ep->name);
+ return -ERANGE;
+ }
+
+ dev = ep->dev;
+ if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) {
+
+ DEBUG("%s: bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ ep->stopped = 0;
+ ep->desc = desc;
+ ep->pio_irqs = 0;
+ ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+
+ /* Reset halt state */
+ s3c_udc_set_halt(_ep, 0);
+
+ spin_lock_irqsave(&ep->dev->lock, flags);
+ s3c_udc_ep_activate(ep);
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+
+ DEBUG("%s: enabled %s, stopped = %d, maxpacket = %d\n",
+ __func__, _ep->name, ep->stopped, ep->ep.maxpacket);
+ return 0;
+}
+
+/** Disable EP
+ */
+static int s3c_ep_disable(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ unsigned long flags;
+
+ DEBUG("%s: %p\n", __func__, _ep);
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep || !ep->desc) {
+ DEBUG("%s: %s not enabled\n", __func__,
+ _ep ? ep->ep.name : NULL);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->dev->lock, flags);
+
+ /* Nuke all pending requests */
+ nuke(ep, -ESHUTDOWN);
+
+ ep->desc = 0;
+ ep->stopped = 1;
+
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+
+ DEBUG("%s: disabled %s\n", __func__, _ep->name);
+ return 0;
+}
+
+static struct usb_request *s3c_alloc_request(struct usb_ep *ep,
+ gfp_t gfp_flags)
+{
+ struct s3c_request *req;
+
+ DEBUG("%s: %s %p\n", __func__, ep->name, ep);
+
+ req = kmalloc(sizeof *req, gfp_flags);
+ if (!req)
+ return 0;
+
+ memset(req, 0, sizeof *req);
+ INIT_LIST_HEAD(&req->queue);
+ return &req->req;
+}
+
+static void s3c_free_request(struct usb_ep *ep, struct usb_request *_req)
+{
+ struct s3c_request *req;
+
+ DEBUG("%s: %p\n", __func__, ep);
+
+ req = container_of(_req, struct s3c_request, req);
+ WARN_ON(!list_empty(&req->queue));
+ kfree(req);
+}
+
+/* dequeue JUST ONE request */
+static int s3c_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct s3c_ep *ep;
+ struct s3c_request *req;
+ unsigned long flags;
+
+ DEBUG("%s: %p\n", __func__, _ep);
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep || ep->ep.name == ep0name)
+ return -EINVAL;
+
+ spin_lock_irqsave(&ep->dev->lock, flags);
+
+ /* make sure it's actually queued on this endpoint */
+ list_for_each_entry(req, &ep->queue, queue) {
+ if (&req->req == _req)
+ break;
+ }
+ if (&req->req != _req) {
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+ return -EINVAL;
+ }
+
+ done(ep, req, -ECONNRESET);
+
+ spin_unlock_irqrestore(&ep->dev->lock, flags);
+ return 0;
+}
+
+/** Return bytes in EP FIFO
+ */
+static int s3c_fifo_status(struct usb_ep *_ep)
+{
+ int count = 0;
+ struct s3c_ep *ep;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (!_ep) {
+ DEBUG("%s: bad ep\n", __func__);
+ return -ENODEV;
+ }
+
+ DEBUG("%s: %d\n", __func__, ep_index(ep));
+
+ /* LPD can't report unclaimed bytes from IN fifos */
+ if (ep_is_in(ep))
+ return -EOPNOTSUPP;
+
+ return count;
+}
+
+/** Flush EP FIFO
+ */
+static void s3c_fifo_flush(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
+ DEBUG("%s: bad ep\n", __func__);
+ return;
+ }
+
+ DEBUG("%s: %d\n", __func__, ep_index(ep));
+}
+
+/* ---------------------------------------------------------------------------
+ * device-scoped parts of the api to the usb controller hardware
+ * ---------------------------------------------------------------------------
+ */
+
+static int s3c_udc_get_frame(struct usb_gadget *_gadget)
+{
+ struct s3c_udc *dev = container_of(_gadget, struct s3c_udc, gadget);
+ /*fram count number [21:8]*/
+ unsigned int frame = __raw_readl(dev->regs + S3C_UDC_OTG_DSTS);
+
+ DEBUG("%s: %p\n", __func__, _gadget);
+ return frame & 0x3ff00;
+}
+
+static int s3c_udc_wakeup(struct usb_gadget *_gadget)
+{
+ DEBUG("%s: %p\n", __func__, _gadget);
+ return -ENOTSUPP;
+}
+
+static void s3c_udc_soft_connect(void)
+{
+ struct s3c_udc *dev = the_controller;
+ u32 uTemp;
+
+ DEBUG("[%s]\n", __func__);
+ uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ uTemp = uTemp & ~SOFT_DISCONNECT;
+ __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL);
+}
+
+static void s3c_udc_soft_disconnect(void)
+{
+ struct s3c_udc *dev = the_controller;
+ u32 uTemp;
+ unsigned long flags;
+
+ DEBUG("[%s]\n", __func__);
+ uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ uTemp |= SOFT_DISCONNECT;
+ __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL);
+
+ spin_lock_irqsave(&dev->lock, flags);
+ stop_activity(dev, dev->driver);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on)
+{
+ if (is_on)
+ s3c_udc_soft_connect();
+ else
+ s3c_udc_soft_disconnect();
+ return 0;
+}
+
+static const struct usb_gadget_ops s3c_udc_ops = {
+ .get_frame = s3c_udc_get_frame,
+ .wakeup = s3c_udc_wakeup,
+ /* current versions must always be self-powered */
+ .pullup = s3c_udc_pullup,
+ .vbus_session = s3c_vbus_enable,
+};
+
+static void nop_release(struct device *dev)
+{
+ DEBUG("%s %s\n", __func__, dev->bus_id);
+}
+
+static struct s3c_udc memory = {
+ .usb_address = 0,
+
+ .gadget = {
+ .ops = &s3c_udc_ops,
+ .ep0 = &memory.ep[0].ep,
+ .name = driver_name,
+ .dev = {
+ .release = nop_release,
+ },
+ },
+
+ /* control endpoint */
+ .ep[0] = {
+ .ep = {
+ .name = ep0name,
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP0_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = 0,
+ .bmAttributes = 0,
+
+ .ep_type = ep_control,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP0_FIFO,
+ },
+
+ /* first group of endpoints */
+ .ep[1] = {
+ .ep = {
+ .name = "ep1-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_OUT | 1,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_out,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP1_FIFO,
+ },
+
+ .ep[2] = {
+ .ep = {
+ .name = "ep2-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 2,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_in,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP2_FIFO,
+ },
+
+ .ep[3] = {
+ .ep = {
+ .name = "ep3-int",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 3,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+
+ .ep_type = ep_interrupt,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP3_FIFO,
+ },
+ .ep[4] = {
+ .ep = {
+ .name = "ep4-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_OUT | 4,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_out,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP4_FIFO,
+ },
+ .ep[5] = {
+ .ep = {
+ .name = "ep5-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 5,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_in,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP5_FIFO,
+ },
+ .ep[6] = {
+ .ep = {
+ .name = "ep6-int",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 6,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+
+ .ep_type = ep_interrupt,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP6_FIFO,
+ },
+ .ep[7] = {
+ .ep = {
+ .name = "ep7-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_OUT | 7,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_out,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP7_FIFO,
+ },
+ .ep[8] = {
+ .ep = {
+ .name = "ep8-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 8,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_in,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP8_FIFO,
+ },
+ .ep[9] = {
+ .ep = {
+ .name = "ep9-int",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 9,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+
+ .ep_type = ep_interrupt,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP9_FIFO,
+ },
+ .ep[10] = {
+ .ep = {
+ .name = "ep10-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_OUT | 10,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_out,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP10_FIFO,
+ },
+ .ep[11] = {
+ .ep = {
+ .name = "ep11-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 11,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_in,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP11_FIFO,
+ },
+ .ep[12] = {
+ .ep = {
+ .name = "ep12-int",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 12,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+
+ .ep_type = ep_interrupt,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP12_FIFO,
+ },
+ .ep[13] = {
+ .ep = {
+ .name = "ep13-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_OUT | 13,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_out,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP13_FIFO,
+ },
+ .ep[14] = {
+ .ep = {
+ .name = "ep14-bulk",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 14,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_in,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP14_FIFO,
+ },
+ .ep[15] = {
+ .ep = {
+ .name = "ep15-int",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 15,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+
+ .ep_type = ep_interrupt,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP15_FIFO,
+ },
+};
+
+/*
+ * probe - binds to the platform device
+ */
+static int s3c_udc_probe(struct platform_device *pdev)
+{
+ struct s5p_usbgadget_platdata *pdata;
+ struct s3c_udc *dev = &memory;
+ struct resource *res;
+ unsigned int irq;
+ int retval;
+
+ if (!pdev) {
+ pr_err("%s: pdev is null\n", __func__);
+ return -EINVAL;
+ }
+ DEBUG("%s: %p\n", __func__, pdev);
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data defined\n");
+ return -EINVAL;
+ }
+#ifdef CONFIG_USB_S3C_OTG_HOST
+ pdata->udc_irq = s3c_udc_irq;
+ pr_info("otg pdata %p, irq %p\n", pdata, pdata->udc_irq);
+#endif
+
+ spin_lock_init(&dev->lock);
+ dev->dev = pdev;
+
+ device_initialize(&dev->gadget.dev);
+ dev->gadget.dev.parent = &pdev->dev;
+ dev_set_name(&dev->gadget.dev, "%s", "gadget");
+
+ dev->gadget.is_dualspeed = 1; /* Hack only*/
+ dev->gadget.is_otg = 0;
+ dev->gadget.is_a_peripheral = 0;
+ dev->gadget.b_hnp_enable = 0;
+ dev->gadget.a_hnp_support = 0;
+ dev->gadget.a_alt_hnp_support = 0;
+
+ the_controller = dev;
+ platform_set_drvdata(pdev, dev);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ DEBUG(KERN_ERR "cannot find register resource 0\n");
+ return -EINVAL;
+ }
+
+ dev->regs_res = request_mem_region(res->start, resource_size(res),
+ dev_name(&pdev->dev));
+ if (!dev->regs_res) {
+ DEBUG(KERN_ERR "cannot reserve registers\n");
+ return -ENOENT;
+ }
+
+ dev->regs = ioremap(res->start, resource_size(res));
+ if (!dev->regs) {
+ DEBUG(KERN_ERR "cannot map registers\n");
+ retval = -ENXIO;
+ goto err_regs_res;
+ }
+
+ udc_reinit(dev);
+ wake_lock_init(&dev->usbd_wake_lock, WAKE_LOCK_SUSPEND,
+ "usb device wake lock");
+ wake_lock_init(&dev->usb_cb_wake_lock, WAKE_LOCK_SUSPEND,
+ "usb cb wake lock");
+
+ /* irq setup after old hardware state is cleaned up */
+ irq = platform_get_irq(pdev, 0);
+
+ retval =
+ request_irq(irq, s3c_udc_irq, 0, driver_name, dev);
+
+ if (retval != 0) {
+ DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name,
+ dev->irq, retval);
+ retval = -EBUSY;
+ goto err_regs;
+ }
+ dev->irq = irq;
+ disable_irq(dev->irq);
+
+ dev->clk = clk_get(&pdev->dev, "usbotg");
+
+ if (IS_ERR(dev->clk)) {
+ dev_err(&pdev->dev, "Failed to get clock\n");
+ goto err_irq;
+ }
+
+ dev->usb_ctrl = dma_alloc_coherent(&pdev->dev,
+ sizeof(struct usb_ctrlrequest)*BACK2BACK_SIZE,
+ &dev->usb_ctrl_dma, GFP_KERNEL);
+
+ if (!dev->usb_ctrl) {
+ DEBUG(KERN_ERR "%s: can't get usb_ctrl dma memory\n",
+ driver_name);
+ retval = -ENOMEM;
+ goto err_clk;
+ }
+
+ create_proc_files();
+
+ return retval;
+err_clk:
+ clk_put(dev->clk);
+err_irq:
+ free_irq(dev->irq, dev);
+err_regs:
+ iounmap(dev->regs);
+err_regs_res:
+ release_resource(dev->regs_res);
+ kfree(dev->regs_res);
+ return retval;
+}
+
+static int s3c_udc_remove(struct platform_device *pdev)
+{
+ struct s3c_udc *dev = platform_get_drvdata(pdev);
+
+ DEBUG("%s: %p\n", __func__, pdev);
+
+ remove_proc_files();
+ usb_gadget_unregister_driver(dev->driver);
+ clk_put(dev->clk);
+ if (dev->usb_ctrl)
+ dma_free_coherent(&pdev->dev,
+ sizeof(struct usb_ctrlrequest)*BACK2BACK_SIZE,
+ dev->usb_ctrl, dev->usb_ctrl_dma);
+ free_irq(dev->irq, dev);
+ iounmap(dev->regs);
+ release_resource(dev->regs_res);
+ kfree(dev->regs_res);
+
+ platform_set_drvdata(pdev, 0);
+
+ the_controller = 0;
+ wake_lock_destroy(&dev->usbd_wake_lock);
+ wake_lock_destroy(&dev->usb_cb_wake_lock);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c_udc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct s3c_udc *dev = the_controller;
+ int i;
+
+ if (dev->driver) {
+ if (dev->driver->suspend)
+ dev->driver->suspend(&dev->gadget);
+
+ /* Terminate any outstanding requests */
+ for (i = 0; i < S3C_MAX_ENDPOINTS; i++) {
+ struct s3c_ep *ep = &dev->ep[i];
+ if (ep->dev != NULL)
+ spin_lock(&ep->dev->lock);
+ ep->stopped = 1;
+ nuke(ep, -ESHUTDOWN);
+ if (ep->dev != NULL)
+ spin_unlock(&ep->dev->lock);
+ }
+
+ if (dev->driver->disconnect)
+ dev->driver->disconnect(&dev->gadget);
+
+#if defined(CONFIG_USB_EXYNOS_SWITCH) || defined(CONFIG_MFD_MAX77693)\
+ || defined(CONFIG_MFD_MAX8997) || defined(CONFIG_MFD_MAX77686)
+ /* Nothing to do */
+#else
+ udc_disable(dev);
+#endif
+ }
+
+ return 0;
+}
+
+static int s3c_udc_resume(struct platform_device *pdev)
+{
+ struct s3c_udc *dev = the_controller;
+
+ if (dev->driver) {
+ udc_reinit(dev);
+#if defined(CONFIG_USB_EXYNOS_SWITCH) || defined(CONFIG_MFD_MAX77693)\
+ || defined(CONFIG_MFD_MAX8997) || defined(CONFIG_MFD_MAX77686)
+ /* Nothing to do */
+#else
+ udc_enable(dev);
+#endif
+
+ if (dev->driver->resume)
+ dev->driver->resume(&dev->gadget);
+ }
+
+ return 0;
+}
+#else
+#define s3c_udc_suspend NULL
+#define s3c_udc_resume NULL
+#endif /* CONFIG_PM */
+
+/*-------------------------------------------------------------------------*/
+static struct platform_driver s3c_udc_driver = {
+ .probe = s3c_udc_probe,
+ .remove = s3c_udc_remove,
+ .suspend = s3c_udc_suspend,
+ .resume = s3c_udc_resume,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s3c-usbgadget",
+ },
+};
+
+static int __init udc_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&s3c_udc_driver);
+ if (!ret)
+ printk(KERN_INFO "%s : %s\n"
+ "%s : version %s\n",
+ driver_name, DRIVER_DESC,
+ driver_name, DRIVER_VERSION);
+
+ return ret;
+}
+
+static void __exit udc_exit(void)
+{
+ platform_driver_unregister(&s3c_udc_driver);
+ printk(KERN_INFO "Unloaded %s version %s\n",
+ driver_name, DRIVER_VERSION);
+}
+
+module_init(udc_init);
+module_exit(udc_exit);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Samsung");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c
new file mode 100644
index 0000000..7554772
--- /dev/null
+++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c
@@ -0,0 +1,1455 @@
+/* drivers/usb/gadget/s3c_udc_otg_xfer_dma.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com/
+ *
+ * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers
+ *
+ * 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.
+ */
+
+#define GINTMSK_INIT (INT_OUT_EP | INT_IN_EP | INT_RESUME |\
+ INT_ENUMDONE|INT_RESET|INT_SUSPEND)
+#define DOEPMSK_INIT (CTRL_OUT_EP_SETUP_PHASE_DONE | AHB_ERROR |\
+ TRANSFER_DONE)
+#define DIEPMSK_INIT (NON_ISO_IN_EP_TIMEOUT|AHB_ERROR|TRANSFER_DONE)
+#define GAHBCFG_INIT (PTXFE_HALF | NPTXFE_HALF | MODE_DMA | BURST_INCR4 |\
+ GBL_INT_UNMASK)
+
+#define DMA_ADDR_INVALID (~(dma_addr_t)0)
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+#include <linux/usb/composite.h>
+#endif
+static u8 clear_feature_num;
+static int clear_feature_flag;
+static int set_conf_done;
+
+/* Bulk-Only Mass Storage Reset (class-specific request) */
+#define GET_MAX_LUN_REQUEST 0xFE
+#define BOT_RESET_REQUEST 0xFF
+
+/* TEST MODE in set_feature request */
+#define TEST_SELECTOR_MASK 0xFF
+#define TEST_PKT_SIZE 53
+
+static u8 test_pkt[TEST_PKT_SIZE] __attribute__((aligned(8))) = {
+ /* JKJKJKJK x 9 */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ /* JJKKJJKK x 8 */
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ /* JJJJKKKK x 8 */
+ 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
+ /* JJJJJJJKKKKKKK x8 - '1' */
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ /* '1' + JJJJJJJK x 8 */
+ 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD,
+ /* {JKKKKKKK x 10},JK */
+ 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E
+};
+
+static void s3c_udc_ep_set_stall(struct s3c_ep *ep);
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+u32 cable_connected;
+
+void s3c_udc_cable_connect(struct s3c_udc *dev)
+{
+ samsung_cable_check_status(1);
+ cable_connected = 1;
+}
+
+void s3c_udc_cable_disconnect(struct s3c_udc *dev)
+{
+ if (cable_connected) {
+ samsung_cable_check_status(0);
+ cable_connected = 0;
+ }
+}
+#endif
+
+static inline void s3c_udc_ep0_zlp(struct s3c_udc *dev)
+{
+ u32 ep_ctrl;
+
+ __raw_writel(dev->usb_ctrl_dma, dev->regs + S3C_UDC_OTG_DIEPDMA(EP0_CON));
+ __raw_writel((1<<19 | 0<<0), dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ __raw_writel(ep_ctrl | DEPCTL_EPENA | DEPCTL_CNAK,
+ dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ DEBUG_EP0("%s:EP0 ZLP DIEPCTL0 = 0x%x\n",
+ __func__, __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)));
+}
+
+static inline void s3c_udc_pre_setup(struct s3c_udc *dev)
+{
+ u32 ep_ctrl;
+
+ DEBUG_IN_EP("%s : Prepare Setup packets.\n", __func__);
+
+ __raw_writel((3<<29) | (1<<19) | sizeof(struct usb_ctrlrequest),
+ dev->regs + S3C_UDC_OTG_DOEPTSIZ(EP0_CON));
+ __raw_writel(dev->usb_ctrl_dma, dev->regs + S3C_UDC_OTG_DOEPDMA(EP0_CON));
+
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ __raw_writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK,
+ dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+}
+
+static int setdma_rx(struct s3c_ep *ep, struct s3c_request *req)
+{
+ u32 *buf, ctrl;
+ u32 length, pktcnt;
+ u32 ep_num = ep_index(ep);
+ struct s3c_udc *udc = ep->dev;
+ struct device *dev = &udc->dev->dev;
+
+ buf = req->req.buf + req->req.actual;
+ prefetchw(buf);
+
+ length = req->req.length - req->req.actual;
+
+ req->req.dma = dma_map_single(dev, buf,
+ length, DMA_FROM_DEVICE);
+ req->mapped = 1;
+
+ if (length == 0)
+ pktcnt = 1;
+ else
+ pktcnt = (length - 1)/(ep->ep.maxpacket) + 1;
+
+ ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+
+ __raw_writel(virt_to_phys(buf), udc->regs + S3C_UDC_OTG_DOEPDMA(ep_num));
+ __raw_writel((pktcnt<<19) | (length<<0),
+ udc->regs + S3C_UDC_OTG_DOEPTSIZ(ep_num));
+ __raw_writel(DEPCTL_EPENA | DEPCTL_CNAK | ctrl,
+ udc->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+
+ DEBUG_OUT_EP("%s: EP%d RX DMA start : DOEPDMA = 0x%x,"
+ "DOEPTSIZ = 0x%x, DOEPCTL = 0x%x\n"
+ "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n",
+ __func__, ep_num,
+ __raw_readl(udc->regs + S3C_UDC_OTG_DOEPDMA(ep_num)),
+ __raw_readl(udc->regs + S3C_UDC_OTG_DOEPTSIZ(ep_num)),
+ __raw_readl(udc->regs + S3C_UDC_OTG_DOEPCTL(ep_num)),
+ buf, pktcnt, length);
+ return 0;
+}
+
+static int setdma_tx(struct s3c_ep *ep, struct s3c_request *req)
+{
+ u32 *buf, ctrl = 0;
+ u32 length, pktcnt;
+ u32 ep_num = ep_index(ep);
+ struct s3c_udc *udc = ep->dev;
+ struct device *dev = &udc->dev->dev;
+
+ buf = req->req.buf + req->req.actual;
+ prefetch(buf);
+ length = req->req.length - req->req.actual;
+
+ if (ep_num == EP0_CON)
+ length = min(length, (u32)ep_maxpacket(ep));
+
+ req->req.actual += length;
+
+ req->req.dma = dma_map_single(dev, buf,
+ length, DMA_TO_DEVICE);
+ req->mapped = 1;
+
+ if (length == 0)
+ pktcnt = 1;
+ else
+ pktcnt = (length - 1)/(ep->ep.maxpacket) + 1;
+
+#ifdef DED_TX_FIFO
+ /* Write the FIFO number to be used for this endpoint */
+ ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+ ctrl &= ~DEPCTL_TXFNUM_MASK;;
+ ctrl |= (ep_num << DEPCTL_TXFNUM_BIT);
+ __raw_writel(ctrl , udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+#endif
+
+ __raw_writel(virt_to_phys(buf), udc->regs + S3C_UDC_OTG_DIEPDMA(ep_num));
+ __raw_writel((pktcnt<<19)|(length<<0), udc->regs + S3C_UDC_OTG_DIEPTSIZ(ep_num));
+ ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+ __raw_writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl,
+ udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+
+#ifndef DED_TX_FIFO
+ ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ ctrl = (ctrl & ~(EP_MASK<<DEPCTL_NEXT_EP_BIT)) |
+ (ep_num<<DEPCTL_NEXT_EP_BIT);
+ __raw_writel(ctrl, udc->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+#endif
+
+ DEBUG_IN_EP("%s:EP%d TX DMA start : DIEPDMA0 = 0x%x,"
+ "DIEPTSIZ0 = 0x%x, DIEPCTL0 = 0x%x\n"
+ "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n",
+ __func__, ep_num,
+ __raw_readl(udc->regs + S3C_UDC_OTG_DIEPDMA(ep_num)),
+ __raw_readl(udc->regs + S3C_UDC_OTG_DIEPTSIZ(ep_num)),
+ __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num)),
+ buf, pktcnt, length);
+
+ req->written_bytes = length;
+ return length;
+}
+
+static void complete_rx(struct s3c_udc *dev, u8 ep_num)
+{
+ struct s3c_ep *ep = &dev->ep[ep_num];
+ struct s3c_request *req = NULL;
+ u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0;
+
+ if (list_empty(&ep->queue)) {
+ DEBUG_OUT_EP("%s: RX DMA done : NULL REQ on OUT EP-%d\n",
+ __func__, ep_num);
+ return;
+ }
+
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+
+ ep_tsr = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPTSIZ(ep_num));
+
+ if (ep_num == EP0_CON)
+ xfer_size = (ep_tsr & 0x7f);
+
+ else
+ xfer_size = (ep_tsr & 0x7fff);
+
+ __dma_single_cpu_to_dev(req->req.buf, req->req.length, DMA_FROM_DEVICE);
+ xfer_length = req->req.length - xfer_size;
+ req->req.actual += min(xfer_length, req->req.length - req->req.actual);
+ is_short = (xfer_length < ep->ep.maxpacket);
+
+ DEBUG_OUT_EP("%s: RX DMA done : ep = %d, rx bytes = %d/%d, "
+ "is_short = %d, DOEPTSIZ = 0x%x, remained bytes = %d\n",
+ __func__, ep_num, req->req.actual, req->req.length,
+ is_short, ep_tsr, xfer_size);
+
+ if (is_short || req->req.actual == xfer_length) {
+ if (ep_num == EP0_CON && dev->ep0state == DATA_STATE_RECV) {
+ DEBUG_OUT_EP(" => Send ZLP\n");
+ dev->ep0state = WAIT_FOR_SETUP;
+ s3c_udc_ep0_zlp(dev);
+ } else {
+ done(ep, req, 0);
+
+ if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next,
+ struct s3c_request, queue);
+ DEBUG_OUT_EP("%s: Next Rx request start...\n",
+ __func__);
+ setdma_rx(ep, req);
+ }
+ }
+ }
+}
+
+static void complete_tx(struct s3c_udc *dev, u8 ep_num)
+{
+ struct s3c_ep *ep = &dev->ep[ep_num];
+ struct s3c_request *req;
+ u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0;
+ u32 last;
+
+ if (list_empty(&ep->queue)) {
+ DEBUG_IN_EP("%s: TX DMA done : NULL REQ on IN EP-%d\n",
+ __func__, ep_num);
+ return;
+ }
+
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+
+ if (dev->ep0state == DATA_STATE_XMIT) {
+ DEBUG_IN_EP("%s: ep_num = %d, ep0stat == DATA_STATE_XMIT\n",
+ __func__, ep_num);
+
+ last = write_fifo_ep0(ep, req);
+
+ if (last)
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ return;
+ }
+
+ ep_tsr = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPTSIZ(ep_num));
+
+ if (ep_num == EP0_CON)
+ xfer_size = (ep_tsr & 0x7f);
+ else
+ xfer_size = (ep_tsr & 0x7fff);
+
+ req->req.actual = req->req.length - xfer_size;
+ xfer_length = req->req.length - xfer_size;
+ req->req.actual += min(xfer_length, req->req.length - req->req.actual);
+ is_short = (xfer_length < ep->ep.maxpacket);
+
+ DEBUG_IN_EP("%s: TX DMA done : ep = %d, tx bytes = %d/%d, "
+ "is_short = %d, DIEPTSIZ = 0x%x, remained bytes = %d\n",
+ __func__, ep_num, req->req.actual, req->req.length,
+ is_short, ep_tsr, xfer_size);
+
+ if (req->req.actual == req->req.length) {
+ /* send ZLP when req.zero is set and the last packet is maxpacket */
+ if (req->req.zero) {
+ req->req.zero = 0;
+ if (req->written_bytes == ep_maxpacket(ep)) {
+ setdma_tx(ep, req);
+ return;
+ }
+ }
+ done(ep, req, 0);
+
+ if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct s3c_request,
+ queue);
+ DEBUG_IN_EP("%s: Next Tx request start...\n", __func__);
+ setdma_tx(ep, req);
+ }
+ }
+}
+
+static inline void s3c_udc_check_tx_queue(struct s3c_udc *dev, u8 ep_num)
+{
+ struct s3c_ep *ep = &dev->ep[ep_num];
+ struct s3c_request *req;
+
+ DEBUG_IN_EP("%s: Check queue, ep_num = %d\n", __func__, ep_num);
+
+ if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+ DEBUG_IN_EP("%s: Next Tx request(0x%p) start...\n",
+ __func__, req);
+
+ if (ep_is_in(ep))
+ setdma_tx(ep, req);
+ else
+ setdma_rx(ep, req);
+ } else {
+ DEBUG_IN_EP("%s: NULL REQ on IN EP-%d\n", __func__, ep_num);
+
+ return;
+ }
+}
+
+static void process_ep_in_intr(struct s3c_udc *dev)
+{
+ u32 ep_intr, ep_intr_status;
+ u8 ep_num = 0;
+
+ ep_intr = __raw_readl(dev->regs + S3C_UDC_OTG_DAINT);
+ DEBUG_IN_EP("*** %s: EP In interrupt : DAINT = 0x%x\n",
+ __func__, ep_intr);
+
+ ep_intr &= DAINT_MASK;
+
+ while (ep_intr) {
+ if (ep_intr & 0x1) {
+ ep_intr_status = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPINT(ep_num));
+ DEBUG_IN_EP("\tEP%d-IN : DIEPINT = 0x%x\n",
+ ep_num, ep_intr_status);
+
+ /* Interrupt Clear */
+ __raw_writel(ep_intr_status, dev->regs + S3C_UDC_OTG_DIEPINT(ep_num));
+
+ if (ep_intr_status & TRANSFER_DONE) {
+ complete_tx(dev, ep_num);
+
+ if (ep_num == 0) {
+ if (dev->ep0state == WAIT_FOR_SETUP)
+ s3c_udc_pre_setup(dev);
+
+ /* continue transfer after
+ set_clear_halt for DMA mode */
+ if (clear_feature_flag == 1) {
+ s3c_udc_check_tx_queue(dev,
+ clear_feature_num);
+ clear_feature_flag = 0;
+ }
+ }
+ }
+ }
+ ep_num++;
+ ep_intr >>= 1;
+ }
+}
+
+static void process_ep_out_intr(struct s3c_udc *dev)
+{
+ u32 ep_intr, ep_intr_status;
+ u8 ep_num = 0;
+ u32 ep_ctrl = 0;
+ ep_intr = __raw_readl(dev->regs + S3C_UDC_OTG_DAINT);
+ DEBUG_OUT_EP("*** %s: EP OUT interrupt : DAINT = 0x%x\n",
+ __func__, ep_intr);
+
+ ep_intr = (ep_intr >> DAINT_OUT_BIT) & DAINT_MASK;
+
+ while (ep_intr) {
+ if (ep_intr & 0x1) {
+ ep_intr_status = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPINT(ep_num));
+ DEBUG_OUT_EP("\tEP%d-OUT : DOEPINT = 0x%x\n",
+ ep_num, ep_intr_status);
+
+ /* Interrupt Clear */
+ __raw_writel(ep_intr_status, dev->regs + S3C_UDC_OTG_DOEPINT(ep_num));
+
+ if (ep_num == 0) {
+ if (ep_intr_status &
+ CTRL_OUT_EP_SETUP_PHASE_DONE) {
+ DEBUG_OUT_EP("\tSETUP"
+ "packet(transaction)"
+ "arrived\n");
+ s3c_handle_ep0(dev);
+ }
+
+ if (ep_intr_status & TRANSFER_DONE) {
+ complete_rx(dev, ep_num);
+ __raw_writel((3<<29)|(1 << 19)|sizeof(struct usb_ctrlrequest),
+ dev->regs + S3C_UDC_OTG_DOEPTSIZ(EP0_CON));
+ __raw_writel(dev->usb_ctrl_dma,
+ dev->regs + S3C_UDC_OTG_DOEPDMA(EP0_CON));
+
+ ep_ctrl = readl(dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ __raw_writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_SNAK,
+ dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ }
+
+ } else {
+ if (ep_intr_status & TRANSFER_DONE)
+ complete_rx(dev, ep_num);
+ }
+ }
+ ep_num++;
+ ep_intr >>= 1;
+ }
+}
+
+/*
+ * usb client interrupt handler.
+ */
+static irqreturn_t s3c_udc_irq(int irq, void *_dev)
+{
+ struct s3c_udc *dev = _dev;
+ u32 intr_status;
+ u32 usb_status, gintmsk;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ intr_status = __raw_readl(dev->regs + S3C_UDC_OTG_GINTSTS);
+ gintmsk = __raw_readl(dev->regs + S3C_UDC_OTG_GINTMSK);
+
+ DEBUG_ISR("\n*** %s : GINTSTS=0x%x(on state %s), GINTMSK :"
+ "0x%x, DAINT : 0x%x, DAINTMSK : 0x%x\n",
+ __func__, intr_status,
+ state_names[dev->ep0state], gintmsk,
+ __raw_readl(dev->regs + S3C_UDC_OTG_DAINT), __raw_readl(dev->regs + S3C_UDC_OTG_DAINTMSK));
+
+ if (!intr_status) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return IRQ_HANDLED;
+ }
+
+ if (intr_status & INT_ENUMDONE) {
+ DEBUG_ISR("\tSpeed Detection interrupt\n");
+
+ __raw_writel(INT_ENUMDONE, dev->regs + S3C_UDC_OTG_GINTSTS);
+ usb_status = (__raw_readl(dev->regs + S3C_UDC_OTG_DSTS) & 0x6);
+
+ if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) {
+ DEBUG_ISR("\t\tFull Speed Detection\n");
+ set_max_pktsize(dev, USB_SPEED_FULL);
+
+ } else {
+ DEBUG_ISR("\t\tHigh Speed Detection : 0x%x\n",
+ usb_status);
+ set_max_pktsize(dev, USB_SPEED_HIGH);
+ }
+ wake_lock(&dev->usbd_wake_lock);
+ }
+
+ if (intr_status & INT_EARLY_SUSPEND) {
+ DEBUG_ISR("\tEarly suspend interrupt\n");
+ __raw_writel(INT_EARLY_SUSPEND, dev->regs + S3C_UDC_OTG_GINTSTS);
+ }
+
+ if (intr_status & INT_SUSPEND) {
+ usb_status = __raw_readl(dev->regs + S3C_UDC_OTG_DSTS);
+ DEBUG_ISR("\tSuspend interrupt :(DSTS):0x%x\n", usb_status);
+ __raw_writel(INT_SUSPEND, dev->regs + S3C_UDC_OTG_GINTSTS);
+
+ if (dev->gadget.speed != USB_SPEED_UNKNOWN
+ && dev->driver
+ && dev->driver->suspend) {
+
+ dev->driver->suspend(&dev->gadget);
+ }
+ }
+
+ if (intr_status & INT_RESUME) {
+ DEBUG_ISR("\tResume interrupt\n");
+ __raw_writel(INT_RESUME, dev->regs + S3C_UDC_OTG_GINTSTS);
+
+ if (dev->gadget.speed != USB_SPEED_UNKNOWN
+ && dev->driver
+ && dev->driver->resume) {
+ dev->driver->resume(&dev->gadget);
+ }
+ }
+
+ if (intr_status & INT_RESET) {
+ usb_status = __raw_readl(dev->regs + S3C_UDC_OTG_GOTGCTL);
+ DEBUG_ISR("\tReset interrupt - (GOTGCTL):0x%x\n", usb_status);
+ __raw_writel(INT_RESET, dev->regs + S3C_UDC_OTG_GINTSTS);
+
+#if defined(CONFIG_MACH_M0_CMCC)
+ pr_info("[YSJ][%s] intr_status=0x%x, "
+ "gintmsk=0x%x, "
+ "usb_status=0x%x\n",
+ __func__,
+ intr_status,
+ gintmsk,
+ usb_status);
+#endif
+
+ set_conf_done = 0;
+
+ if ((usb_status & 0xc0000) == (0x3 << 18)) {
+ if (reset_available) {
+ DEBUG_ISR("\t\tOTG core got reset (%d)!!\n",
+ reset_available);
+ reset_usbd();
+ dev->ep0state = WAIT_FOR_SETUP;
+ reset_available = 0;
+ s3c_udc_pre_setup(dev);
+ } else {
+ reset_available = 1;
+ }
+ } else {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ struct usb_composite_dev *cdev;
+#endif
+ reset_available = 1;
+ DEBUG_ISR("\t\tRESET handling skipped\n");
+ wake_lock_timeout(&dev->usbd_wake_lock, HZ * 5);
+ printk(KERN_DEBUG "usb: reset\n");
+ /* report disconnect; the driver is already quiesced */
+ if (dev->driver) {
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ cdev = get_gadget_data(&dev->gadget);
+ if (cdev != NULL) {
+ cdev->mute_switch = 0;
+ cdev->force_disconnect = 1;
+ }
+#endif
+ spin_unlock(&dev->lock);
+#if defined(CONFIG_MACH_M0_CMCC)
+ pr_info("[YSJ][%s] disconnect gadget",
+ __func__);
+#endif
+ dev->driver->disconnect(&dev->gadget);
+ spin_lock(&dev->lock);
+ }
+
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+ s3c_udc_cable_disconnect(dev);
+#endif
+ }
+ }
+
+ if (intr_status & INT_IN_EP)
+ {
+ process_ep_in_intr(dev);
+ }
+ if (intr_status & INT_OUT_EP)
+ {
+ process_ep_out_intr(dev);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+/** Queue one request
+ * Kickstart transfer if needed
+ */
+static int s3c_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ struct s3c_request *req;
+ struct s3c_ep *ep;
+ struct s3c_udc *dev;
+ unsigned long flags;
+ u32 ep_num, gintsts;
+
+ req = container_of(_req, struct s3c_request, req);
+ if (unlikely(!_req || !_req->complete ||
+ !_req->buf || !list_empty(&req->queue))) {
+
+ DEBUG("%s: bad params\n", __func__);
+ return -EINVAL;
+ }
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+
+ if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) {
+
+ DEBUG("%s: bad ep\n", __func__);
+ return -EINVAL;
+ }
+
+ ep_num = ep_index(ep);
+ dev = ep->dev;
+ if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) {
+
+ DEBUG("%s: bogus device state %p\n", __func__, dev->driver);
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ _req->status = -EINPROGRESS;
+ _req->actual = 0;
+
+ /* kickstart this i/o queue? */
+ DEBUG("\n*** %s: %s-%s req = %p, len = %d, buf = %p"
+ "Q empty = %d, stopped = %d\n",
+ __func__, _ep->name, ep_is_in(ep) ? "in" : "out",
+ _req, _req->length, _req->buf,
+ list_empty(&ep->queue), ep->stopped);
+
+ if (list_empty(&ep->queue) && !ep->stopped) {
+
+ if (ep_num == 0) {
+ /* EP0 */
+ list_add_tail(&req->queue, &ep->queue);
+ s3c_ep0_kick(dev, ep);
+ req = 0;
+
+ } else if (ep_is_in(ep)) {
+ gintsts = __raw_readl(dev->regs + S3C_UDC_OTG_GINTSTS);
+ DEBUG_IN_EP("%s: ep_is_in, S3C_UDC_OTG_GINTSTS=0x%x\n",
+ __func__, gintsts);
+
+ if (set_conf_done == 1) {
+ setdma_tx(ep, req);
+ } else {
+ done(ep, req, 0);
+ DEBUG("%s: Not yet Set_configureation,"
+ "ep_num = %d, req = %p\n",
+ __func__, ep_num, req);
+ req = 0;
+ }
+
+ } else {
+ gintsts = __raw_readl(dev->regs + S3C_UDC_OTG_GINTSTS);
+ DEBUG_OUT_EP("%s: ep_is_out,"
+ "S3C_UDC_OTG_GINTSTS=0x%x\n",
+ __func__, gintsts);
+
+ setdma_rx(ep, req);
+ }
+ }
+
+ /* pio or dma irq handler advances the queue. */
+ if (likely(req != 0))
+ list_add_tail(&req->queue, &ep->queue);
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return 0;
+}
+
+/****************************************************************/
+/* End Point 0 related functions */
+/****************************************************************/
+
+/* return: 0 = still running, 1 = completed, negative = errno */
+static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req)
+{
+ u32 max;
+ unsigned count;
+ int is_last;
+
+ max = ep_maxpacket(ep);
+
+ DEBUG_EP0("%s: max = %d\n", __func__, max);
+
+ count = setdma_tx(ep, req);
+
+ /* last packet is usually short (or a zlp) */
+ if (likely(count != max))
+ is_last = 1;
+ else {
+ if (likely(req->req.length != req->req.actual))
+ is_last = 0;
+ else
+ is_last = 1;
+ }
+
+ DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __func__,
+ ep->ep.name, count,
+ is_last ? "/L" : "", req->req.length - req->req.actual, req);
+
+ /* requests complete when all IN data is in the FIFO */
+ if (is_last) {
+ ep->dev->ep0state = WAIT_FOR_SETUP;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * udc_set_address - set the USB address for this device
+ * @address:
+ *
+ * Called from control endpoint function
+ * after it decodes a set address setup packet.
+ */
+static void udc_set_address(struct s3c_udc *dev, unsigned char address)
+{
+ u32 ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DCFG);
+ __raw_writel(address << 4 | ctrl, dev->regs + S3C_UDC_OTG_DCFG);
+
+ s3c_udc_ep0_zlp(dev);
+
+ DEBUG_EP0("%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n",
+ __func__, address, __raw_readl(dev->regs + S3C_UDC_OTG_DCFG));
+
+ dev->usb_address = address;
+}
+
+static inline void s3c_udc_ep0_set_stall(struct s3c_ep *ep)
+{
+ struct s3c_udc *dev;
+ u32 ep_ctrl = 0;
+
+ dev = ep->dev;
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ /* set the disable and stall bits */
+ if (ep_ctrl & DEPCTL_EPENA)
+ ep_ctrl |= DEPCTL_EPDIS;
+
+ ep_ctrl |= DEPCTL_STALL;
+
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ DEBUG_EP0("%s: set ep%d stall, DIEPCTL0 = 0x%x\n",
+ __func__, ep_index(ep), __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)));
+ /*
+ * The application can only set this bit, and the core clears it,
+ * when a SETUP token is received for this endpoint
+ */
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ s3c_udc_pre_setup(dev);
+}
+
+static void s3c_ep0_read(struct s3c_udc *dev)
+{
+ struct s3c_request *req;
+ struct s3c_ep *ep = &dev->ep[0];
+ int ret;
+
+ if (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+
+ } else {
+ DEBUG("%s: ---> BUG\n", __func__);
+ BUG();
+ return;
+ }
+
+ DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n",
+ __func__, req, req->req.length, req->req.actual);
+
+ if (req->req.length == 0) {
+ /* zlp for Set_configuration, Set_interface,
+ * or Bulk-Only mass storge reset */
+
+ dev->ep0state = WAIT_FOR_SETUP;
+ set_conf_done = 1;
+ s3c_udc_ep0_zlp(dev);
+
+ DEBUG_EP0("%s: req.length = 0, bRequest = %d\n",
+ __func__, dev->usb_ctrl->bRequest);
+ return;
+ }
+
+ ret = setdma_rx(ep, req);
+}
+
+/*
+ * DATA_STATE_XMIT
+ */
+static int s3c_ep0_write(struct s3c_udc *dev)
+{
+ struct s3c_request *req;
+ struct s3c_ep *ep = &dev->ep[0];
+ int ret;
+
+ if (list_empty(&ep->queue))
+ req = 0;
+ else
+ req = list_entry(ep->queue.next, struct s3c_request, queue);
+
+ if (!req) {
+ DEBUG_EP0("%s: NULL REQ\n", __func__);
+ return 0;
+ }
+
+ DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n",
+ __func__, req, req->req.length, req->req.actual);
+
+ ret = write_fifo_ep0(ep, req);
+
+ if (ret == 1) {
+ /* Last packet */
+ dev->ep0state = WAIT_FOR_SETUP;
+ DEBUG_EP0("%s: finished, waiting for status\n", __func__);
+
+ } else {
+ dev->ep0state = DATA_STATE_XMIT;
+ DEBUG_EP0("%s: not finished\n", __func__);
+ }
+
+ return 1;
+}
+
+u16 g_status __attribute__((aligned(8)));
+
+static int s3c_udc_get_status(struct s3c_udc *dev,
+ struct usb_ctrlrequest *crq)
+{
+ u8 ep_num = crq->wIndex & 0x7F;
+ u32 ep_ctrl;
+
+ DEBUG_SETUP("%s: *** USB_REQ_GET_STATUS\n", __func__);
+
+ switch (crq->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_INTERFACE:
+ g_status = 0;
+ DEBUG_SETUP("\tGET_STATUS: USB_RECIP_INTERFACE,"
+ "g_stauts = %d\n", g_status);
+ break;
+
+ case USB_RECIP_DEVICE:
+ g_status = 0x0;
+ DEBUG_SETUP("\tGET_STATUS: USB_RECIP_DEVICE,"
+ "g_stauts = %d\n", g_status);
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ if (crq->wLength > 2) {
+ DEBUG_SETUP("\tGET_STATUS:"
+ "Not support EP or wLength\n");
+ return 1;
+ }
+
+ g_status = dev->ep[ep_num].stopped;
+ DEBUG_SETUP("\tGET_STATUS: USB_RECIP_ENDPOINT,"
+ "g_stauts = %d\n", g_status);
+
+ break;
+ default:
+ return 1;
+ }
+
+ __dma_single_cpu_to_dev(&g_status, 2, DMA_TO_DEVICE);
+
+ __raw_writel(virt_to_phys(&g_status), dev->regs + S3C_UDC_OTG_DIEPDMA(EP0_CON));
+ __raw_writel((1<<19)|(2<<0), dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ __raw_writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ return 0;
+}
+
+static void s3c_udc_ep_set_stall(struct s3c_ep *ep)
+{
+ struct s3c_udc *dev = ep->dev;
+ u8 ep_num;
+ u32 ep_ctrl = 0;
+
+ ep_num = ep_index(ep);
+ DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type);
+
+ if (ep_is_in(ep)) {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+
+ /* set the disable and stall bits */
+ if (ep_ctrl & DEPCTL_EPENA)
+ ep_ctrl |= DEPCTL_EPDIS;
+
+ ep_ctrl |= DEPCTL_STALL;
+
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+ DEBUG("%s: set stall, DIEPCTL%d = 0x%x\n",
+ __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)));
+
+ } else {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+
+ /* set the stall bit */
+ ep_ctrl |= DEPCTL_STALL;
+
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+ DEBUG("%s: set stall, DOEPCTL%d = 0x%x\n",
+ __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)));
+ }
+
+ return;
+}
+
+void s3c_udc_ep_clear_stall(struct s3c_ep *ep)
+{
+ struct s3c_udc *dev = ep->dev;
+ u8 ep_num;
+ u32 ep_ctrl = 0;
+
+ ep_num = ep_index(ep);
+ DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type);
+
+ if (ep_is_in(ep)) {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+
+ /* clear stall bit */
+ ep_ctrl &= ~DEPCTL_STALL;
+
+ /*
+ * USB Spec 9.4.5: For endpoints using data toggle, regardless
+ * of whether an endpoint has the Halt feature set, a
+ * ClearFeature(ENDPOINT_HALT) request always results in the
+ * data toggle being reinitialized to DATA0.
+ */
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_INT
+ || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+ ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */
+ }
+
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+ DEBUG("%s: cleared stall, DIEPCTL%d = 0x%x\n",
+ __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)));
+
+ } else {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+
+ /* clear stall bit */
+ ep_ctrl &= ~DEPCTL_STALL;
+
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_INT
+ || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) {
+ ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */
+ }
+
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+ DEBUG("%s: cleared stall, DOEPCTL%d = 0x%x\n",
+ __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)));
+ }
+
+ return;
+}
+
+static int s3c_udc_set_halt(struct usb_ep *_ep, int value)
+{
+ struct s3c_ep *ep;
+ struct s3c_udc *dev;
+ unsigned long flags;
+ u8 ep_num;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ ep_num = ep_index(ep);
+
+ if (unlikely(!_ep || !ep->desc || ep_num == EP0_CON ||
+ ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC)) {
+ DEBUG("%s: %s bad ep or descriptor\n", __func__, ep->ep.name);
+ return -EINVAL;
+ }
+
+ /* Attempt to halt IN ep will fail if any transfer requests
+ * are still queue */
+ if (value && ep_is_in(ep) && !list_empty(&ep->queue)) {
+ DEBUG("%s: %s queue not empty, req = %p\n",
+ __func__, ep->ep.name,
+ list_entry(ep->queue.next, struct s3c_request, queue));
+
+ return -EAGAIN;
+ }
+
+ dev = ep->dev;
+ DEBUG("%s: ep_num = %d, value = %d\n", __func__, ep_num, value);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (value == 0) {
+ ep->stopped = 0;
+ s3c_udc_ep_clear_stall(ep);
+ } else {
+ ep->stopped = 1;
+ s3c_udc_ep_set_stall(ep);
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ return 0;
+}
+
+void s3c_udc_ep_activate(struct s3c_ep *ep)
+{
+ struct s3c_udc *dev = ep->dev;
+ u8 ep_num;
+ u32 ep_ctrl = 0, daintmsk = 0;
+
+ ep_num = ep_index(ep);
+
+ /* Read DEPCTLn register */
+ if (ep_is_in(ep)) {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+ daintmsk = 1 << ep_num;
+ } else {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+ daintmsk = (1 << ep_num) << DAINT_OUT_BIT;
+ }
+
+ DEBUG("%s: EPCTRL%d = 0x%x, ep_is_in = %d\n",
+ __func__, ep_num, ep_ctrl, ep_is_in(ep));
+
+ /* If the EP is already active don't change the EP Control
+ * register. */
+ if (!(ep_ctrl & DEPCTL_USBACTEP)) {
+ ep_ctrl = (ep_ctrl & ~DEPCTL_TYPE_MASK) |
+ (ep->bmAttributes << DEPCTL_TYPE_BIT);
+ ep_ctrl = (ep_ctrl & ~DEPCTL_MPS_MASK) |
+ (ep->ep.maxpacket << DEPCTL_MPS_BIT);
+ ep_ctrl |= (DEPCTL_SETD0PID | DEPCTL_USBACTEP | DEPCTL_SNAK);
+
+ if (ep_is_in(ep)) {
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num));
+ DEBUG("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n",
+ __func__, ep_num, ep_num,
+ __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)));
+ } else {
+ __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num));
+ DEBUG("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n",
+ __func__, ep_num, ep_num,
+ __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)));
+ }
+ }
+
+ /* Unmask EP Interrtupt */
+ __raw_writel(__raw_readl(dev->regs + S3C_UDC_OTG_DAINTMSK)|daintmsk, dev->regs + S3C_UDC_OTG_DAINTMSK);
+ DEBUG("%s: DAINTMSK = 0x%x\n", __func__, __raw_readl(dev->regs + S3C_UDC_OTG_DAINTMSK));
+}
+
+static int s3c_udc_clear_feature(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ u8 ep_num;
+ struct usb_ctrlrequest *usb_ctrl;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ ep_num = ep_index(ep);
+ usb_ctrl = ep->dev->usb_ctrl;
+
+ DEBUG_SETUP("%s: ep_num = %d, is_in = %d, clear_feature_flag = %d\n",
+ __func__, ep_num, ep_is_in(ep), clear_feature_flag);
+
+ if (usb_ctrl->wLength != 0) {
+ DEBUG_SETUP("\tCLEAR_FEATURE:"
+ "wLength is not zero.....\n");
+ return 1;
+ }
+
+ switch (usb_ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (usb_ctrl->wValue) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ DEBUG_SETUP("\tCLEAR_FEATURE:"
+ "USB_DEVICE_REMOTE_WAKEUP\n");
+ break;
+
+ case USB_DEVICE_TEST_MODE:
+ DEBUG_SETUP("\tCLEAR_FEATURE:"
+ "USB_DEVICE_TEST_MODE\n");
+ /** @todo Add CLEAR_FEATURE for TEST modes. */
+ break;
+ default:
+ s3c_udc_ep0_set_stall(ep);
+ return 1;
+ }
+
+ s3c_udc_ep0_zlp(ep->dev);
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ DEBUG_SETUP("\tCLEAR_FEATURE: USB_RECIP_ENDPOINT,"
+ "wValue = %d\n", usb_ctrl->wValue);
+
+ if (usb_ctrl->wValue == USB_ENDPOINT_HALT) {
+ if (ep_num == 0) {
+ s3c_udc_ep0_set_stall(ep);
+ return 0;
+ }
+
+ s3c_udc_ep0_zlp(ep->dev);
+
+ s3c_udc_ep_clear_stall(ep);
+ s3c_udc_ep_activate(ep);
+ ep->stopped = 0;
+
+ clear_feature_num = ep_num;
+ clear_feature_flag = 1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* Set into the test mode for Test Mode set_feature request */
+static inline void set_test_mode(struct s3c_udc *dev)
+{
+ u32 ep_ctrl, dctl;
+ u8 test_selector = (dev->usb_ctrl->wIndex>>8) & TEST_SELECTOR_MASK;
+
+ if (test_selector > 0 && test_selector < 6) {
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ __raw_writel(1<<19 | 0<<0, dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+ __raw_writel(ep_ctrl | DEPCTL_EPENA | DEPCTL_CNAK
+ | EP0_CON<<DEPCTL_NEXT_EP_BIT,
+ dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ }
+
+ switch (test_selector) {
+ case TEST_J_SEL:
+ /* some delay is necessary like printk() or udelay() */
+ printk(KERN_INFO "Test mode selector in set_feature request is"
+ "TEST J\n");
+
+ dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_J_MODE,
+ dev->regs + S3C_UDC_OTG_DCTL);
+ break;
+ case TEST_K_SEL:
+ /* some delay is necessary like printk() or udelay() */
+ printk(KERN_INFO "Test mode selector in set_feature request is"
+ "TEST K\n");
+
+ dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ __raw_writel((dctl&~(TEST_CONTROL_MASK))|TEST_K_MODE,
+ dev->regs + S3C_UDC_OTG_DCTL);
+ break;
+ case TEST_SE0_NAK_SEL:
+ /* some delay is necessary like printk() or udelay() */
+ printk(KERN_INFO "Test mode selector in set_feature request is"
+ "TEST SE0 NAK\n");
+
+ dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_SE0_NAK_MODE,
+ dev->regs + S3C_UDC_OTG_DCTL);
+ break;
+ case TEST_PACKET_SEL:
+ /* some delay is necessary like printk() or udelay() */
+ printk(KERN_INFO "Test mode selector in set_feature request is"
+ "TEST PACKET\n");
+
+ __dma_single_cpu_to_dev(test_pkt, TEST_PKT_SIZE, DMA_TO_DEVICE);
+ __raw_writel(virt_to_phys(test_pkt), dev->regs + S3C_UDC_OTG_DIEPDMA(EP0_CON));
+
+ ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ __raw_writel(1<<19 | TEST_PKT_SIZE<<0, dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+ __raw_writel(ep_ctrl | DEPCTL_EPENA | DEPCTL_CNAK
+ | EP0_CON<<DEPCTL_NEXT_EP_BIT,
+ dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_PACKET_MODE,
+ dev->regs + S3C_UDC_OTG_DCTL);
+ break;
+ case TEST_FORCE_ENABLE_SEL:
+ /* some delay is necessary like printk() or udelay() */
+ printk(KERN_INFO "Test mode selector in set_feature request is"
+ "TEST FORCE ENABLE\n");
+
+ dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL);
+ __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_FORCE_ENABLE_MODE,
+ dev->regs + S3C_UDC_OTG_DCTL);
+ break;
+ }
+}
+
+static int s3c_udc_set_feature(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ u8 ep_num;
+ struct usb_ctrlrequest *usb_ctrl;
+
+ ep = container_of(_ep, struct s3c_ep, ep);
+ ep_num = ep_index(ep);
+ usb_ctrl = ep->dev->usb_ctrl;
+
+ DEBUG_SETUP("%s: *** USB_REQ_SET_FEATURE,"
+ "ep_num = %d\n", __func__, ep_num);
+
+ if (usb_ctrl->wLength != 0) {
+ DEBUG_SETUP("\tSET_FEATURE: wLength is not zero.....\n");
+ return 1;
+ }
+
+ switch (usb_ctrl->bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (usb_ctrl->wValue) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ DEBUG_SETUP("\tSET_FEATURE:"
+ "USB_DEVICE_REMOTE_WAKEUP\n");
+ break;
+
+ case USB_DEVICE_TEST_MODE:
+ DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_TEST_MODE\n");
+ set_test_mode(ep->dev);
+ break;
+
+ case USB_DEVICE_B_HNP_ENABLE:
+ DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n");
+ break;
+
+ case USB_DEVICE_A_HNP_SUPPORT:
+ /* RH port supports HNP */
+ DEBUG_SETUP("\tSET_FEATURE:"
+ "USB_DEVICE_A_HNP_SUPPORT\n");
+ break;
+
+ case USB_DEVICE_A_ALT_HNP_SUPPORT:
+ /* other RH port does */
+ DEBUG_SETUP("\tSET_FEATURE:"
+ "USB_DEVICE_A_ALT_HNP_SUPPORT\n");
+ break;
+ default:
+ s3c_udc_ep0_set_stall(ep);
+ return 1;
+ }
+
+ s3c_udc_ep0_zlp(ep->dev);
+ return 0;
+
+ case USB_RECIP_INTERFACE:
+ DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_INTERFACE\n");
+ break;
+
+ case USB_RECIP_ENDPOINT:
+ DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_ENDPOINT\n");
+ if (usb_ctrl->wValue == USB_ENDPOINT_HALT) {
+ if (ep_num == 0) {
+ s3c_udc_ep0_set_stall(ep);
+ return 0;
+ }
+ ep->stopped = 1;
+ s3c_udc_ep_set_stall(ep);
+ }
+
+ s3c_udc_ep0_zlp(ep->dev);
+ return 0;
+ }
+
+ return 1;
+}
+
+/*
+ * WAIT_FOR_SETUP (OUT_PKT_RDY)
+ */
+static void s3c_ep0_setup(struct s3c_udc *dev)
+{
+ struct s3c_ep *ep = &dev->ep[0];
+ int i, is_in;
+ u8 ep_num;
+ struct usb_ctrlrequest *usb_ctrl = dev->usb_ctrl;
+
+ /* Nuke all previous transfers */
+ nuke(ep, -EPROTO);
+
+ DEBUG_SETUP("%s: bRequestType = 0x%x(%s), bRequest = 0x%x"
+ "\twLength = 0x%x, wValue = 0x%x, wIndex= 0x%x\n",
+ __func__, usb_ctrl->bRequestType,
+ (usb_ctrl->bRequestType & USB_DIR_IN) ? "IN" : "OUT",
+ usb_ctrl->bRequest, usb_ctrl->wLength, usb_ctrl->wValue,
+ usb_ctrl->wIndex);
+
+ if (usb_ctrl->bRequest == GET_MAX_LUN_REQUEST && usb_ctrl->wLength != 1) {
+ DEBUG_SETUP("\t%s:GET_MAX_LUN_REQUEST:invalid wLength = %d,"
+ "setup returned\n", __func__, usb_ctrl->wLength);
+
+ s3c_udc_ep0_set_stall(ep);
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ return;
+ } else if (usb_ctrl->bRequest ==
+ BOT_RESET_REQUEST && usb_ctrl->wLength != 0) {
+ /* Bulk-Only *mass storge reset of class-specific request */
+ DEBUG_SETUP("\t%s:BOT Rest:invalid wLength = %d,"
+ "setup returned\n",
+ __func__, usb_ctrl->wLength);
+
+ s3c_udc_ep0_set_stall(ep);
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ return;
+ }
+
+ /* Set direction of EP0 */
+ if (likely(usb_ctrl->bRequestType & USB_DIR_IN)) {
+ ep->bEndpointAddress |= USB_DIR_IN;
+ is_in = 1;
+
+ } else {
+ ep->bEndpointAddress &= ~USB_DIR_IN;
+ is_in = 0;
+ }
+ /* cope with automagic for some standard requests. */
+ dev->req_std = (usb_ctrl->bRequestType & USB_TYPE_MASK)
+ == USB_TYPE_STANDARD;
+ dev->req_config = 0;
+ dev->req_pending = 1;
+
+ /* Handle some SETUP packets ourselves */
+ switch (usb_ctrl->bRequest) {
+ case USB_REQ_SET_ADDRESS:
+ DEBUG_SETUP("%s: *** USB_REQ_SET_ADDRESS (%d)\n",
+ __func__, usb_ctrl->wValue);
+
+ if (usb_ctrl->bRequestType
+ != (USB_TYPE_STANDARD | USB_RECIP_DEVICE))
+ break;
+
+ udc_set_address(dev, usb_ctrl->wValue);
+ return;
+
+ case USB_REQ_SET_CONFIGURATION:
+ DEBUG_SETUP("============================================\n");
+ DEBUG_SETUP("%s: USB_REQ_SET_CONFIGURATION (%d)\n",
+ __func__, usb_ctrl->wValue);
+
+ if (usb_ctrl->bRequestType == USB_RECIP_DEVICE) {
+ reset_available = 1;
+ dev->req_config = 1;
+ }
+#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS)
+ s3c_udc_cable_connect(dev);
+#endif
+ break;
+
+ case USB_REQ_GET_DESCRIPTOR:
+ DEBUG_SETUP("%s: *** USB_REQ_GET_DESCRIPTOR\n", __func__);
+ break;
+
+ case USB_REQ_SET_INTERFACE:
+ DEBUG_SETUP("%s: *** USB_REQ_SET_INTERFACE (%d)\n",
+ __func__, usb_ctrl->wValue);
+
+ if (usb_ctrl->bRequestType == USB_RECIP_INTERFACE) {
+ reset_available = 1;
+ dev->req_config = 1;
+ }
+ break;
+
+ case USB_REQ_GET_CONFIGURATION:
+ DEBUG_SETUP("%s: *** USB_REQ_GET_CONFIGURATION\n", __func__);
+ break;
+
+ case USB_REQ_GET_STATUS:
+ if (dev->req_std) {
+ if (!s3c_udc_get_status(dev, usb_ctrl))
+ return;
+ }
+ break;
+
+ case USB_REQ_CLEAR_FEATURE:
+ ep_num = usb_ctrl->wIndex & 0x7f;
+
+ if (!s3c_udc_clear_feature(&dev->ep[ep_num].ep))
+ return;
+ break;
+ case USB_REQ_SET_FEATURE:
+ ep_num = usb_ctrl->wIndex & 0x7f;
+
+ if (!s3c_udc_set_feature(&dev->ep[ep_num].ep))
+ return;
+ break;
+ default:
+ DEBUG_SETUP("%s: *** Default of usb_ctrl->bRequest=0x%x"
+ "happened.\n", __func__, usb_ctrl->bRequest);
+ break;
+ }
+
+ if (likely(dev->driver)) {
+ /* device-2-host (IN) or no data setup command,
+ * process immediately */
+ DEBUG_SETUP("%s: usb_ctrlrequest will be passed to"
+ "fsg_setup()\n", __func__);
+
+ spin_unlock(&dev->lock);
+ i = dev->driver->setup(&dev->gadget, usb_ctrl);
+ spin_lock(&dev->lock);
+
+ if (i < 0) {
+ if (dev->req_config) {
+ DEBUG_SETUP("\tconfig change 0x%02x fail %d?\n",
+ (u32)usb_ctrl->bRequest, i);
+ }
+
+ /* setup processing failed, force stall */
+ s3c_udc_ep0_set_stall(ep);
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ DEBUG_SETUP("\tdev->driver->setup failed (%d),"
+ "bRequest = %d\n",
+ i, usb_ctrl->bRequest);
+ } else if (dev->req_pending) {
+ dev->req_pending = 0;
+ DEBUG_SETUP("\tdev->req_pending...\n");
+ }
+
+ DEBUG_SETUP("\tep0state = %s\n", state_names[dev->ep0state]);
+ }
+}
+
+/*
+ * handle ep0 interrupt
+ */
+static void s3c_handle_ep0(struct s3c_udc *dev)
+{
+ if (dev->ep0state == WAIT_FOR_SETUP) {
+ DEBUG_OUT_EP("%s: WAIT_FOR_SETUP\n", __func__);
+ s3c_ep0_setup(dev);
+
+ } else {
+ DEBUG_OUT_EP("%s: strange state!!(state = %s)\n",
+ __func__, state_names[dev->ep0state]);
+ }
+}
+
+static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep)
+{
+ DEBUG_EP0("%s: ep_is_in = %d\n", __func__, ep_is_in(ep));
+ if (ep_is_in(ep)) {
+ dev->ep0state = DATA_STATE_XMIT;
+ s3c_ep0_write(dev);
+
+ } else {
+ dev->ep0state = DATA_STATE_RECV;
+ s3c_ep0_read(dev);
+ }
+}
diff --git a/drivers/usb/gadget/serial_acm.c b/drivers/usb/gadget/serial_acm.c
new file mode 100644
index 0000000..aa4a38d
--- /dev/null
+++ b/drivers/usb/gadget/serial_acm.c
@@ -0,0 +1,171 @@
+/*
+ * serial_acm.c -- USB modem serial driver
+ *
+ * Copyright 2008 (C) Samsung Electronics
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/usb/cdc.h>
+
+static int acm_notify(void *dev, u16 state);
+
+
+static wait_queue_head_t modem_wait_q;
+
+static unsigned int read_state;
+static unsigned int control_line_state;
+
+static void *acm_data;
+
+static int modem_open(struct inode *inode, struct file *file)
+{
+ read_state = 0;
+
+ return 0;
+}
+
+static int modem_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static ssize_t modem_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ret = 0;
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ ret = wait_event_interruptible(modem_wait_q, read_state);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(buf, &control_line_state, sizeof(u32)))
+ return -EFAULT;
+
+ read_state = 0;
+
+ return sizeof(u32);
+}
+
+static unsigned int modem_poll(struct file *file, poll_table *wait)
+{
+ int ret;
+ poll_wait(file, &modem_wait_q, wait);
+
+ ret = (read_state ? (POLLIN | POLLRDNORM) : 0);
+
+ return ret;
+}
+
+void notify_control_line_state(u32 value)
+{
+ control_line_state = value;
+
+ read_state = 1;
+
+ wake_up_interruptible(&modem_wait_q);
+}
+EXPORT_SYMBOL(notify_control_line_state);
+
+
+#define GS_CDC_NOTIFY_SERIAL_STATE _IOW('S', 1, int)
+#define GS_IOC_NOTIFY_DTR_TEST _IOW('S', 3, int)
+
+static long
+modem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ printk(KERN_INFO "modem_ioctl: cmd=0x%x, arg=%lu\n", cmd, arg);
+
+ /* handle ioctls */
+ switch (cmd) {
+ case GS_CDC_NOTIFY_SERIAL_STATE:
+ ret = acm_notify(acm_data, __constant_cpu_to_le16(arg));
+ return ret;
+
+ case GS_IOC_NOTIFY_DTR_TEST:
+ {
+ printk(KERN_ALERT"DUN : DTR %d\n", (int)arg);
+ notify_control_line_state((int)arg);
+ break;
+ }
+
+ default:
+ printk(KERN_INFO "modem_ioctl: Unknown ioctl cmd(0x%x).\n",
+ cmd);
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+
+static const struct file_operations modem_fops = {
+ .owner = THIS_MODULE,
+ .open = modem_open,
+ .release = modem_close,
+ .read = modem_read,
+ .poll = modem_poll,
+ .llseek = no_llseek,
+ .unlocked_ioctl = modem_ioctl,
+};
+
+static struct miscdevice modem_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "dun",
+ .fops = &modem_fops,
+};
+
+int modem_register(void *data)
+{
+ if (data == NULL) {
+ printk(KERN_INFO "DUN register failed. data is null.\n");
+ return -1;
+ }
+
+ acm_data = data;
+
+ init_waitqueue_head(&modem_wait_q);
+
+ printk(KERN_INFO "DUN is registerd\n");
+
+ return 0;
+}
+EXPORT_SYMBOL(modem_register);
+
+static int modem_misc_register(void)
+{
+ int ret;
+ ret = misc_register(&modem_device);
+ if (ret) {
+ printk(KERN_ERR "DUN register is failed, ret = %d\n", ret);
+ return ret;
+ }
+ return ret;
+}
+
+void modem_unregister(void)
+{
+ acm_data = NULL;
+
+ printk(KERN_INFO "DUN is unregisterd\n");
+}
+EXPORT_SYMBOL(modem_unregister);
diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c
index 1fa4f70..4dd598c 100644
--- a/drivers/usb/gadget/storage_common.c
+++ b/drivers/usb/gadget/storage_common.c
@@ -260,6 +260,7 @@ static struct fsg_lun *fsg_lun_from_dev(struct device *dev)
/* Big enough to hold our biggest descriptor */
#define EP0_BUFSIZE 256
+#define EP0_BUFSIZE_SS 512
#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */
/* Number of buffers we will use. 2 is enough for double-buffering */
@@ -429,6 +430,118 @@ static struct usb_descriptor_header *fsg_fs_function[] = {
NULL,
};
+static struct usb_endpoint_descriptor
+fsg_ss_bulk_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc = {
+ .bLength = sizeof(fsg_ss_bulk_in_comp_desc),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /*.bMaxBurst = DYNAMIC, */
+};
+
+static struct usb_endpoint_descriptor
+fsg_ss_bulk_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(1024),
+};
+
+static struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc = {
+ .bLength = sizeof(fsg_ss_bulk_in_comp_desc),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ /*.bMaxBurst = DYNAMIC, */
+};
+
+#ifndef FSG_NO_INTR_EP
+
+static struct usb_endpoint_descriptor
+fsg_ss_intr_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+
+ /* bEndpointAddress copied from fs_intr_in_desc during fsg_bind() */
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(2),
+ .bInterval = 9, /* 2**(9-1) = 256 uframes -> 32 ms */
+};
+
+static struct usb_ss_ep_comp_descriptor fsg_ss_intr_in_comp_desc = {
+ .bLength = sizeof(fsg_ss_bulk_in_comp_desc),
+ .bDescriptorType = USB_DT_SS_ENDPOINT_COMP,
+
+ .wBytesPerInterval = cpu_to_le16(1024),
+};
+
+#ifndef FSG_NO_OTG
+# define FSG_SS_FUNCTION_PRE_EP_ENTRIES 4
+#else
+# define FSG_SS_FUNCTION_PRE_EP_ENTRIES 3
+#endif
+
+#endif
+
+static __maybe_unused struct usb_ext_cap_descriptor fsg_ext_cap_desc = {
+ .bLength = USB_DT_USB_EXT_CAP_SIZE,
+ .bDescriptorType = USB_DT_DEVICE_CAPABILITY,
+ .bDevCapabilityType = USB_CAP_TYPE_EXT,
+
+ .bmAttributes = cpu_to_le32(USB_LPM_SUPPORT),
+};
+
+static __maybe_unused struct usb_ss_cap_descriptor fsg_ss_cap_desc = {
+ .bLength = USB_DT_USB_SS_CAP_SIZE,
+ .bDescriptorType = USB_DT_DEVICE_CAPABILITY,
+ .bDevCapabilityType = USB_SS_CAP_TYPE,
+
+ /* .bmAttributes = LTM is not supported yet */
+
+ .wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION
+ | USB_FULL_SPEED_OPERATION
+ | USB_HIGH_SPEED_OPERATION
+ | USB_5GBPS_OPERATION),
+ .bFunctionalitySupport = USB_LOW_SPEED_OPERATION,
+ .bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT,
+ .bU2DevExitLat = USB_DEFAULT_U2_DEV_EXIT_LAT,
+};
+
+static __maybe_unused struct usb_bos_descriptor fsg_bos_desc = {
+ .bLength = USB_DT_BOS_SIZE,
+ .bDescriptorType = USB_DT_BOS,
+
+ .wTotalLength = USB_DT_BOS_SIZE
+ + USB_DT_USB_EXT_CAP_SIZE
+ + USB_DT_USB_SS_CAP_SIZE,
+
+ .bNumDeviceCaps = 2,
+};
+
+static struct usb_descriptor_header *fsg_ss_function[] = {
+#ifndef FSG_NO_OTG
+ (struct usb_descriptor_header *) &fsg_otg_desc,
+#endif
+ (struct usb_descriptor_header *) &fsg_intf_desc,
+ (struct usb_descriptor_header *) &fsg_ss_bulk_in_desc,
+ (struct usb_descriptor_header *) &fsg_ss_bulk_in_comp_desc,
+ (struct usb_descriptor_header *) &fsg_ss_bulk_out_desc,
+ (struct usb_descriptor_header *) &fsg_ss_bulk_out_comp_desc,
+#ifndef FSG_NO_INTR_EP
+ (struct usb_descriptor_header *) &fsg_ss_intr_in_desc,
+ (struct usb_descriptor_header *) &fsg_ss_intr_in_comp_desc,
+#endif
+ NULL,
+};
/*
* USB 2.0 devices need to expose both high speed and full speed
@@ -763,10 +876,16 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr,
struct rw_semaphore *filesem = dev_get_drvdata(dev);
int rc = 0;
+
+#if !defined(CONFIG_USB_ANDROID_MASS_STORAGE) && !defined(CONFIG_USB_G_ANDROID)
+ /* disabled in android because we need to allow closing the backing file
+ * if the media was removed
+ */
if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) {
LDBG(curlun, "eject attempt prevented\n");
return -EBUSY; /* "Door is locked" */
}
+#endif
/* Remove a trailing newline */
if (count > 0 && buf[count-1] == '\n')
diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c
index 2ac1d21..b0a56ee 100644
--- a/drivers/usb/gadget/u_ether.c
+++ b/drivers/usb/gadget/u_ether.c
@@ -97,16 +97,17 @@ struct eth_dev {
static unsigned qmult = 5;
module_param(qmult, uint, S_IRUGO|S_IWUSR);
-MODULE_PARM_DESC(qmult, "queue length multiplier at high speed");
+MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed");
#else /* full speed (low speed doesn't do bulk) */
#define qmult 1
#endif
-/* for dual-speed hardware, use deeper queues at highspeed */
+/* for dual-speed hardware, use deeper queues at high/super speed */
static inline int qlen(struct usb_gadget *gadget)
{
- if (gadget_is_dualspeed(gadget) && gadget->speed == USB_SPEED_HIGH)
+ if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH ||
+ gadget->speed == USB_SPEED_SUPER))
return qmult * DEFAULT_QLEN;
else
return DEFAULT_QLEN;
@@ -242,8 +243,11 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
if (dev->port_usb->is_fixed)
size = max_t(size_t, size, dev->port_usb->fixed_out_len);
-
+#ifdef CONFIG_USB_S3C_OTGD
+ skb = alloc_skb(size + NET_IP_ALIGN + 6, gfp_flags);
+#else
skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags);
+#endif
if (skb == NULL) {
DBG(dev, "no rx skb\n");
goto enomem;
@@ -253,8 +257,11 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
* but on at least one, checksumming fails otherwise. Note:
* RNDIS headers involve variable numbers of LE32 values.
*/
+#ifdef CONFIG_USB_S3C_OTGD
+ skb_reserve(skb, NET_IP_ALIGN + 6);
+#else
skb_reserve(skb, NET_IP_ALIGN);
-
+#endif
req->buf = skb->data;
req->length = size;
req->complete = rx_complete;
@@ -482,6 +489,11 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req)
spin_lock(&dev->req_lock);
list_add(&req->list, &dev->tx_reqs);
spin_unlock(&dev->req_lock);
+
+#ifdef CONFIG_USB_S3C_OTGD
+ if (req->buf != skb->data)
+ kfree(req->buf);
+#endif
dev_kfree_skb_any(skb);
atomic_dec(&dev->tx_qlen);
@@ -577,7 +589,19 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
length = skb->len;
}
+#ifdef CONFIG_USB_S3C_OTGD
+ /* for double word align */
+ req->buf = kmalloc(skb->len, GFP_ATOMIC | GFP_DMA);
+
+ if (!req->buf) {
+ req->buf = skb->data;
+ ERROR(dev, "fail to kmalloc[req->buf = skb->data]\n");
+ } else {
+ memcpy((void *)req->buf, (void *)skb->data, skb->len);
+ }
+#else
req->buf = skb->data;
+#endif
req->context = skb;
req->complete = tx_complete;
@@ -598,9 +622,10 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
req->length = length;
- /* throttle highspeed IRQ rate back slightly */
+ /* throttle high/super speed IRQ rate back slightly */
if (gadget_is_dualspeed(dev->gadget))
- req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH)
+ req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH ||
+ dev->gadget->speed == USB_SPEED_SUPER)
? ((atomic_read(&dev->tx_qlen) % qmult) != 0)
: 0;
@@ -615,9 +640,14 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
}
if (retval) {
+#ifdef CONFIG_USB_S3C_OTGD
+ if (req->buf != skb->data)
+ kfree(req->buf);
+#endif
dev_kfree_skb_any(skb);
drop:
dev->net->stats.tx_dropped++;
+
spin_lock_irqsave(&dev->req_lock, flags);
if (list_empty(&dev->tx_reqs))
netif_start_queue(net);
@@ -631,8 +661,7 @@ drop:
static void eth_start(struct eth_dev *dev, gfp_t gfp_flags)
{
- DBG(dev, "%s\n", __func__);
-
+ printk(KERN_DEBUG "usb: %s ++\n", __func__);
/* fill the rx queue */
rx_fill(dev, gfp_flags);
@@ -646,7 +675,7 @@ static int eth_open(struct net_device *net)
struct eth_dev *dev = netdev_priv(net);
struct gether *link;
- DBG(dev, "%s\n", __func__);
+ printk(KERN_DEBUG "usb: %s ++\n", __func__);
if (netif_carrier_ok(dev->net))
eth_start(dev, GFP_KERNEL);
@@ -765,6 +794,26 @@ static struct device_type gadget_type = {
*/
int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN])
{
+ return gether_setup_name(g, ethaddr, "usb");
+}
+
+/**
+ * gether_setup_name - initialize one ethernet-over-usb link
+ * @g: gadget to associated with these links
+ * @ethaddr: NULL, or a buffer in which the ethernet address of the
+ * host side of the link is recorded
+ * @netname: name for network device (for example, "usb")
+ * Context: may sleep
+ *
+ * This sets up the single network link that may be exported by a
+ * gadget driver using this framework. The link layer addresses are
+ * set up using module parameters.
+ *
+ * Returns negative errno, or zero on success
+ */
+int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+ const char *netname)
+{
struct eth_dev *dev;
struct net_device *net;
int status;
@@ -787,17 +836,23 @@ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN])
/* network device setup */
dev->net = net;
- strcpy(net->name, "usb%d");
+ snprintf(net->name, sizeof(net->name), "%s%%d", netname);
if (get_ether_addr(dev_addr, net->dev_addr))
dev_warn(&g->dev,
"using random %s ethernet address\n", "self");
+
+#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE
+ memcpy(dev->host_mac, ethaddr, ETH_ALEN);
+ printk(KERN_DEBUG "usb: set unique host mac\n");
+#else
if (get_ether_addr(host_addr, dev->host_mac))
dev_warn(&g->dev,
"using random %s ethernet address\n", "host");
if (ethaddr)
memcpy(ethaddr, dev->host_mac, ETH_ALEN);
+#endif
net->netdev_ops = &eth_netdev_ops;
@@ -867,6 +922,7 @@ struct net_device *gether_connect(struct gether *link)
struct eth_dev *dev = the_dev;
int result = 0;
+ printk(KERN_DEBUG "usb: %s ++\n", __func__);
if (!dev)
return ERR_PTR(-EINVAL);
@@ -886,12 +942,15 @@ struct net_device *gether_connect(struct gether *link)
goto fail1;
}
+ printk(KERN_DEBUG "usb: %s enable ep in/out\n", __func__);
if (result == 0)
result = alloc_requests(dev, link, qlen(dev->gadget));
if (result == 0) {
dev->zlp = link->is_zlp_ok;
DBG(dev, "qlen %d\n", qlen(dev->gadget));
+ printk(KERN_DEBUG "usb: %s qlen=%d\n",
+ __func__, qlen(dev->gadget));
dev->header_len = link->header_len;
dev->unwrap = link->unwrap;
@@ -909,9 +968,12 @@ struct net_device *gether_connect(struct gether *link)
}
spin_unlock(&dev->lock);
+ printk(KERN_DEBUG "usb: %s netif_carrier_on\n", __func__);
netif_carrier_on(dev->net);
- if (netif_running(dev->net))
+ if (netif_running(dev->net)) {
+ printk(KERN_DEBUG "usb: %s eth_start\n", __func__);
eth_start(dev, GFP_ATOMIC);
+ }
/* on error, disable any endpoints */
} else {
@@ -943,7 +1005,6 @@ void gether_disconnect(struct gether *link)
struct eth_dev *dev = link->ioport;
struct usb_request *req;
- WARN_ON(!dev);
if (!dev)
return;
diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h
index b56e1e7..64b65f9 100644
--- a/drivers/usb/gadget/u_ether.h
+++ b/drivers/usb/gadget/u_ether.h
@@ -86,6 +86,9 @@ struct gether {
/* netdev setup/teardown as directed by the gadget driver */
int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]);
void gether_cleanup(void);
+/* variant of gether_setup that allows customizing network device name */
+int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN],
+ const char *netname);
/* connect/disconnect is handled by individual functions */
struct net_device *gether_connect(struct gether *);
@@ -112,12 +115,14 @@ int eem_bind_config(struct usb_configuration *c);
#ifdef USB_ETH_RNDIS
-int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]);
+int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+ u32 vendorID, const char *manufacturer);
#else
static inline int
-rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
+rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN],
+ u32 vendorID, const char *manufacturer)
{
return 0;
}
diff --git a/drivers/usb/gadget/u_ncm.c b/drivers/usb/gadget/u_ncm.c
new file mode 100644
index 0000000..33e9f50
--- /dev/null
+++ b/drivers/usb/gadget/u_ncm.c
@@ -0,0 +1,229 @@
+/*
+ * File Name : u_ncm.c
+ *
+ * ncm utilities for composite USB gadgets.
+ * This utilitie can support to connect head unit for mirror link
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Author: SoonYong, Cho <soonyong.cho@samsung.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 "f_ncm.c"
+
+/* Support dynamic tethering mode.
+ * if ncm_connect is true, device is received vendor specific request
+ * from head unit.
+ */
+static bool ncm_connect;
+
+/* terminal version using vendor specific request */
+u16 terminal_mode_version;
+u16 terminal_mode_vendor_id;
+
+struct ncm_function_config {
+ u8 ethaddr[ETH_ALEN];
+};
+
+static int ncm_function_init(struct android_usb_function *f,
+ struct usb_composite_dev *cdev)
+{
+ f->config = kzalloc(sizeof(struct ncm_function_config), GFP_KERNEL);
+ return 0;
+}
+
+static void ncm_function_cleanup(struct android_usb_function *f)
+{
+ kfree(f->config);
+ f->config = NULL;
+}
+
+static int ncm_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ int ret;
+ int i;
+ char *src;
+ struct ncm_function_config *ncm = f->config;
+
+ if (!ncm) {
+ pr_err("%s: ncm_pdata\n", __func__);
+ return -1;
+ }
+
+ ncm = f->config;
+ if (!f->config)
+ return -ENOMEM;
+
+ for (i = 0; i < ETH_ALEN; i++)
+ ncm->ethaddr[i] = 0;
+ /* create a fake MAC address from our serial number.
+ * first byte is 0x02 to signify locally administered.
+ */
+ ncm->ethaddr[0] = 0x02;
+ src = serial_string;
+ for (i = 0; (i < 256) && *src; i++) {
+ /* XOR the USB serial across the remaining bytes */
+ ncm->ethaddr[i % (ETH_ALEN - 1) + 1] ^= *src++;
+ }
+
+ printk(KERN_DEBUG "usb: %s MAC:%02X:%02X:%02X:%02X:%02X:%02X\n",
+ __func__, ncm->ethaddr[0], ncm->ethaddr[1],
+ ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4],
+ ncm->ethaddr[5]);
+
+
+ printk(KERN_DEBUG "usb: %s before MAC:%02X:%02X:%02X:%02X:%02X:%02X\n",
+ __func__, ncm->ethaddr[0], ncm->ethaddr[1],
+ ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4],
+ ncm->ethaddr[5]);
+ /* we have to use trick.
+ * rndis name will be used for ethernet interface name.
+ */
+ ret = gether_setup_name(c->cdev->gadget, ncm->ethaddr, "rndis");
+ printk(KERN_DEBUG "usb: %s after MAC:%02X:%02X:%02X:%02X:%02X:%02X\n",
+ __func__, ncm->ethaddr[0], ncm->ethaddr[1],
+ ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4],
+ ncm->ethaddr[5]);
+ if (ret) {
+ pr_err("%s: gether_setup failed\n", __func__);
+ return ret;
+ }
+
+ return ncm_bind_config(c, ncm->ethaddr);
+}
+
+static void ncm_function_unbind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ gether_cleanup();
+}
+
+static struct android_usb_function ncm_function = {
+ .name = "ncm",
+ .init = ncm_function_init,
+ .cleanup = ncm_function_cleanup,
+ .bind_config = ncm_function_bind_config,
+ .unbind_config = ncm_function_unbind_config,
+};
+
+bool is_ncm_ready(char *name)
+{
+ /* Enable ncm function */
+ if (!strcmp(name, "rndis") || !strcmp(name, "ncm")) {
+ if (ncm_connect) {
+ printk(KERN_DEBUG "usb: %s ncm ready (%s)\n",
+ __func__, name);
+ return true;
+ }
+ }
+ return false;
+}
+
+void set_ncm_device_descriptor(struct usb_device_descriptor *desc)
+{
+ desc->idProduct = 0x685d;
+ desc->bDeviceClass = USB_CLASS_COMM;
+ printk(KERN_DEBUG "usb: %s idProduct=0x%x, DeviceClass=0x%x\n",
+ __func__, desc->idProduct, desc->bDeviceClass);
+
+}
+
+void set_ncm_ready(bool ready)
+{
+ if (ready != ncm_connect)
+ printk(KERN_DEBUG "usb: %s old status=%d, new status=%d\n",
+ __func__, ncm_connect, ready);
+ ncm_connect = ready;
+ if (ready == false) {
+ terminal_mode_version = 0;
+ terminal_mode_vendor_id = 0;
+ }
+}
+
+static ssize_t terminal_version_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ ret = sprintf(buf, "major %x minor %x vendor %x\n",
+ terminal_mode_version & 0xff,
+ (terminal_mode_version >> 8 & 0xff),
+ terminal_mode_vendor_id);
+ printk(KERN_DEBUG "usb: %s terminal_mode %s\n", __func__, buf);
+ return ret;
+}
+
+static ssize_t terminal_version_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ int value;
+ sscanf(buf, "%x", &value);
+ terminal_mode_version = (u16)value;
+ printk(KERN_DEBUG "usb: %s buf=%s\n", __func__, buf);
+ /* always set ncm ready */
+ set_ncm_ready(true);
+ return size;
+}
+
+static DEVICE_ATTR(terminal_version, S_IRUGO | S_IWUSR,
+ terminal_version_show, terminal_version_store);
+
+static int create_terminal_attribute(struct device **pdev)
+{
+ int err;
+ if (IS_ERR(*pdev)) {
+ printk(KERN_DEBUG "usb: %s error pdev(%p)\n",
+ __func__, *pdev);
+ return PTR_ERR(*pdev);
+ }
+
+ err = device_create_file(*pdev, &dev_attr_terminal_version);
+ if (err) {
+ printk(KERN_DEBUG "usb: %s failed to create attr\n",
+ __func__);
+ return err;
+ }
+ return 0;
+}
+
+static int terminal_ctrl_request(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+
+ if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
+ /* Handle Terminal mode request */
+ if (ctrl->bRequest == 0xf0) {
+ terminal_mode_version = w_value;
+ terminal_mode_vendor_id = w_index;
+ set_ncm_ready(true);
+ printk(KERN_DEBUG "usb: %s ver=0x%x vendor_id=0x%x\n",
+ __func__, terminal_mode_version,
+ terminal_mode_vendor_id);
+ value = 0;
+ }
+ }
+
+ /* respond ZLP */
+ if (value >= 0) {
+ int rc;
+ cdev->req->zero = 0;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ printk(KERN_DEBUG "usb: %s failed usb_ep_queue\n",
+ __func__);
+ }
+ return value;
+}
diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c
index 40f7716..6698ebb14 100644
--- a/drivers/usb/gadget/u_serial.c
+++ b/drivers/usb/gadget/u_serial.c
@@ -122,7 +122,7 @@ struct gs_port {
};
/* increase N_PORTS if you need more */
-#define N_PORTS 4
+#define N_PORTS 8
static struct portmaster {
struct mutex lock; /* protect open/close */
struct gs_port *port;
@@ -380,7 +380,8 @@ __acquires(&port->port_lock)
req->length = len;
list_del(&req->list);
- req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0);
+ req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0)
+ && (req->length % in->maxpacket == 0);
pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n",
port->port_num, len, *((u8 *)req->buf),
@@ -1028,7 +1029,7 @@ static const struct tty_operations gs_tty_ops = {
static struct tty_driver *gs_tty_driver;
-static int __init
+static int
gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
{
struct gs_port *port;
@@ -1074,7 +1075,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding)
*
* Returns negative errno or zero.
*/
-int __init gserial_setup(struct usb_gadget *g, unsigned count)
+int gserial_setup(struct usb_gadget *g, unsigned count)
{
unsigned i;
struct usb_cdc_line_coding coding;
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index ab085f1..912cac7 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -19,7 +19,7 @@ config USB_C67X00_HCD
config USB_XHCI_HCD
tristate "xHCI HCD (USB 3.0) support (EXPERIMENTAL)"
- depends on USB && PCI && EXPERIMENTAL
+ depends on USB && USB_ARCH_HAS_XHCI && EXPERIMENTAL
---help---
The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0
"SuperSpeed" host controller hardware.
@@ -37,6 +37,12 @@ config USB_XHCI_HCD_DEBUGGING
If unsure, say N.
+config USB_XHCI_EXYNOS
+ boolean "EXYNOS XHCI support"
+ depends on USB_XHCI_HCD && ARCH_EXYNOS
+ help
+ Enable support for the EXYNOS SOC's on-chip XHCI controller.
+
config USB_EHCI_HCD
tristate "EHCI HCD (USB 2.0) support"
depends on USB && USB_ARCH_HAS_EHCI
@@ -189,11 +195,25 @@ config USB_EHCI_SH
If you use the PCI EHCI controller, this option is not necessary.
config USB_EHCI_S5P
- boolean "S5P EHCI support"
- depends on USB_EHCI_HCD && PLAT_S5P
- help
+ boolean "S5P EHCI support"
+ depends on USB_EHCI_HCD && PLAT_S5P
+ help
Enable support for the S5P SOC's on-chip EHCI controller.
+config USB_S5P_HSIC0
+ boolean "S5P HSIC0 support"
+ depends on USB_EHCI_HCD && PLAT_S5P && USB_EHCI_S5P
+ default y
+ help
+ Enable support for the S5P SOC's on-chip HSIC PHY.
+
+config USB_S5P_HSIC1
+ boolean "S5P HSIC1 support"
+ depends on USB_EHCI_HCD && PLAT_S5P && USB_EHCI_S5P
+ default y
+ help
+ Enable support for the S5P SOC's on-chip HSIC PHY.
+
config USB_W90X900_EHCI
bool "W90X900(W90P910) EHCI support"
depends on USB_EHCI_HCD && ARCH_W90X900
@@ -371,6 +391,12 @@ config USB_OHCI_SH
Enables support for the on-chip OHCI controller on the SuperH.
If you use the PCI OHCI controller, this option is not necessary.
+config USB_OHCI_S5P
+ boolean "S5P OHCI support"
+ depends on USB_OHCI_HCD && PLAT_S5P
+ help
+ Enable support for the S5P SOC's on-chip OHCI controller.
+
config USB_CNS3XXX_OHCI
bool "Cavium CNS3XXX OHCI Module"
depends on USB_OHCI_HCD && ARCH_CNS3XXX
@@ -578,3 +604,18 @@ config USB_OCTEON_OHCI
config USB_OCTEON2_COMMON
bool
default y if USB_OCTEON_EHCI || USB_OCTEON_OHCI
+
+config USB_S3C_OTG_HOST
+ tristate "S3C USB OTG Host support"
+ depends on USB && (PLAT_S3C64XX || PLAT_S5P)
+ help
+ Samsung's S3C64XX processors include high speed USB OTG2.0
+ controller. It has 15 configurable endpoints, as well as
+ endpoint zero (for control transfers).
+
+ This driver support only OTG Host role. If you want to use
+ OTG Device role, select USB Gadget support and S3C OTG Device.
+
+ Say "y" to link the driver statically, or "m" to build a
+ dynamically linked module called "s3c_otg_hcd" and force all
+ drivers to also be dynamically linked.
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 624a362..5720733 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -11,10 +11,13 @@ fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o
fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o
-xhci-hcd-y := xhci.o xhci-mem.o xhci-pci.o
+xhci-hcd-y := xhci.o xhci-mem.o
xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o
+xhci-hcd-$(CONFIG_PCI) += xhci-pci.o
+xhci-hcd-$(CONFIG_USB_XHCI_EXYNOS) += xhci-exynos.o
obj-$(CONFIG_USB_WHCI_HCD) += whci/
+obj-$(CONFIG_USB_S3C_OTG_HOST) += shost/
obj-$(CONFIG_PCI) += pci-quirks.o
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index b27ceab..ac0377f 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -885,12 +885,32 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd)
ehci->reset_done[i] == 0))
continue;
+#if defined(CONFIG_LINK_DEVICE_HSIC)
+ /* FIXME: Debugging
+ * HSIC with XMM6262 don't use the remote wakeup,
+ * but someitme EHCI got the PCD and we have a doubt
+ * whether remote wakeup or not. So, we find the irq
+ * from XMM6262, print out the port status.
+ */
+ printk(KERN_DEBUG "mif: %s port %d,"
+ "pcd status 0x%x, ehci port status 0x%x, "
+ "suspend_ports %lu, reset_done %lu\n",
+ __func__, i + 1, status, pstatus,
+ ehci->suspended_ports, ehci->reset_done[i]);
+#endif
+
/* start 20 msec resume signaling from this port,
* and make khubd collect PORT_STAT_C_SUSPEND to
* stop that signaling. Use 5 ms extra for safety,
* like usb_port_resume() does.
*/
+#ifdef CONFIG_LINK_DEVICE_HSIC
+ /* ensure suspend bit clear by adding 5 msec delay. */
+ ehci->reset_done[i] = jiffies + msecs_to_jiffies(30);
+#else
ehci->reset_done[i] = jiffies + msecs_to_jiffies(25);
+#endif
+
ehci_dbg (ehci, "port %d remote wakeup\n", i + 1);
mod_timer(&hcd->rh_timer, ehci->reset_done[i]);
}
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index f5d7fed..fb0394b 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -734,6 +734,340 @@ ehci_hub_descriptor (
}
/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_HOST_COMPLIANT_TEST
+struct api_context {
+ struct completion done;
+ int status;
+};
+
+static int single_step_get_descriptor( struct usb_hcd *hcd, u8 port)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
+ struct list_head qtd_list;
+ struct list_head test_list;
+ struct usb_device *udev;
+ struct ehci_qtd *qtd;
+ struct urb *urb;
+ struct usb_ctrlrequest setup_packet;
+ char data_buffer[USB_DT_DEVICE_SIZE];
+ int retval = 0;
+
+ ehci_info (ehci, "Testing SINGLE_STEP_GET_DEV_DESC\n");
+
+ udev = hcd->self.root_hub;
+ if (udev == NULL) {
+ ehci_err (ehci, "EHSET: root_hub pointer is NULL\n");
+ retval = -EPIPE;
+ goto error;
+ }
+
+ if (udev->children[port] != NULL) {
+ udev = udev->children[port];
+ }
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+ if (!urb) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ setup_packet.bRequestType = USB_DIR_IN;
+ setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_packet.wValue = (USB_DT_DEVICE << 8);
+ setup_packet.wIndex = 0;
+ setup_packet.wLength = USB_DT_DEVICE_SIZE;
+
+ INIT_LIST_HEAD (&qtd_list);
+ INIT_LIST_HEAD (&test_list);
+
+ urb->dev = udev;
+ urb->pipe = usb_rcvctrlpipe(udev, 0);
+ urb->hcpriv= udev->ep0.hcpriv;
+ urb->setup_packet = (char *)&setup_packet;
+ urb->transfer_buffer = data_buffer;
+ urb->transfer_flags = URB_HCD_DRIVER_TEST;
+ urb->ep = udev->ep_in[usb_pipeendpoint(urb->pipe)];
+ if (!urb->ep) {
+ retval = -ENOENT;
+ goto error;
+ }
+
+ urb->setup_dma = dma_map_single(
+ hcd->self.controller,
+ urb->setup_packet,
+ sizeof(struct usb_ctrlrequest),
+ DMA_TO_DEVICE);
+
+ urb->transfer_dma = dma_map_single (
+ hcd->self.controller,
+ urb->transfer_buffer,
+ urb->transfer_buffer_length,
+ DMA_TO_DEVICE);
+
+ if (!urb->setup_dma || !urb->transfer_dma) {
+ ehci_err (ehci, "dma_map_single Failed"
+ "\n");
+ retval = -EBUSY;
+ goto error;
+ }
+
+ if (!qh_urb_transaction (ehci, urb, &qtd_list,
+ GFP_ATOMIC)) {
+ ehci_err (ehci, "qh_urb_transaction "
+ "Failed\n");
+ retval = -EBUSY;
+ goto error;
+ }
+
+ qtd = container_of (qtd_list.next,
+ struct ehci_qtd, qtd_list);
+ list_del_init (&qtd->qtd_list);
+ list_add (&qtd->qtd_list, &test_list);
+ qtd = container_of (qtd_list.next,
+ struct ehci_qtd, qtd_list);
+ list_del_init (&qtd->qtd_list);
+ ehci_qtd_free (ehci, qtd);
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(15000));
+
+ ehci_info (ehci, "Sending SETUP PHASE\n");
+ if (submit_async (ehci, urb,
+ &test_list, GFP_ATOMIC)) {
+ ehci_err (ehci, "Failed to queue up "
+ "qtds\n");
+ retval = -EBUSY;
+ goto error;
+ }
+error:
+ return retval;
+}
+
+static int single_step_set_feature( struct usb_hcd *hcd, u8 port)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
+ struct usb_device *udev;
+ struct list_head qtd_list;
+ struct list_head setup_list;
+ struct list_head data_list;
+ struct ehci_qtd *qtd;
+ struct urb *urb;
+ struct usb_ctrlrequest setup_packet;
+ char data_buffer[USB_DT_DEVICE_SIZE];
+ int retval = 0;
+
+ ehci_info (ehci, "Testing SINGLE_STEP_SET_FEATURE\n");
+
+ udev = hcd->self.root_hub;
+ if (udev == NULL) {
+ ehci_err (ehci, "EHSET: root_hub pointer is NULL\n");
+ retval = -EPIPE;
+ goto error;
+ }
+
+ if (udev->children[port] != NULL) {
+ udev = udev->children[port];
+ }
+
+ urb = usb_alloc_urb(0, GFP_ATOMIC);
+ if (!urb) {
+ retval = -ENOMEM;
+ goto error;
+ }
+ setup_packet.bRequestType = USB_DIR_IN;
+ setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR;
+ setup_packet.wValue = (USB_DT_DEVICE << 8);
+ setup_packet.wIndex = 0;
+ setup_packet.wLength = USB_DT_DEVICE_SIZE;
+
+ INIT_LIST_HEAD (&qtd_list);
+ INIT_LIST_HEAD (&setup_list);
+ INIT_LIST_HEAD (&data_list);
+
+ urb->transfer_buffer_length = USB_DT_DEVICE_SIZE;
+ urb->dev = udev;
+ urb->pipe = usb_rcvctrlpipe(udev, 0);
+ urb->hcpriv = udev->ep0.hcpriv;
+ urb->setup_packet = (char *)&setup_packet;
+ urb->transfer_buffer = data_buffer;
+ urb->transfer_flags = URB_HCD_DRIVER_TEST;
+ urb->ep = udev->ep_in[usb_pipeendpoint(urb->pipe)];
+ if (!urb->ep) {
+ retval = -ENOENT;
+ goto error;
+ }
+
+ urb->setup_dma = dma_map_single( hcd->self.controller,
+ urb->setup_packet,
+ sizeof (struct usb_ctrlrequest),
+ DMA_TO_DEVICE);
+
+ urb->transfer_dma = dma_map_single (hcd->self.controller,
+ urb->transfer_buffer,
+ sizeof (struct usb_ctrlrequest),
+ DMA_TO_DEVICE);
+
+ if (!urb->setup_dma || !urb->transfer_dma) {
+ ehci_err (ehci, "dma_map_single Failed\n");
+ retval = -EBUSY;
+ goto error;
+ }
+
+ if (!qh_urb_transaction (ehci, urb, &qtd_list, GFP_ATOMIC)) {
+ ehci_err (ehci, "qh_urb_transaction Failed\n");
+ retval = -EBUSY;
+ goto error;
+ }
+
+ qtd = container_of (qtd_list.next, struct ehci_qtd, qtd_list);
+ list_del_init (&qtd->qtd_list);
+ list_add (&qtd->qtd_list, &setup_list);
+ qtd = container_of (qtd_list.next, struct ehci_qtd, qtd_list);
+ list_del_init (&qtd->qtd_list);
+ list_add (&qtd->qtd_list, &data_list);
+ qtd = container_of (qtd_list.next, struct ehci_qtd, qtd_list);
+ list_del_init (&qtd->qtd_list);
+ ehci_qtd_free (ehci, qtd);
+
+ ehci_info (ehci, "Sending SETUP PHASE\n");
+ if (submit_async (ehci, urb, &setup_list, GFP_ATOMIC)) {
+ ehci_err (ehci, "Failed to queue up qtds\n");
+ retval = -EBUSY;
+ goto error;
+ }
+
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(15000));
+ urb->status = 0;
+ urb->actual_length = 0;
+
+ ehci_info (ehci, "Sending DATA PHASE\n");
+ if (submit_async (ehci, urb, &data_list, GFP_ATOMIC))
+ {
+ ehci_err (ehci, "Failed to queue up qtds\n");
+ retval = -EBUSY;
+ goto error;
+ }
+error:
+ return retval;
+}
+
+static int ehci_port_test(struct usb_hcd *hcd, u8 selector, u8 port,
+ unsigned long flags)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci (hcd);
+ u32 temp;
+ u32 __iomem *status_reg = &ehci->regs->port_status[port];
+ int retval = 0;
+
+ temp = ehci_readl(ehci, status_reg);
+
+ ehci_info (ehci, "status_reg BEFORE write regs = 0x%x\n",temp);
+ switch (selector) {
+ case USB_PORT_TEST_J:
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ ehci_info (ehci, "Testing J State\n");
+ ehci_quiesce(ehci);
+ if(hcd->driver->bus_suspend)
+ hcd->driver->bus_suspend(hcd);
+ ehci_halt(ehci);
+ spin_lock_irqsave (&ehci->lock, flags);
+ ehci_writel(ehci, temp|PORT_TEST_J, status_reg);
+ break;
+
+ case USB_PORT_TEST_K:
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ ehci_info (ehci, "Testing K State\n");
+ ehci_quiesce(ehci);
+ if(hcd->driver->bus_suspend)
+ hcd->driver->bus_suspend(hcd);
+ ehci_halt(ehci);
+ spin_lock_irqsave (&ehci->lock, flags);
+ ehci_writel(ehci, temp|PORT_TEST_K, status_reg);
+ break;
+
+ case USB_PORT_TEST_SE0_NAK:
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ ehci_info (ehci, "Testing SE0_NAK\n");
+ ehci_quiesce(ehci);
+ if(hcd->driver->bus_suspend)
+ hcd->driver->bus_suspend(hcd);
+ ehci_halt(ehci);
+ spin_lock_irqsave (&ehci->lock, flags);
+ ehci_writel(ehci, temp|PORT_TEST_SE0_NAK, status_reg);
+ break;
+
+ case USB_PORT_TEST_PACKET:
+ ehci_info (ehci, "Sending Test Packets\n");
+ ehci_writel(ehci, temp|PORT_TEST_PKT, status_reg);
+ break;
+
+ case USB_PORT_TEST_FORCE_ENABLE:
+ ehci_info (ehci, "Testing FORCE_ENABLE\n");
+ ehci_writel(ehci, temp|PORT_TEST_FORCE, status_reg);
+ break;
+
+ case (EHSET_HS_HOST_PORT_SUSPEND_RESUME & 0xFF):
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ ehci_info (ehci, "Testing SUSPEND RESUME\n");
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(15000));
+ ehci_info (ehci, "Suspend Root Hub\n");
+ temp = ehci_readl(ehci, status_reg);
+ ehci_info(ehci, "[Before -> Suspend Status Reg : 0x%x\n",temp);
+ if(hcd->driver->bus_suspend)
+ hcd->driver->bus_suspend(hcd);
+ temp = ehci_readl(ehci, status_reg);
+ ehci_info(ehci, "[After -> Suspend Status Reg : 0x%x\n",temp);
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(msecs_to_jiffies(15000));
+ ehci_info (ehci, "Resume Root Hub\n");
+ if(hcd->driver->bus_resume)
+ hcd->driver->bus_resume(hcd);
+
+ spin_lock_irqsave (&ehci->lock, flags);
+ break;
+
+ case (EHSET_SINGLE_STEP_GET_DEV_DESC&0xFF):
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ retval = single_step_get_descriptor(hcd, port);
+ if (retval < 0) {
+ ehci_err (ehci, "EHSET: get descriptor test fail\n");
+ spin_lock_irqsave (&ehci->lock, flags);
+ goto error;
+ }
+ spin_lock_irqsave (&ehci->lock, flags);
+ break;
+
+ case (EHSET_SINGLE_STEP_SET_FEATURE & 0xFF):
+ spin_unlock_irqrestore (&ehci->lock, flags);
+ retval = single_step_set_feature(hcd, port);
+ if (retval < 0) {
+ ehci_err (ehci, "EHSET: set feature test fail\n");
+ spin_lock_irqsave (&ehci->lock, flags);
+ goto error;
+ }
+ spin_lock_irqsave (&ehci->lock, flags);
+ break;
+ default:
+ ehci_err (ehci, "EHSET: Unknown test %x\n",
+ (selector));
+ goto error;
+ }
+
+ temp = ehci_readl(ehci, status_reg);
+ ehci_info (ehci, "status_reg AFTER write regs = 0x%x\n",temp);
+ ehci_err(ehci, "EHSET test done. retval = 0x%x\n", retval);
+ return retval;
+
+error:
+ ehci_err (ehci, "EHSET test error. retval = 0x%x\n",retval);
+ return retval;
+
+}
+
+#endif /* CONFIG_HOST_COMPLIANT_TEST */
static int ehci_hub_control (
struct usb_hcd *hcd,
@@ -1116,9 +1450,20 @@ static int ehci_hub_control (
* or else system reboot). See EHCI 2.3.9 and 4.14 for info
* about the EHCI-specific stuff.
*/
+#ifdef CONFIG_HOST_COMPLIANT_TEST
case USB_PORT_FEAT_TEST:
- if (!selector || selector > 5)
+ ehci_info (ehci, "TEST MODE !!!!!!!! selector == 0x%x \n",selector);
+
+ ehci_info (ehci, "running EHCI test %x on port %x\n",
+ selector, wIndex);
+
+ retval = ehci_port_test(hcd, selector & 0xFF, wIndex, flags);
+ if (retval < 0) {
+ ehci_info (ehci, "EHCI test Fail!!\n");
goto error;
+ }
+ break;
+#endif
ehci_quiesce(ehci);
/* Put all enabled ports into suspend */
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 0917e3a..e4cc485 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -289,6 +289,12 @@ __acquires(ehci->lock)
urb->actual_length, urb->transfer_buffer_length);
#endif
+#ifdef CONFIG_HOST_COMPLIANT_TEST
+ if (likely (urb->transfer_flags == URB_HCD_DRIVER_TEST)) {
+ ehci_dbg(ehci, "USB_TEST : transfer_flags = URB_HCD_DRIVER_TEST so... return!\n");
+ return;
+ }
+#endif
/* complete() can reenter this HCD */
usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb);
spin_unlock (&ehci->lock);
@@ -387,10 +393,6 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
QTD_CERR(token) == 0 &&
++qh->xacterrs < QH_XACTERR_MAX &&
!urb->unlinked) {
- ehci_dbg(ehci,
- "detected XactErr len %zu/%zu retry %d\n",
- qtd->length - QTD_LENGTH(token), qtd->length, qh->xacterrs);
-
/* reset the token in the qtd and the
* qh overlay (which still contains
* the qtd) so that we pick up from
@@ -406,6 +408,11 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh)
token);
goto retry_xacterr;
}
+ if (qh->xacterrs >= QH_XACTERR_MAX)
+ ehci_dbg(ehci,
+ "detected XactErr len %zu/%zu retry %d\n",
+ qtd->length - QTD_LENGTH(token),
+ qtd->length, qh->xacterrs);
stopped = 1;
/* magic dummy for some short reads; qh won't advance.
@@ -995,6 +1002,12 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh)
head->qh_next.qh = qh;
head->hw->hw_next = dma;
+ /*
+ * flush qh descriptor into memory immediately,
+ * see comments in qh_append_tds.
+ * */
+ ehci_sync_mem();
+
qh_get(qh);
qh->xacterrs = 0;
qh->qh_state = QH_STATE_LINKED;
@@ -1082,6 +1095,18 @@ static struct ehci_qh *qh_append_tds (
wmb ();
dummy->hw_token = token;
+ /*
+ * Writing to dma coherent buffer on ARM may
+ * be delayed to reach memory, so HC may not see
+ * hw_token of dummy qtd in time, which can cause
+ * the qtd transaction to be executed very late,
+ * and degrade performance a lot. ehci_sync_mem
+ * is added to flush 'token' immediatelly into
+ * memory, so that ehci can execute the transaction
+ * ASAP.
+ * */
+ ehci_sync_mem();
+
urb->hcpriv = qh_get (qh);
}
}
diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c
index 491a209..8cb7ae2 100644
--- a/drivers/usb/host/ehci-s5p.c
+++ b/drivers/usb/host/ehci-s5p.c
@@ -14,17 +14,324 @@
#include <linux/clk.h>
#include <linux/platform_device.h>
-#include <mach/regs-pmu.h>
+#include <linux/pm_runtime.h>
+
#include <plat/cpu.h>
#include <plat/ehci.h>
#include <plat/usb-phy.h>
+#include <mach/regs-pmu.h>
+#include <mach/regs-usb-host.h>
+#include <mach/board_rev.h>
+
struct s5p_ehci_hcd {
struct device *dev;
struct usb_hcd *hcd;
struct clk *clk;
+ int power_on;
};
+#ifdef CONFIG_USB_EXYNOS_SWITCH
+int s5p_ehci_port_power_off(struct platform_device *pdev)
+{
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ (void) ehci_hub_control(hcd,
+ ClearPortFeature,
+ USB_PORT_FEAT_POWER,
+ 1, NULL, 0);
+ /* Flush those writes */
+ ehci_readl(ehci, &ehci->regs->command);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_ehci_port_power_off);
+
+int s5p_ehci_port_power_on(struct platform_device *pdev)
+{
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ (void) ehci_hub_control(hcd,
+ SetPortFeature,
+ USB_PORT_FEAT_POWER,
+ 1, NULL, 0);
+ /* Flush those writes */
+ ehci_readl(ehci, &ehci->regs->command);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_ehci_port_power_on);
+#endif
+
+static int s5p_ehci_configurate(struct usb_hcd *hcd)
+{
+ int delay_count = 0;
+
+ /* This is for waiting phy before ehci configuration */
+ do {
+ if (readl(hcd->regs))
+ break;
+ udelay(1);
+ ++delay_count;
+ } while (delay_count < 200);
+ if (delay_count)
+ dev_info(hcd->self.controller, "phy delay count = %d\n",
+ delay_count);
+
+ /* DMA burst Enable, set utmi suspend_on_n */
+ writel(readl(INSNREG00(hcd->regs)) | ENA_DMA_INCR | OHCI_SUSP_LGCY,
+ INSNREG00(hcd->regs));
+ return 0;
+}
+
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) ||\
+ defined(CONFIG_CDMA_MODEM_MDM6600)
+#define CP_PORT 2 /* HSIC0 in S5PC210 */
+#define RETRY_CNT_LIMIT 30 /* Max 300ms wait for cp resume*/
+
+int s5p_ehci_port_control(struct platform_device *pdev, int port, int enable)
+{
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ (void) ehci_hub_control(hcd,
+ enable ? SetPortFeature : ClearPortFeature,
+ USB_PORT_FEAT_POWER,
+ port, NULL, 0);
+ /* Flush those writes */
+ ehci_readl(ehci, &ehci->regs->command);
+ return 0;
+}
+
+static void s5p_wait_for_cp_resume(struct platform_device *pdev,
+ struct usb_hcd *hcd)
+{
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+ u32 __iomem *portsc ;
+ u32 val32, retry_cnt = 0;
+
+ portsc = &ehci->regs->port_status[CP_PORT-1];
+
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_ON);
+
+ do {
+ msleep(10);
+ val32 = ehci_readl(ehci, portsc);
+ } while (++retry_cnt < RETRY_CNT_LIMIT && !(val32 & PORT_CONNECT));
+
+ if (retry_cnt >= RETRY_CNT_LIMIT)
+ dev_info(&pdev->dev, "%s: retry_cnt = %d, portsc = 0x%x\n",
+ __func__, retry_cnt, val32);
+
+#if defined(CONFIG_UMTS_MODEM_XMM6262)
+ if (pdata->get_cp_active_state && !pdata->get_cp_active_state()) {
+ s5p_ehci_port_control(pdev, CP_PORT, 0);
+ pr_err("mif: force port%d off by cp reset\n", CP_PORT);
+ }
+#endif
+}
+#endif
+
+static void s5p_ehci_phy_init(struct platform_device *pdev)
+{
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ u32 delay_count = 0;
+
+ if (pdata->phy_init) {
+ pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+
+ while (!readl(hcd->regs) && delay_count < 200) {
+ delay_count++;
+ udelay(1);
+ }
+ if (delay_count)
+ dev_info(&pdev->dev, "waiting time = %d\n",
+ delay_count);
+ s5p_ehci_configurate(hcd);
+ dev_dbg(&pdev->dev, "%s : 0x%x\n", __func__,
+ readl(INSNREG00(hcd->regs)));
+ }
+
+}
+
+#ifdef CONFIG_PM
+static int s5p_ehci_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ unsigned long flags;
+ int rc = 0;
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(10);
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible, bail out if RH has been resumed. Use
+ * the spinlock to properly synchronize with possible pending
+ * RH suspend or resume activity.
+ *
+ * This is still racy as hcd->state is manipulated outside of
+ * any locks =P But that will be a different fix.
+ */
+
+ spin_lock_irqsave(&ehci->lock, flags);
+ if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) {
+ spin_unlock_irqrestore(&ehci->lock, flags);
+ return -EINVAL;
+ }
+ ehci_writel(ehci, 0, &ehci->regs->intr_enable);
+ (void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ spin_unlock_irqrestore(&ehci->lock, flags);
+
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_OFF);
+#endif
+
+ clk_disable(s5p_ehci->clk);
+
+ return rc;
+}
+
+static int s5p_ehci_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+
+ clk_enable(s5p_ehci->clk);
+ pm_runtime_resume(&pdev->dev);
+
+ s5p_ehci_phy_init(pdev);
+
+ /* if EHCI was off, hcd was removed */
+ if (!s5p_ehci->power_on) {
+ dev_info(dev, "Nothing to do for the device (power off)\n");
+ return 0;
+ }
+
+ if (time_before(jiffies, ehci->next_statechange))
+ msleep(10);
+
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) {
+ int mask = INTR_MASK;
+
+ if (!hcd->self.root_hub->do_remote_wakeup)
+ mask &= ~STS_PCD;
+ ehci_writel(ehci, mask, &ehci->regs->intr_enable);
+ ehci_readl(ehci, &ehci->regs->intr_enable);
+ return 0;
+ }
+
+ ehci_dbg(ehci, "lost power, restarting\n");
+ usb_root_hub_lost_power(hcd->self.root_hub);
+
+ (void) ehci_halt(ehci);
+ (void) ehci_reset(ehci);
+
+ /* emptying the schedule aborts any urbs */
+ spin_lock_irq(&ehci->lock);
+ if (ehci->reclaim)
+ end_unlink_async(ehci);
+ ehci_work(ehci);
+ spin_unlock_irq(&ehci->lock);
+
+ ehci_writel(ehci, ehci->command, &ehci->regs->command);
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
+
+ /* here we "know" root ports should always stay powered */
+ ehci_port_power(ehci, 1);
+
+ hcd->state = HC_STATE_SUSPENDED;
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ s5p_wait_for_cp_resume(pdev, hcd);
+#endif
+ return 0;
+}
+
+#else
+#define s5p_ehci_suspend NULL
+#define s5p_ehci_resume NULL
+#endif
+
+#ifdef CONFIG_USB_SUSPEND
+static int s5p_ehci_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+
+ if (pdata && pdata->phy_suspend)
+ pdata->phy_suspend(pdev, S5P_USB_PHY_HOST);
+
+ return 0;
+}
+
+static int s5p_ehci_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+ int rc = 0;
+
+ if (dev->power.is_suspended)
+ return 0;
+
+ /* platform device isn't suspended */
+ if (pdata && pdata->phy_resume)
+ rc = pdata->phy_resume(pdev, S5P_USB_PHY_HOST);
+
+ if (rc) {
+ s5p_ehci_configurate(hcd);
+
+ /* emptying the schedule aborts any urbs */
+ spin_lock_irq(&ehci->lock);
+ if (ehci->reclaim)
+ end_unlink_async(ehci);
+ ehci_work(ehci);
+ spin_unlock_irq(&ehci->lock);
+
+ usb_root_hub_lost_power(hcd->self.root_hub);
+
+ ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
+ (void)ehci_readl(ehci, &ehci->regs->intr_enable);
+
+ /* here we "know" root ports should always stay powered */
+ ehci_port_power(ehci, 1);
+
+ hcd->state = HC_STATE_SUSPENDED;
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ s5p_wait_for_cp_resume(pdev, hcd);
+#endif
+ }
+
+ return 0;
+}
+#else
+#define s5p_ehci_runtime_suspend NULL
+#define s5p_ehci_runtime_resume NULL
+#endif
+
static const struct hc_driver s5p_ehci_hc_driver = {
.description = hcd_name,
.product_desc = "S5P EHCI Host Controller",
@@ -56,6 +363,140 @@ static const struct hc_driver s5p_ehci_hc_driver = {
.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
};
+static ssize_t show_ehci_power(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+
+ return sprintf(buf, "EHCI Power %s\n", (s5p_ehci->power_on) ? "on" : "off");
+}
+
+static ssize_t store_ehci_power(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ehci->hcd;
+ int power_on;
+ int irq;
+ int retval;
+
+ if (sscanf(buf, "%d", &power_on) != 1)
+ return -EINVAL;
+
+ device_lock(dev);
+
+ if (!power_on && s5p_ehci->power_on) {
+ printk(KERN_DEBUG "%s: EHCI turns off\n", __func__);
+ pm_runtime_forbid(dev);
+ s5p_ehci->power_on = 0;
+ usb_remove_hcd(hcd);
+
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ /*HSIC IPC control the ACTIVE_STATE*/
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_OFF);
+#endif
+ } else if (power_on) {
+ printk(KERN_DEBUG "%s: EHCI turns on\n", __func__);
+ if (s5p_ehci->power_on) {
+ pm_runtime_forbid(dev);
+ usb_remove_hcd(hcd);
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ /*HSIC IPC control the ACTIVE_STATE*/
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_OFF);
+#endif
+ } else
+ s5p_ehci_phy_init(pdev);
+
+ irq = platform_get_irq(pdev, 0);
+ retval = usb_add_hcd(hcd, irq,
+ IRQF_DISABLED | IRQF_SHARED);
+ if (retval < 0) {
+ dev_err(dev, "Power On Fail\n");
+ goto exit;
+ }
+
+ s5p_ehci->power_on = 1;
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ /* Sometimes XMM6262 send remote wakeup when hub enter suspend
+ * So, set the hub waiting 500ms autosuspend delay*/
+ if (hcd->self.root_hub)
+ pm_runtime_set_autosuspend_delay(
+ &hcd->self.root_hub->dev,
+ msecs_to_jiffies(500));
+
+ /*HSIC IPC control the ACTIVE_STATE*/
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_ON);
+#endif
+ pm_runtime_allow(dev);
+ }
+exit:
+ device_unlock(dev);
+ return count;
+}
+static DEVICE_ATTR(ehci_power, 0664, show_ehci_power, store_ehci_power);
+
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+static ssize_t store_port_power(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ehci_platdata *pdata = pdev->dev.platform_data;
+
+ int power_on, port;
+ int err;
+
+ err = sscanf(buf, "%d %d", &power_on, &port);
+ if (err < 2 || port < 0 || port > 3 || power_on < 0 || power_on > 1) {
+ pr_err("port power fail: port_power 1 2(port 2 enable 1)\n");
+ return count;
+ }
+
+ pr_debug("%s: Port:%d power: %d\n", __func__, port, power_on);
+ device_lock(dev);
+ s5p_ehci_port_control(pdev, port, power_on);
+
+ /*HSIC IPC control the ACTIVE_STATE*/
+ if (pdata && pdata->noti_host_states && port == CP_PORT)
+ pdata->noti_host_states(pdev, power_on ? S5P_HOST_ON :
+ S5P_HOST_OFF);
+ device_unlock(dev);
+ return count;
+}
+static DEVICE_ATTR(port_power, 0664, NULL, store_port_power);
+#endif
+
+static inline int create_ehci_sys_file(struct ehci_hcd *ehci)
+{
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ BUG_ON(device_create_file(ehci_to_hcd(ehci)->self.controller,
+ &dev_attr_port_power));
+#endif
+ return device_create_file(ehci_to_hcd(ehci)->self.controller,
+ &dev_attr_ehci_power);
+}
+
+static inline void remove_ehci_sys_file(struct ehci_hcd *ehci)
+{
+ device_remove_file(ehci_to_hcd(ehci)->self.controller,
+ &dev_attr_ehci_power);
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ device_remove_file(ehci_to_hcd(ehci)->self.controller,
+ &dev_attr_port_power);
+#endif
+}
+
static int __devinit s5p_ehci_probe(struct platform_device *pdev)
{
struct s5p_ehci_platdata *pdata;
@@ -75,7 +516,6 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev)
s5p_ehci = kzalloc(sizeof(struct s5p_ehci_hcd), GFP_KERNEL);
if (!s5p_ehci)
return -ENOMEM;
-
s5p_ehci->dev = &pdev->dev;
hcd = usb_create_hcd(&s5p_ehci_hc_driver, &pdev->dev,
@@ -122,8 +562,9 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev)
goto fail;
}
- if (pdata->phy_init)
- pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+ platform_set_drvdata(pdev, s5p_ehci);
+
+ s5p_ehci_phy_init(pdev);
ehci = hcd_to_ehci(hcd);
ehci->caps = hcd->regs;
@@ -142,7 +583,20 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev)
goto fail;
}
- platform_set_drvdata(pdev, s5p_ehci);
+ create_ehci_sys_file(ehci);
+ s5p_ehci->power_on = 1;
+
+#ifdef CONFIG_USB_SUSPEND
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+#endif
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ /* for cp enumeration */
+ pm_runtime_forbid(&pdev->dev);
+ /*HSIC IPC control the ACTIVE_STATE*/
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_ON);
+#endif
return 0;
@@ -165,8 +619,18 @@ static int __devexit s5p_ehci_remove(struct platform_device *pdev)
struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev);
struct usb_hcd *hcd = s5p_ehci->hcd;
+#ifdef CONFIG_USB_SUSPEND
+ pm_runtime_disable(&pdev->dev);
+#endif
+ s5p_ehci->power_on = 0;
+ remove_ehci_sys_file(hcd_to_ehci(hcd));
usb_remove_hcd(hcd);
+#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB)
+ /*HSIC IPC control the ACTIVE_STATE*/
+ if (pdata && pdata->noti_host_states)
+ pdata->noti_host_states(pdev, S5P_HOST_OFF);
+#endif
if (pdata && pdata->phy_exit)
pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
@@ -190,6 +654,18 @@ static void s5p_ehci_shutdown(struct platform_device *pdev)
hcd->driver->shutdown(hcd);
}
+static const struct dev_pm_ops s5p_ehci_pm_ops = {
+ .suspend = s5p_ehci_suspend,
+ .resume = s5p_ehci_resume,
+#ifdef CONFIG_HIBERNATION
+ .freeze = s5p_ehci_suspend,
+ .thaw = s5p_ehci_resume,
+ .restore = s5p_ehci_resume,
+#endif
+ .runtime_suspend = s5p_ehci_runtime_suspend,
+ .runtime_resume = s5p_ehci_runtime_resume,
+};
+
static struct platform_driver s5p_ehci_driver = {
.probe = s5p_ehci_probe,
.remove = __devexit_p(s5p_ehci_remove),
@@ -197,6 +673,7 @@ static struct platform_driver s5p_ehci_driver = {
.driver = {
.name = "s5p-ehci",
.owner = THIS_MODULE,
+ .pm = &s5p_ehci_pm_ops,
}
};
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index 3ffb27f..f3db2b3 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -591,6 +591,24 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
#define ehci_port_speed(ehci, portsc) USB_PORT_STAT_HIGH_SPEED
#endif
+#ifdef CONFIG_HOST_COMPLIANT_TEST
+static struct list_head * qh_urb_transaction (
+ struct ehci_hcd *ehci,
+ struct urb *urb,
+ struct list_head *head,
+ gfp_t flags);
+
+static int submit_async (
+ struct ehci_hcd *ehci,
+ struct urb *urb,
+ struct list_head *qtd_list,
+ gfp_t mem_flags);
+
+static inline void ehci_qtd_free (
+ struct ehci_hcd *ehci,
+ struct ehci_qtd *qtd);
+#endif
+
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PPC_83xx
@@ -737,6 +755,23 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x)
#endif
+/*
+ * Writing to dma coherent memory on ARM may be delayed via L2
+ * writing buffer, so introduce the helper which can flush L2 writing
+ * buffer into memory immediately, especially used to flush ehci
+ * descriptor to memory.
+ * */
+#ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE
+static inline void ehci_sync_mem(void)
+{
+ mb();
+}
+#else
+static inline void ehci_sync_mem(void)
+{
+}
+#endif
+
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PCI
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index f9cf3f0..f428987 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -899,8 +899,11 @@ static void ohci_stop (struct usb_hcd *hcd)
if (quirk_nec(ohci))
flush_work_sync(&ohci->nec_work);
- ohci_usb_reset (ohci);
ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
+ ohci_usb_reset (ohci);
+
+ // flush those writes
+ (void) ohci_readl (ohci, &ohci->regs->intrdisable);
free_irq(hcd->irq, hcd);
hcd->irq = -1;
@@ -1008,6 +1011,11 @@ MODULE_LICENSE ("GPL");
#define PLATFORM_DRIVER ohci_hcd_s3c2410_driver
#endif
+#ifdef CONFIG_USB_OHCI_S5P
+#include "ohci-s5p.c"
+#define PLATFORM_DRIVER ohci_hcd_s5p_driver
+#endif
+
#ifdef CONFIG_USB_OHCI_HCD_OMAP1
#include "ohci-omap.c"
#define OMAP1_PLATFORM_DRIVER ohci_hcd_omap_driver
diff --git a/drivers/usb/host/ohci-s5p.c b/drivers/usb/host/ohci-s5p.c
new file mode 100644
index 0000000..fce68ef
--- /dev/null
+++ b/drivers/usb/host/ohci-s5p.c
@@ -0,0 +1,485 @@
+/* ohci-s5p.c - Driver for USB HOST on Samsung S5P platform device
+ *
+ * Bus Glue for SAMSUNG S5P USB HOST OHCI Controller
+ *
+ * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
+ * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
+ * (C) Copyright 2002 Hewlett-Packard Company
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ * Author: Jingoo Han <jg1.han@samsung.com>
+ *
+ * Based on "ohci-au1xxx.c" by Matt Porter <mporter@kernel.crashing.org>
+ * Modified for SAMSUNG s5p OHCI by Jingoo Han <jg1.han@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/platform_device.h>
+#include <linux/clk.h>
+
+#include <plat/ehci.h>
+#include <plat/usb-phy.h>
+
+#include <mach/board_rev.h>
+
+struct s5p_ohci_hcd {
+ struct device *dev;
+ struct usb_hcd *hcd;
+ struct clk *clk;
+ int power_on;
+};
+
+#ifdef CONFIG_USB_EXYNOS_SWITCH
+int s5p_ohci_port_power_off(struct platform_device *pdev)
+{
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+
+ ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
+ (void)ohci_readl(ohci, &ohci->regs->intrdisable);
+
+ ohci_writel (ohci, RH_HS_LPS, &ohci->regs->roothub.status);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_ohci_port_power_off);
+
+int s5p_ohci_port_power_on(struct platform_device *pdev)
+{
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+
+ ohci_writel (ohci, RH_HS_LPSC, &ohci->regs->roothub.status);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(s5p_ohci_port_power_on);
+#endif
+
+#ifdef CONFIG_PM
+static int ohci_hcd_s5p_drv_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ unsigned long flags;
+ int rc = 0;
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible, bail out if RH has been resumed. Use
+ * the spinlock to properly synchronize with possible pending
+ * RH suspend or resume activity.
+ *
+ * This is still racy as hcd->state is manipulated outside of
+ * any locks =P But that will be a different fix.
+ */
+ spin_lock_irqsave(&ohci->lock, flags);
+ if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) {
+ spin_unlock_irqrestore(&ohci->lock, flags);
+ return -EINVAL;
+ }
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ spin_unlock_irqrestore(&ohci->lock, flags);
+
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+
+ clk_disable(s5p_ohci->clk);
+
+ return rc;
+}
+
+static int ohci_hcd_s5p_drv_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+ int rc = 0;
+
+ clk_enable(s5p_ohci->clk);
+ pm_runtime_resume(&pdev->dev);
+
+ if (pdata && pdata->phy_init)
+ pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+
+ /* if OHCI was off, hcd was removed */
+ if (!s5p_ohci->power_on) {
+ dev_info(dev, "Nothing to do for the device (power off)\n");
+ return 0;
+ }
+
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ ohci_finish_controller_resume(hcd);
+
+ return rc;
+}
+
+#else
+#define ohci_hcd_s5p_drv_suspend NULL
+#define ohci_hcd_s5p_drv_resume NULL
+#endif
+
+#ifdef CONFIG_USB_SUSPEND
+static int ohci_hcd_s5p_drv_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ unsigned long flags;
+ int rc = 0;
+
+ /* Root hub was already suspended. Disable irq emission and
+ * mark HW unaccessible, bail out if RH has been resumed. Use
+ * the spinlock to properly synchronize with possible pending
+ * RH suspend or resume activity.
+ *
+ * This is still racy as hcd->state is manipulated outside of
+ * any locks =P But that will be a different fix.
+ */
+ spin_lock_irqsave(&ohci->lock, flags);
+ if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) {
+ spin_unlock_irqrestore(&ohci->lock, flags);
+ err("Not ready %s", hcd->self.bus_name);
+ return rc;
+ }
+
+ ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable);
+ (void)ohci_readl(ohci, &ohci->regs->intrdisable);
+
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+ spin_unlock_irqrestore(&ohci->lock, flags);
+
+ if (pdata->phy_suspend)
+ pdata->phy_suspend(pdev, S5P_USB_PHY_HOST);
+
+ return rc;
+}
+
+static int ohci_hcd_s5p_drv_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+
+ if (dev->power.is_suspended)
+ return 0;
+
+ if (pdata->phy_resume)
+ pdata->phy_resume(pdev, S5P_USB_PHY_HOST);
+ /* Mark hardware accessible again as we are out of D3 state by now */
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
+
+ ohci_finish_controller_resume(hcd);
+
+ return 0;
+}
+#else
+#define ohci_hcd_s5p_drv_runtime_suspend NULL
+#define ohci_hcd_s5p_drv_runtime_resume NULL
+#endif
+
+static int ohci_s5p_start(struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ int ret;
+
+ ohci_dbg(ohci, "ohci_s5p_start, ohci:%p", ohci);
+
+ ret = ohci_init(ohci);
+ if (ret < 0)
+ return ret;
+
+ ret = ohci_run(ohci);
+ if (ret < 0) {
+ err("can't start %s", hcd->self.bus_name);
+ ohci_stop(hcd);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct hc_driver ohci_s5p_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "s5p OHCI",
+ .hcd_priv_size = sizeof(struct ohci_hcd),
+
+ .irq = ohci_irq,
+ .flags = HCD_MEMORY|HCD_USB11,
+
+ .start = ohci_s5p_start,
+ .stop = ohci_stop,
+ .shutdown = ohci_shutdown,
+
+ .get_frame_number = ohci_get_frame,
+
+ .urb_enqueue = ohci_urb_enqueue,
+ .urb_dequeue = ohci_urb_dequeue,
+ .endpoint_disable = ohci_endpoint_disable,
+
+ .hub_status_data = ohci_hub_status_data,
+ .hub_control = ohci_hub_control,
+#ifdef CONFIG_PM
+ .bus_suspend = ohci_bus_suspend,
+ .bus_resume = ohci_bus_resume,
+#endif
+ .start_port_reset = ohci_start_port_reset,
+};
+
+static ssize_t show_ohci_power(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+
+ return sprintf(buf, "OHCI Power %s\n", (s5p_ohci->power_on) ? "on" : "off");
+}
+
+static ssize_t store_ohci_power(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+ int power_on;
+ int irq;
+ int retval;
+
+ if (sscanf(buf, "%d", &power_on) != 1)
+ return -EINVAL;
+
+ device_lock(dev);
+ if (!power_on && s5p_ohci->power_on) {
+ printk(KERN_DEBUG "%s: OHCI turns off\n", __func__);
+ pm_runtime_forbid(dev);
+ s5p_ohci->power_on = 0;
+ usb_remove_hcd(hcd);
+
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+ } else if (power_on) {
+ printk(KERN_DEBUG "%s: OHCI turns on\n", __func__);
+ if (s5p_ohci->power_on) {
+ pm_runtime_forbid(dev);
+ usb_remove_hcd(hcd);
+ } else {
+ if (pdata && pdata->phy_init)
+ pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ retval = usb_add_hcd(hcd, irq,
+ IRQF_DISABLED | IRQF_SHARED);
+ if (retval < 0) {
+ dev_err(dev, "Power On Fail\n");
+ goto exit;
+ }
+
+ s5p_ohci->power_on = 1;
+ pm_runtime_allow(dev);
+ }
+exit:
+ device_unlock(dev);
+ return count;
+}
+static DEVICE_ATTR(ohci_power, 0664, show_ohci_power, store_ohci_power);
+
+static inline int create_ohci_sys_file(struct ohci_hcd *ohci)
+{
+ return device_create_file(ohci_to_hcd(ohci)->self.controller,
+ &dev_attr_ohci_power);
+}
+
+static inline void remove_ohci_sys_file(struct ohci_hcd *ohci)
+{
+ device_remove_file(ohci_to_hcd(ohci)->self.controller,
+ &dev_attr_ohci_power);
+}
+static int __devinit ohci_hcd_s5p_drv_probe(struct platform_device *pdev)
+{
+ struct s5p_ohci_platdata *pdata;
+ struct s5p_ohci_hcd *s5p_ohci;
+ struct usb_hcd *hcd = NULL;
+ struct ohci_hcd *ohci;
+ struct resource *res;
+ int irq;
+ int err;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data defined\n");
+ return -EINVAL;
+ }
+
+ s5p_ohci = kzalloc(sizeof(struct s5p_ohci_hcd), GFP_KERNEL);
+ if (!s5p_ohci)
+ return -ENOMEM;
+
+ s5p_ohci->dev = &pdev->dev;
+
+ hcd = usb_create_hcd(&ohci_s5p_hc_driver, &pdev->dev,
+ dev_name(&pdev->dev));
+ if (!hcd) {
+ dev_err(&pdev->dev, "Unable to create HCD\n");
+ err = -ENOMEM;
+ goto fail_hcd;
+ }
+
+ s5p_ohci->hcd = hcd;
+ s5p_ohci->clk = clk_get(&pdev->dev, "usbhost");
+
+ if (IS_ERR(s5p_ohci->clk)) {
+ dev_err(&pdev->dev, "Failed to get usbhost clock\n");
+ err = PTR_ERR(s5p_ohci->clk);
+ goto fail_clk;
+ }
+
+ err = clk_enable(s5p_ohci->clk);
+ if (err)
+ goto fail_clken;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get I/O memory\n");
+ err = -ENXIO;
+ goto fail_io;
+ }
+
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = ioremap(res->start, resource_size(res));
+ if (!hcd->regs) {
+ dev_err(&pdev->dev, "Failed to remap I/O memory\n");
+ err = -ENOMEM;
+ goto fail_io;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq) {
+ dev_err(&pdev->dev, "Failed to get IRQ\n");
+ err = -ENODEV;
+ goto fail;
+ }
+
+ if (pdata->phy_init)
+ pdata->phy_init(pdev, S5P_USB_PHY_HOST);
+
+ ohci = hcd_to_ohci(hcd);
+ ohci_hcd_init(ohci);
+
+ err = usb_add_hcd(hcd, irq,
+ IRQF_DISABLED | IRQF_SHARED);
+
+ if (err) {
+ dev_err(&pdev->dev, "Failed to add USB HCD\n");
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, s5p_ohci);
+
+ create_ohci_sys_file(ohci);
+ s5p_ohci->power_on = 1;
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+
+fail:
+ iounmap(hcd->regs);
+fail_io:
+ clk_disable(s5p_ohci->clk);
+fail_clken:
+ clk_put(s5p_ohci->clk);
+fail_clk:
+ usb_put_hcd(hcd);
+fail_hcd:
+ kfree(s5p_ohci);
+ return err;
+}
+
+static int __devexit ohci_hcd_s5p_drv_remove(struct platform_device *pdev)
+{
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+
+ if (pdata && pdata->phy_resume)
+ pdata->phy_resume(pdev, S5P_USB_PHY_HOST);
+
+ usb_remove_hcd(hcd);
+
+ s5p_ohci->power_on = 0;
+ remove_ohci_sys_file(hcd_to_ohci(hcd));
+
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, S5P_USB_PHY_HOST);
+
+ iounmap(hcd->regs);
+
+ clk_disable(s5p_ohci->clk);
+ clk_put(s5p_ohci->clk);
+
+ usb_put_hcd(hcd);
+ kfree(s5p_ohci);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static void ohci_hcd_s5p_drv_shutdown(struct platform_device *pdev)
+{
+ struct s5p_ohci_platdata *pdata = pdev->dev.platform_data;
+ struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_ohci->hcd;
+
+ if (!s5p_ohci->power_on)
+ return;
+
+ if (pdata && pdata->phy_resume)
+ pdata->phy_resume(pdev, S5P_USB_PHY_HOST);
+
+ if (hcd->driver->shutdown)
+ hcd->driver->shutdown(hcd);
+}
+
+static const struct dev_pm_ops ohci_s5p_pm_ops = {
+ .suspend = ohci_hcd_s5p_drv_suspend,
+ .resume = ohci_hcd_s5p_drv_resume,
+#ifdef CONFIG_HIBERNATION
+ .freeze = ohci_hcd_s5p_drv_suspend,
+ .thaw = ohci_hcd_s5p_drv_resume,
+ .restore = ohci_hcd_s5p_drv_resume,
+#endif
+ .runtime_suspend = ohci_hcd_s5p_drv_runtime_suspend,
+ .runtime_resume = ohci_hcd_s5p_drv_runtime_resume,
+};
+
+static struct platform_driver ohci_hcd_s5p_driver = {
+ .probe = ohci_hcd_s5p_drv_probe,
+ .remove = __devexit_p(ohci_hcd_s5p_drv_remove),
+ .shutdown = ohci_hcd_s5p_drv_shutdown,
+ .driver = {
+ .name = "s5p-ohci",
+ .owner = THIS_MODULE,
+ .pm = &ohci_s5p_pm_ops,
+ }
+};
+
+MODULE_ALIAS("platform:s5p-ohci");
diff --git a/drivers/usb/host/shost/Makefile b/drivers/usb/host/shost/Makefile
new file mode 100644
index 0000000..c891a18
--- /dev/null
+++ b/drivers/usb/host/shost/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for USB OTG Host Controller Drivers
+#
+
+obj-$(CONFIG_USB_S3C_OTG_HOST) += shost_hcd.o
+
+shost_hcd-objs += shost_driver.o
+shost_hcd-objs += shost_scheduler.o
+shost_hcd-objs += shost_transferchecker.o
diff --git a/drivers/usb/host/shost/shost.h b/drivers/usb/host/shost/shost.h
new file mode 100644
index 0000000..6774667
--- /dev/null
+++ b/drivers/usb/host/shost/shost.h
@@ -0,0 +1,56 @@
+#ifndef _SHOST_H
+#define _SHOST_H
+
+
+#include <linux/usb.h>
+#include <linux/errno.h>
+#include <linux/wakelock.h>
+#include <plat/s5p-otghost.h>
+
+
+#include "shost_debug.h"
+#include "shost_list.h"
+
+#include "shost_const.h"
+#include "shost_errorcode.h"
+#include "shost_regs.h"
+#include "shost_struct.h"
+
+#include "shost_kal.h"
+#include "shost_mem.h"
+#include "shost_oci.h"
+
+#include "shost_scheduler.h"
+#include "shost_transfer.h"
+
+/* #ifdef CONFIG_MACH_C1 GB */
+#if 0
+ #include <plat/regs-otg.h>
+ #define OTG_CLOCK S5P_CLKGATE_IP_FSYS
+ #define OTG_PHY_CONTROL S5P_USBOTG_PHY_CONTROL
+ #define OTG_PHYPWR S3C_USBOTG_PHYPWR
+ #define OTG_PHYCLK S3C_USBOTG_PHYCLK
+ #define OTG_RSTCON S3C_USBOTG_RSTCON
+ #define S3C_VA_HSOTG S3C_VA_OTG
+#else /* ICS */
+ #include <mach/regs-usb-phy.h>
+ #define OTG_CLOCK EXYNOS4_CLKGATE_IP_FSYS
+ #define OTG_PHY_CONTROL S5P_USBOTG_PHY_CONTROL
+ #define OTG_PHYPWR EXYNOS4_PHYPWR
+ #define OTG_PHYCLK EXYNOS4_PHYCLK
+ #define OTG_RSTCON EXYNOS4_RSTCON
+ #define IRQ_OTG IRQ_USB_HSOTG
+ #define S3C_VA_OTG S3C_VA_HSOTG
+#endif
+
+/* transferchecker-common.c
+ * called by isr
+ */
+void do_transfer_checker(struct sec_otghost *otghost);
+void otg_print_registers(void);
+
+#ifdef CONFIG_USB_HOST_NOTIFY
+#undef CONFIG_USB_HOST_NOTIFY
+#endif
+
+#endif
diff --git a/drivers/usb/host/shost/shost_const.h b/drivers/usb/host/shost/shost_const.h
new file mode 100644
index 0000000..8ba1494
--- /dev/null
+++ b/drivers/usb/host/shost/shost_const.h
@@ -0,0 +1,140 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : s3c-otg-common-const.h
+ * [Description] : The Header file defines constants
+ * to be used at sub-modules of S3C6400HCD.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/03
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created s3c-otg-common-const.h file and defines some constants.
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _CONST_TYPE_DEF_H_
+#define _CONST_TYPE_DEF_H_
+
+/**
+ * @def OTG_PORT_NUMBER
+ *
+ * @brief write~ description
+ *
+ * describe in detail
+ */
+#define OTG_PORT_NUMBER 0
+
+/* Defines Stages of Control Transfer */
+#define SETUP_STAGE 1
+#define DATA_STAGE 2
+#define STATUS_STAGE 3
+#define COMPLETE_STAGE 4
+
+/* Defines Direction of Endpoint */
+#define EP_IN 1
+#define EP_OUT 0
+
+/* Define speed of USB Device */
+#define LOW_SPEED_OTG 2
+#define FULL_SPEED_OTG 1
+#define HIGH_SPEED_OTG 0
+#define SUPER_SPEED_OTG 3
+
+/* Define multiple count of packet in periodic transfer. */
+#define MULTI_COUNT_ZERO 0
+#define MULTI_COUNT_ONE 1
+#define MULTI_COUNT_TWO 2
+
+/* Define USB Transfer Types. */
+#define CONTROL_TRANSFER 0
+#define ISOCH_TRANSFER 1
+#define BULK_TRANSFER 2
+#define INT_TRANSFER 3
+
+#define BULK_TIMEOUT 300
+
+/* Defines PID */
+#define DATA0 0
+#define DATA1 2
+#define DATA2 1
+#define MDATA 3
+#define SETUP 3
+
+/* Defines USB Transfer Request Size on USB2.0 */
+#define USB_20_STAND_DEV_REQUEST_SIZE 8
+/* Define Max Channel Number */
+#define MAX_CH_NUMBER 16
+/* Define Channel Number */
+#define CH_0 0
+#define CH_1 1
+#define CH_2 2
+#define CH_3 3
+#define CH_4 4
+#define CH_5 5
+#define CH_6 6
+#define CH_7 7
+#define CH_8 8
+#define CH_9 9
+#define CH_10 10
+#define CH_11 11
+#define CH_12 12
+#define CH_13 13
+#define CH_14 14
+#define CH_15 15
+#define CH_NONE 20
+
+/* define the Constant for result of processing the USB Transfer. */
+#define RE_TRANSMIT 1
+#define RE_SCHEDULE 2
+#define DE_ALLOCATE 3
+#define NO_ACTION 4
+
+/* define the threshold value to retransmit USB Transfer */
+#define RETRANSMIT_THRESHOLD 2
+
+/* define the maximum size of data to be tranferred through channel. */
+#define MAX_CH_TRANSFER_SIZE 65536 /* 65535 */
+
+/* define Max Frame Number which Synopsys OTG suppports. */
+#define MAX_FRAME_NUMBER 0x3FFF
+/* Channel Interrupt Status */
+#define CH_STATUS_DataTglErr (0x1<<10)
+#define CH_STATUS_FrmOvrun (0x1<<9)
+#define CH_STATUS_BblErr (0x1<<8)
+#define CH_STATUS_XactErr (0x1<<7)
+#define CH_STATUS_NYET (0x1<<6)
+#define CH_STATUS_ACK (0x1<<5)
+#define CH_STATUS_NAK (0x1<<4)
+#define CH_STATUS_STALL (0x1<<3)
+#define CH_STATUS_AHBErr (0x1<<2)
+#define CH_STATUS_ChHltd (0x1<<1)
+#define CH_STATUS_XferCompl (0x1<<0)
+#define CH_STATUS_ALL 0x7FF
+
+/* Define USB Transfer Flag.. */
+#define USB_TRANS_FLAG_NOT_SHORT URB_SHORT_NOT_OK
+#define USB_TRANS_FLAG_ISO_ASYNCH URB_ISO_ASAP
+
+#define HFNUM_MAX_FRNUM 0x3FFF
+#define SCHEDULE_SLOT 10
+
+
+#endif
+
+
diff --git a/drivers/usb/host/shost/shost_debug.h b/drivers/usb/host/shost/shost_debug.h
new file mode 100644
index 0000000..dd67b98
--- /dev/null
+++ b/drivers/usb/host/shost/shost_debug.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-debug.c
+ * @brief It provides debug functions for display message \n
+ * @version
+ * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Creating the initial version of this code \n
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Optimizing for performance \n
+ * @see None
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _SHOST_DEBUG_H
+#define _SHOST_DEBUG_H
+
+#define OTG_DEBUG
+
+#ifdef OTG_DEBUG
+
+#define OTG_DBG_OTGHCDI_DRIVER 1
+#define OTG_DBG_OTGHCDI_HCD 0
+#define OTG_DBG_OTGHCDI_KAL 0
+#define OTG_DBG_OTGHCDI_LIST 0
+#define OTG_DBG_OTGHCDI_MEM 0
+#define OTG_DBG_OTGHCDI_IRQ 0
+
+#define OTG_DBG_TRANSFER 0
+#define OTG_DBG_SCHEDULE 0
+#define OTG_DBG_SCHEDULE_ED 0
+#define OTG_DBG_OCI 0
+#define OTG_DBG_DONETRASF 0
+#define OTG_DBG_ISR 0
+#define OTG_DBG_ROOTHUB 0
+
+
+#include <linux/kernel.h> /* for printk */
+
+#define otg_err(is_active, msg, args...) \
+ do { \
+ if ((is_active) == true) {\
+ pr_err("OTG_ERR %s(%d): " msg, \
+ __func__ , __LINE__, ##args); \
+ } \
+ } while (0)
+
+#define otg_dbg(is_active, msg, args...) \
+ do { \
+ if ((is_active) == true) { \
+ pr_info("OTG %s(%d): " msg, \
+ __func__, __LINE__, ##args); \
+ } \
+ } while (0)
+
+#else /* OTG_DEBUG */
+
+#define otg_err(is_active, msg...) do {} while (0)
+#define otg_dbg(is_active, msg...) do {} while (0)
+
+#endif
+
+
+#endif /* SHOST_DEBUG_H */
diff --git a/drivers/usb/host/shost/shost_driver.c b/drivers/usb/host/shost/shost_driver.c
new file mode 100644
index 0000000..f10136c
--- /dev/null
+++ b/drivers/usb/host/shost/shost_driver.c
@@ -0,0 +1,450 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-driver.c
+ * @brief It provides functions related with module for OTGHCD driver.
+ * @version
+ * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Creating the initial version of this code
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Modifying for successful rmmod & disconnecting
+ * @see None
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h> /* for SA_SHIRQ */
+#include <mach/map.h> /* address for smdk */
+#include <linux/dma-mapping.h> /* dma_alloc_coherent */
+#include <linux/ioport.h> /* request_mem_request ... */
+#include <asm/irq.h> /* for IRQ_OTG */
+#include <linux/clk.h>
+
+
+#include "shost.h"
+
+
+static inline struct sec_otghost *hcd_to_sec_otghost(struct usb_hcd *hcd)
+{
+ return (struct sec_otghost *)(hcd->hcd_priv);
+}
+static inline struct usb_hcd *sec_otghost_to_hcd(struct sec_otghost *otghost)
+{
+ return container_of((void *) otghost, struct usb_hcd, hcd_priv);
+}
+
+
+#include "shost_oci.c"
+#include "shost_transfer.c"
+#include "shost_roothub.c"
+#include "shost_hcd.c"
+
+volatile u8 *g_pUDCBase;
+struct usb_hcd *g_pUsbHcd;
+
+static const char gHcdName[] = "EMSP_OTG_HCD";
+static struct platform_device *g_pdev;
+
+
+static void otg_power_work(struct work_struct *work)
+{
+ struct sec_otghost *otghost = container_of(work,
+ struct sec_otghost, work);
+ struct sec_otghost_data *hdata = otghost->otg_data;
+
+ if (hdata && hdata->set_pwr_cb) {
+ pr_info("otg power off - don't turn off the power\n");
+ hdata->set_pwr_cb(0);
+#ifdef CONFIG_USB_HOST_NOTIFY
+ if (hdata->host_notify_cb)
+ hdata->host_notify_cb(NOTIFY_HOST_OVERCURRENT);
+#endif
+ } else {
+ otg_err(true, "invalid otghost data\n");
+ }
+}
+
+static int s5pc110_mapping_resources(struct platform_device *pdev)
+{
+ g_pUsbHcd->rsrc_start = pdev->resource[0].start;
+ g_pUsbHcd->rsrc_len = pdev->resource[0].end -
+ pdev->resource[0].start + 1;
+
+ if (!request_mem_region(g_pUsbHcd->rsrc_start,
+ g_pUsbHcd->rsrc_len, gHcdName)) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "failed to request_mem_region\n");
+ return -EBUSY;
+ }
+
+ pr_info("otg rsrc_start %llu, ren %llu\n",
+ g_pUsbHcd->rsrc_start,
+ g_pUsbHcd->rsrc_len);
+
+ pr_info("otg regs : %p\n", S3C_VA_HSOTG);
+
+ /* Physical address => Virtual address */
+ g_pUsbHcd->regs = S3C_VA_HSOTG;
+ g_pUDCBase = (u8 *)g_pUsbHcd->regs;
+
+ return 0;
+}
+
+static void s5pc110_prevent_suspend(struct usb_device *rhdev)
+{
+ dev_info(&rhdev->dev, "otg host do not enter suspend.\n");
+ pm_runtime_disable(&rhdev->dev);
+}
+
+static int s5pc110_start_otg(u32 regs)
+{
+ int ret_val = 0;
+ u32 reg_val = 0;
+ struct platform_device *pdev = g_pdev;
+ struct sec_otghost *otghost = NULL;
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+
+ pr_info("%s + regs=0x%x\n", __func__, regs);
+
+ /* 1. hcd */
+ g_pUsbHcd = usb_create_hcd(&s5pc110_otg_hc_driver, &pdev->dev,
+ "s3cotg");/*pdev->dev.bus_id*/
+ if (g_pUsbHcd == NULL) {
+ otg_err(1, "failed to usb_create_hcd\n");
+ return -ENOMEM;
+ }
+ g_pUsbHcd->self.otg_port = 1;
+
+ /* sec_otghost */
+ otghost = hcd_to_sec_otghost(g_pUsbHcd);
+ if (otghost == NULL) {
+ otg_err(true, "failed to get otghost hcd\n");
+ ret_val = USB_ERR_FAIL;
+ goto err_out_create_hcd;
+ }
+ otghost->otg_data = otg_data;
+
+ /* 2. wake lock */
+ wake_lock_init(&otghost->wake_lock, WAKE_LOCK_SUSPEND, "usb_otg");
+ wake_lock(&otghost->wake_lock);
+
+ /* base address */
+ if (!regs) {
+ pr_info("otg mapping hcd resource\n");
+ ret_val = s5pc110_mapping_resources(pdev);
+ if (ret_val)
+ goto err_out_create_hcd;
+ } else
+ g_pUDCBase = (u8 *)regs;
+
+ pr_info("otg g_pUDCBase 0x%p\n", g_pUDCBase);
+
+ /* 3. workqueue */
+ INIT_WORK(&otghost->work, otg_power_work);
+ otghost->wq = create_singlethread_workqueue("sec_otghostd");
+
+ /* 4. phy */
+ ret_val = otg_hcd_init_modules(otghost);
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "failed to otg_hcd_init_modules\n");
+ ret_val = USB_ERR_FAIL;
+ goto err_out_create_hcd;
+ }
+
+ /**
+ * Attempt to ensure this device is really a s5pc110 USB-OTG Controller.
+ * Read and verify the SNPSID register contents. The value should be
+ * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX".
+ */
+ reg_val = read_reg_32(0x40);
+ pr_info("otg reg 0x40 = %x\n", reg_val);
+ if ((reg_val & 0xFFFFF000) != 0x4F542000) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Bad value for SNPSID: 0x%x\n", reg_val);
+ ret_val = -EINVAL;
+ goto err_out_create_hcd_init;
+ }
+
+#ifdef CONFIG_USB_SEC_WHITELIST
+ if (otg_data->sec_whlist_table_num)
+ g_pUsbHcd->sec_whlist_table_num =
+ otg_data->sec_whlist_table_num;
+#endif
+
+ /* 5. hcd
+ * Finish generic HCD initialization and start the HCD. This function
+ * allocates the DMA buffer pool, registers the USB bus, requests the
+ * IRQ line, and calls s5pc110_otghcd_start method.
+ */
+ ret_val = usb_add_hcd(g_pUsbHcd,
+ pdev->resource[1].start, IRQF_DISABLED);
+ if (ret_val < 0) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Failed to add hcd driver\n");
+ goto err_out_create_hcd_init;
+ }
+
+ s5pc110_prevent_suspend(g_pUsbHcd->self.root_hub);
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "OTG HCD Initialized HCD, bus=%s, usbbus=%d\n",
+ "C110 OTG Controller", g_pUsbHcd->self.busnum);
+
+ /* otg_print_registers(); */
+ pr_info("%s -\n", __func__);
+
+ return USB_ERR_SUCCESS;
+
+err_out_create_hcd_init:
+ otg_hcd_deinit_modules(otghost);
+ if (!regs)
+ release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len);
+
+err_out_create_hcd:
+ usb_put_hcd(g_pUsbHcd);
+
+ return ret_val;
+}
+
+static int s5pc110_stop_otg(void)
+{
+ struct sec_otghost *otghost = NULL;
+ struct sec_otghost_data *otgdata = NULL;
+
+ pr_info("%s +\n", __func__);
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s5pc110_stop_otg\n");
+
+ otghost = hcd_to_sec_otghost(g_pUsbHcd);
+
+ otg_hcd_deinit_modules(otghost);
+
+ /* 5. hcd */
+ usb_remove_hcd(g_pUsbHcd);
+#if 1
+ if (g_pUDCBase == S3C_VA_HSOTG) {
+ pr_info("otg release_mem_region\n");
+ release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len);
+ }
+#endif
+ /* 4. phy */
+ otgdata = otghost->otg_data;
+ if (otgdata && otgdata->phy_exit && otgdata->pdev) {
+ pr_info("otg phy_off\n");
+ otgdata->phy_exit(0);
+ clk_disable(otgdata->clk);
+ }
+
+ /* 3. workqueue */
+ destroy_workqueue(otghost->wq);
+
+ /* 2. wake lock */
+ wake_unlock(&otghost->wake_lock);
+ wake_lock_destroy(&otghost->wake_lock);
+
+ /* 1. hcd */
+ usb_put_hcd(g_pUsbHcd);
+
+ pr_info("%s -\n", __func__);
+
+ return 0;
+}
+
+/**
+ * static int s5pc110_otg_drv_probe (struct platform_device *pdev)
+ *
+ * @brief probe function of OTG hcd platform_driver
+ *
+ * @param [in] pdev : pointer of platform_device of otg hcd platform_driver
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If fail
+ * @remark
+ * it allocates resources of it and call other modules' init function.
+ * then call usb_create_hcd, usb_add_hcd, s5pc110_otghcd_start functions
+ */
+
+static int s5pc110_otg_drv_probe(struct platform_device *pdev)
+{
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+ g_pdev = pdev;
+
+ otg_data->clk = clk_get(&pdev->dev, "usbotg");
+
+ if (IS_ERR(otg_data->clk)) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Failed to get clock\n");
+ return PTR_RET(otg_data->clk);
+ }
+
+ pr_info("otg host_probe start %p\n", s5pc110_start_otg);
+ otg_data->start = s5pc110_start_otg;
+ otg_data->stop = s5pc110_stop_otg;
+ otg_data->pdev = pdev;
+
+ return 0;
+}
+
+
+/**
+ * static int s5pc110_otg_drv_remove (struct platform_device *dev)
+ *
+ * @brief remove function of OTG hcd platform_driver
+ *
+ * @param [in] pdev : pointer of platform_device of otg hcd platform_driver
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If fail
+ * @remark
+ * This function is called when the otg device unregistered with the
+ * s5pc110_otg_driver. This happens, for example, when the rmmod command is
+ * executed. The device may or may not be electrically present. If it is
+ * present, the driver stops device processing. Any resources used on behalf
+ * of this device are freed.
+ */
+
+static int s5pc110_otg_drv_remove(struct platform_device *pdev)
+{
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+
+ clk_put(otg_data->clk);
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * @struct s5pc110_otg_driver
+ *
+ * @brief
+ * This structure defines the methods to be called by a bus driver
+ * during the lifecycle of a device on that bus. Both drivers and
+ * devices are registered with a bus driver. The bus driver matches
+ * devices to drivers based on information in the device and driver
+ * structures.
+ *
+ * The probe function is called when the bus driver matches a device
+ * to this driver. The remove function is called when a device is
+ * unregistered with the bus driver.
+ */
+struct platform_driver s5pc110_otg_driver = {
+ .probe = s5pc110_otg_drv_probe,
+ .remove = s5pc110_otg_drv_remove,
+/* .shutdown = usb_hcd_platform_shutdown, */
+ .driver = {
+ .name = "s3c_otghcd",
+ .owner = THIS_MODULE,
+ },
+};
+
+/**
+ * static int __init s5pc110_otg_module_init(void)
+ *
+ * @brief module_init function
+ *
+ * @return it returns result of platform_driver_register
+ * @remark
+ * This function is called when the s5pc110_otg_driver is installed with the
+ * insmod command. It registers the s5pc110_otg_driver structure with the
+ * appropriate bus driver. This will cause the s5pc110_otg_driver_probe function
+ * to be called. In addition, the bus driver will automatically expose
+ * attributes defined for the device and driver in the special sysfs file
+ * system.
+ */
+static int __init s5pc110_otg_module_init(void)
+{
+ int ret_val = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "s3c_otg_module_init\n");
+
+ ret_val = platform_driver_register(&s5pc110_otg_driver);
+ if (ret_val < 0) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "platform_driver_register\n");
+ }
+ return ret_val;
+}
+
+/**
+ * static void __exit s5pc110_otg_module_exit(void)
+ *
+ * @brief module_exit function
+ *
+ * @remark
+ * This function is called when the driver is removed from the kernel
+ * with the rmmod command. The driver unregisters itself with its bus
+ * driver.
+ */
+static void __exit s5pc110_otg_module_exit(void)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "s3c_otg_module_exit\n");
+ platform_driver_unregister(&s5pc110_otg_driver);
+}
+
+/* for debug */
+void otg_print_registers(void)
+{
+ /* USB PHY Control Registers */
+
+ pr_info("otg clock = %s\n",
+ (readl(OTG_CLOCK) & (1<<13)) ? "ON" : "OFF");
+ pr_info("otg USB_CONTROL = 0x%x.\n", readl(OTG_PHY_CONTROL));
+ pr_info("otg UPHYPWR = 0x%x.\n", readl(OTG_PHYPWR));
+ pr_info("otg UPHYCLK = 0x%x.\n", readl(OTG_PHYCLK));
+ pr_info("otg URSTCON = 0x%x.\n", readl(OTG_RSTCON));
+
+ /* OTG LINK Core registers (Core Global Registers) */
+ pr_info("otg GOTGCTL = 0x%x.\n", read_reg_32(GOTGCTL));
+ pr_info("otg GOTGINT = 0x%x.\n", read_reg_32(GOTGINT));
+ pr_info("otg GAHBCFG = 0x%x.\n", read_reg_32(GAHBCFG));
+ pr_info("otg GUSBCFG = 0x%x.\n", read_reg_32(GUSBCFG));
+ pr_info("otg GINTSTS = 0x%x.\n", read_reg_32(GINTSTS));
+ pr_info("otg GINTMSK = 0x%x.\n", read_reg_32(GINTMSK));
+
+ /* Host Mode Registers */
+ pr_info("otg HCFG = 0x%x.\n", read_reg_32(HCFG));
+ pr_info("otg HPRT = 0x%x.\n", read_reg_32(HPRT));
+ pr_info("otg HFIR = 0x%x.\n", read_reg_32(HFIR));
+
+ /* Synopsys ID */
+ pr_info("otg GSNPSID = 0x%x.\n", read_reg_32(GSNPSID));
+
+ /* HWCFG */
+ pr_info("otg GHWCFG1 = 0x%x.\n", read_reg_32(GHWCFG1));
+ pr_info("otg GHWCFG2 = 0x%x.\n", read_reg_32(GHWCFG2));
+ pr_info("otg GHWCFG3 = 0x%x.\n", read_reg_32(GHWCFG3));
+ pr_info("otg GHWCFG4 = 0x%x.\n", read_reg_32(GHWCFG4));
+
+ /* PCGCCTL */
+ pr_info("otg PCGCCTL = 0x%x.\n", read_reg_32(PCGCCTL));
+}
+
+late_initcall(s5pc110_otg_module_init);
+module_exit(s5pc110_otg_module_exit);
+
+MODULE_DESCRIPTION("OTG USB HOST controller driver");
+MODULE_AUTHOR("SAMSUNG / System LSI / EMSP");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/host/shost/shost_errorcode.h b/drivers/usb/host/shost/shost_errorcode.h
new file mode 100644
index 0000000..f52b606
--- /dev/null
+++ b/drivers/usb/host/shost/shost_errorcode.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : s3c-otg-common-errorcode.h
+ * [Description] : The Header file defines Error Codes
+ * to be used at sub-modules of S3C6400HCD.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/03
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file.
+ * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com )
+ * - add HCD error code
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _ERRORCODE_H
+#define _ERRORCODE_H
+
+
+/* General USB Error Code.*/
+#define USB_ERR_SUCCESS 0
+#define USB_ERR_FAIL (-1)
+
+#define USB_ERR_NO 1
+
+#define USB_ERR_NO_ENTITY (-2)
+
+/* S3CTransfer Error Code */
+#define USB_ERR_NODEV (-ENODEV)
+#define USB_ERR_NOMEM (-ENOMEM)
+#define USB_ERR_NOSPACE (-ENOSPC)
+#define USB_ERR_NOIO (-EIO)
+
+/* OTG-HCD error code */
+#define USB_ERR_NOELEMENT (-ENOENT)
+#define USB_ERR_ESHUTDOWN (-ESHUTDOWN) /* unplug */
+#define USB_ERR_DEQUEUED (-ECONNRESET) /* unlink */
+
+
+/* S3CScheduler Error Code */
+#define USB_ERR_ALREADY_EXIST (-1)
+#define USB_ERR_NO_RESOURCE (-2)
+#define USB_ERR_NO_CHANNEL (-3)
+#define USB_ERR_NO_BANDWIDTH (-4)
+#define USB_ERR_ALL_RESROUCE (-5)
+
+/************************************************
+ *Defines the USB Error Status Code of USB Transfer.
+ ************************************************/
+
+#define USB_ERR_STATUS_COMPLETE 0
+#define USB_ERR_STATUS_INPROGRESS (-EINPROGRESS)
+#define USB_ERR_STATUS_CRC (-EILSEQ)
+#define USB_ERR_STATUS_XACTERR (-EPROTO)
+#define USB_ERR_STATUS_STALL (-EPIPE)
+#define USB_ERR_STATUS_BBLERR (-EOVERFLOW)
+#define USB_ERR_STATUS_AHBERR (-EIO)
+#define USB_ERR_STATUS_FRMOVRUN_OUT (-ENOSR)
+#define USB_ERR_STATUS_FRMOVRUN_IN (-ECOMM)
+#define USB_ERR_STATUS_SHORTREAD (-EREMOTEIO)
+
+
+#endif
+
diff --git a/drivers/usb/host/shost/shost_hcd.c b/drivers/usb/host/shost/shost_hcd.c
new file mode 100644
index 0000000..f06b237
--- /dev/null
+++ b/drivers/usb/host/shost/shost_hcd.c
@@ -0,0 +1,912 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-hcd.c
+ * @brief implementation of structure hc_drive
+ * @version
+ * -# Jun 11,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Creating the initial version of this code
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Modifying for successful rmmod & disconnecting
+ * @see None
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+/*Internal function of isr */
+static void process_port_intr(struct usb_hcd *hcd)
+{
+ hprt_t hprt; /* by ss1, clear_hprt; */
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ hprt.d32 = read_reg_32(HPRT);
+
+ otg_dbg(OTG_DBG_ISR, "Port Interrupt() : HPRT = 0x%x\n", hprt.d32);
+
+ if (hprt.b.prtconndet) {
+ otg_dbg(true, "detect connection");
+
+ otghost->port_flag.b.port_connect_status_change = 1;
+
+ if (hprt.b.prtconnsts)
+ otghost->port_flag.b.port_connect_status = 1;
+
+ /* wake_lock(&otghost->wake_lock); */
+ }
+
+
+ if (hprt.b.prtenchng) {
+ otg_dbg(true, "port enable/disable changed\n");
+ otghost->port_flag.b.port_enable_change = 1;
+ }
+
+ if (hprt.b.prtovrcurrchng) {
+ otg_dbg(true, "over current condition is changed\n");
+
+ if (hprt.b.prtovrcurract) {
+ otg_dbg(true, "port_over_current_change = 1\n");
+ otghost->port_flag.b.port_over_current_change = 1;
+
+ } else {
+ otghost->port_flag.b.port_over_current_change = 0;
+ }
+ /* defer otg power control into a kernel thread */
+ queue_work(otghost->wq, &otghost->work);
+ }
+
+ hprt.b.prtena = 0; /* prtena를 writeclear시키면 안됨. */
+ /* hprt.b.prtpwr = 0; */
+ hprt.b.prtrst = 0;
+ hprt.b.prtconnsts = 0;
+
+ write_reg_32(HPRT, hprt.d32);
+}
+
+/**
+ * void otg_handle_interrupt(void)
+ *
+ * @brief Main interrupt processing routine
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+
+static inline void otg_handle_interrupt(struct usb_hcd *hcd)
+{
+ gintsts_t clearIntr = {.d32 = 0};
+ gintsts_t gintsts = {.d32 = 0};
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ gintsts.d32 = read_reg_32(GINTSTS) & read_reg_32(GINTMSK);
+
+ otg_dbg(OTG_DBG_ISR, "otg_handle_interrupt - GINTSTS=0x%8x\n",
+ gintsts.d32);
+
+ if (gintsts.b.wkupintr) {
+ otg_dbg(true, "Wakeup Interrupt\n");
+ clearIntr.b.wkupintr = 1;
+ }
+
+ if (gintsts.b.disconnect) {
+ otg_dbg(true, "Disconnect Interrupt\n");
+ otghost->port_flag.b.port_connect_status_change = 1;
+ otghost->port_flag.b.port_connect_status = 0;
+ clearIntr.b.disconnect = 1;
+ /*
+ wake_unlock(&otghost->wake_lock);
+ */
+ }
+
+ if (gintsts.b.conidstschng) {
+ otg_dbg(OTG_DBG_ISR, "Connect ID Status Change Interrupt\n");
+ clearIntr.b.conidstschng = 1;
+ oci_init_mode();
+ }
+
+ if (gintsts.b.hcintr) {
+ /* Mask Channel Interrupt to prevent generating interrupt */
+ otg_dbg(OTG_DBG_ISR, "Channel Interrupt\n");
+
+ if (!otghost->ch_halt)
+ do_transfer_checker(otghost);
+ }
+
+ if (gintsts.b.portintr) {
+ /* Read Only */
+ otg_dbg(true, "Port Interrupt\n");
+ process_port_intr(hcd);
+ }
+
+
+ if (gintsts.b.otgintr) {
+ /* Read Only */
+ otg_dbg(OTG_DBG_ISR, "OTG Interrupt\n");
+ }
+
+ if (gintsts.b.sofintr) {
+ /* otg_dbg(OTG_DBG_ISR, "SOF Interrupt\n"); */
+ do_schedule(otghost);
+ clearIntr.b.sofintr = 1;
+ }
+
+ if (gintsts.b.modemismatch) {
+ otg_dbg(OTG_DBG_ISR, "Mode Mismatch Interrupt\n");
+ clearIntr.b.modemismatch = 1;
+ }
+ update_reg_32(GINTSTS, clearIntr.d32);
+}
+
+
+
+/**
+ * otg_hcd_init_modules(struct sec_otghost *otghost)
+ *
+ * @brief call other modules' init functions
+ *
+ * @return PASS : If success
+ * FAIL : If fail
+ */
+static int otg_hcd_init_modules(struct sec_otghost *otghost)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_init_modules\n");
+
+ spin_lock_init(&otghost->lock);
+
+ init_transfer();
+ init_scheduler();
+ oci_init(otghost);
+
+ return USB_ERR_SUCCESS;
+};
+
+/**
+ * void otg_hcd_deinit_modules(struct sec_otghost *otghost)
+ *
+ * @brief call other modules' de-init functions
+ *
+ * @return PASS : If success
+ * FAIL : If fail
+ */
+static void otg_hcd_deinit_modules(struct sec_otghost *otghost)
+{
+ unsigned long spin_lock_flag = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_deinit_modules\n");
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ deinit_transfer(otghost);
+
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+}
+
+/**
+ * irqreturn_t (*s5pc110_otghcd_irq) (struct usb_hcd *hcd)
+ *
+ * @brief interrupt handler of otg irq
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ *
+ * @return IRQ_HANDLED
+ */
+static irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd)
+{
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_OTGHCDI_IRQ, "s5pc110_otghcd_irq\n");
+
+ spin_lock(&otghost->lock);
+ otg_handle_interrupt(hcd);
+ spin_unlock(&otghost->lock);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * int s5pc110_otghcd_start(struct usb_hcd *hcd)
+ *
+ * @brief initialize and start otg hcd
+ *
+ * @param [in] usb_hcd_p : pointer of usb_hcd
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If call fail
+ */
+static int s5pc110_otghcd_start(struct usb_hcd *usb_hcd_p)
+{
+ struct usb_bus *usb_bus_p;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start\n");
+
+ usb_bus_p = hcd_to_bus(usb_hcd_p);
+
+ /* Initialize and connect root hub if one is not already attached */
+ if (usb_bus_p->root_hub) {
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "OTG HCD Has Root Hub\n");
+
+ /* Inform the HUB driver to resume. */
+ otg_usbcore_resume_roothub();
+ } else {
+ otg_err(OTG_DBG_OTGHCDI_HCD,
+ "OTG HCD Does Not Have Root Hub\n");
+ return USB_ERR_FAIL;
+ }
+
+/* #ifdef CONFIG_MACH_C1 */
+#if 0
+ usb_hcd_p->poll_rh = 1;
+#else
+ set_bit(HCD_FLAG_POLL_RH, &usb_hcd_p->flags);
+#endif
+ usb_hcd_p->uses_new_polling = 1;
+ usb_hcd_p->has_tt = 1;
+
+ /* init bus state before enable irq */
+ usb_hcd_p->state = HC_STATE_RUNNING;
+
+ oci_start(); /* enable irq */
+
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * void s5pc110_otghcd_stop(struct usb_hcd *hcd)
+ *
+ * @brief deinitialize and stop otg hcd
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ *
+ */
+static void s5pc110_otghcd_stop(struct usb_hcd *hcd)
+{
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_stop\n");
+
+ otg_hcd_deinit_modules(otghost);
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ oci_stop();
+
+ root_hub_feature(hcd, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL);
+
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+}
+
+/**
+ * void s5pc110_otghcd_shutdown(struct usb_hcd *hcd)
+ *
+ * @brief shutdown otg hcd
+ *
+ * @param [in] usb_hcd_p : pointer of usb_hcd
+ *
+ */
+static void s5pc110_otghcd_shutdown(struct usb_hcd *usb_hcd_p)
+{
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(usb_hcd_p);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_shutdown\n");
+ otg_hcd_deinit_modules(otghost);
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ oci_stop();
+ root_hub_feature(usb_hcd_p, 0, ClearPortFeature,
+ USB_PORT_FEAT_POWER, NULL);
+
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+
+ free_irq(IRQ_OTG, usb_hcd_p);
+ usb_hcd_p->state = HC_STATE_HALT;
+ otg_usbcore_hc_died();
+}
+
+
+/**
+ * int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd)
+ *
+ * @brief get currnet frame number
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ *
+ * @return ret : frame number
+ */
+static int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd)
+{
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+ unsigned long spin_lock_flag = 0;
+ int ret = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_get_frame_number\n");
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+ ret = oci_get_frame_num();
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+
+ return ret;
+}
+
+int compare_ed(struct sec_otghost *otghost, void *hcpriv, struct urb *urb)
+{
+ struct ed *ped = NULL;
+ int ret = 0;
+ u32 update = 0;
+
+ if (!hcpriv)
+ return ret;
+
+ ped = (struct ed *)hcpriv;
+
+ if (ped->ed_desc.device_addr != usb_pipedevice(urb->pipe)) {
+ ped->ed_desc.device_addr = usb_pipedevice(urb->pipe);
+ update |= 1 << 1;
+ }
+
+ if (ped->ed_desc.endpoint_num != usb_pipeendpoint(urb->pipe)) {
+ ped->ed_desc.endpoint_num = usb_pipeendpoint(urb->pipe);
+ update |= 1 << 2;
+ }
+
+ if (ped->ed_desc.is_ep_in != (usb_pipein(urb->pipe) ? 1 : 0)) {
+ ped->ed_desc.is_ep_in = usb_pipein(urb->pipe) ? 1 : 0;
+ update |= 1 << 3;
+ }
+
+ if (ped->ed_desc.max_packet_size !=
+ usb_maxpacket(urb->dev, urb->pipe,
+ !(usb_pipein(urb->pipe)))) {
+ update |= 1 << 4;
+ }
+
+ if (update)
+ otg_dbg(1, "update ed %d (0x%x)\n", update, update);
+
+ return ret;
+}
+
+/**
+ * int s5pc110_otghcd_urb_enqueue()
+ *
+ * @brief enqueue a urb to otg hcd
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ * [in] ep : pointer of usb_host_endpoint
+ * [in] urb : pointer of urb
+ * [in] mem_flags : type of gfp_t
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If call fail
+ */
+static int s5pc110_otghcd_urb_enqueue(struct usb_hcd *hcd,
+ struct urb *urb,
+ gfp_t mem_flags)
+{
+ int ret_val = 0;
+ u32 trans_flag = 0;
+ u32 return_td_addr = 0;
+ u8 dev_speed, ed_type = 0, additional_multi_count;
+ u16 max_packet_size;
+
+ u8 dev_addr = 0;
+ u8 ep_num = 0;
+ bool f_is_ep_in = true;
+ u8 interval = 0;
+ u32 sched_frame = 0;
+ u8 hub_addr = 0;
+ u8 hub_port = 0;
+ bool f_is_do_split = false;
+
+ struct ed *target_ed = NULL;
+ struct isoch_packet_desc *new_isoch_packet_desc = NULL;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ unsigned long spin_lock_flag = 0;
+
+ if (!otghost && !otghost->port_flag.b.port_connect_status) {
+ otg_err(1, "Error : %s is zero\n", otghost ?
+ "Port status" : "otghost");
+ return USB_ERR_NOIO;
+ }
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "enqueue\n");
+ if (compare_ed(otghost, urb->ep->hcpriv, urb)) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "compare ed error\n");
+ pr_info("otg compare ed error\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ /* check ep has ed_t or not */
+ if (!(urb->ep->hcpriv)) {
+ /* for getting dev_speed */
+ switch (urb->dev->speed) {
+ case USB_SPEED_HIGH:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "HS_OTG\n");
+ dev_speed = HIGH_SPEED_OTG;
+ break;
+
+ case USB_SPEED_FULL:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "FS_OTG\n");
+ dev_speed = FULL_SPEED_OTG;
+ break;
+
+ case USB_SPEED_LOW:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "LS_OTG\n");
+ dev_speed = LOW_SPEED_OTG;
+ break;
+
+ default:
+ otg_err(OTG_DBG_OTGHCDI_HCD,
+ "unKnown Device Speed\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ /* for getting ed_type */
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_BULK:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "bulk transfer\n");
+ ed_type = BULK_TRANSFER;
+ break;
+
+ case PIPE_INTERRUPT:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "interrupt transfer\n");
+ ed_type = INT_TRANSFER;
+ break;
+
+ case PIPE_CONTROL:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "control transfer\n");
+ ed_type = CONTROL_TRANSFER;
+ break;
+
+ case PIPE_ISOCHRONOUS:
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "isochronous transfer\n");
+ ed_type = ISOCH_TRANSFER;
+ break;
+ default:
+ otg_err(OTG_DBG_OTGHCDI_HCD, "unKnown ep type\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ max_packet_size = usb_maxpacket(urb->dev, urb->pipe,
+ !(usb_pipein(urb->pipe)));
+
+ additional_multi_count = ((max_packet_size) >> 11) & 0x03;
+ dev_addr = usb_pipedevice(urb->pipe);
+ ep_num = usb_pipeendpoint(urb->pipe);
+
+ f_is_ep_in = usb_pipein(urb->pipe) ? true : false;
+ interval = (u8)(urb->interval);
+ sched_frame = (u8)(urb->start_frame);
+
+ /* check */
+ if (urb->dev->tt == NULL) {
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "urb->dev->tt == NULL\n");
+ hub_port = 0; /* u8 hub_port */
+ hub_addr = 0; /* u8 hub_addr */
+
+ } else {
+ hub_port = (u8)(urb->dev->ttport);
+ if (urb->dev->tt->hub) {
+ if (((dev_speed == FULL_SPEED_OTG) ||
+ (dev_speed == LOW_SPEED_OTG)) &&
+ (urb->dev->tt) &&
+ (urb->dev->tt->hub->devnum != 1)) {
+ f_is_do_split = true;
+ }
+ hub_addr = (u8)(urb->dev->tt->hub->devnum);
+ }
+
+ if (urb->dev->tt->multi)
+ hub_addr = 0x80;
+ }
+ otg_dbg(OTG_DBG_OTGHCDI_HCD,
+ "dev_spped =%d, hub_port=%d, hub_addr=%d\n",
+ dev_speed, hub_port, hub_addr);
+
+ ret_val = create_ed(&target_ed);
+
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD,
+ "fail to create_ed()\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return ret_val;
+ }
+
+ ret_val = init_ed(target_ed,
+ dev_addr,
+ ep_num,
+ f_is_ep_in,
+ dev_speed,
+ ed_type,
+ max_packet_size,
+ additional_multi_count,
+ interval,
+ sched_frame,
+ hub_addr,
+ hub_port,
+ f_is_do_split,
+ (void *)urb->ep);
+
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD,
+ "fail to init_ed() :err = %d\n", (int)ret_val);
+ otg_mem_free(target_ed);
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ urb->ep->hcpriv = (void *)(target_ed);
+
+ } else { /* if (!(ep->hcpriv)) */
+
+ dev_addr = usb_pipedevice(urb->pipe);
+ if (((struct ed *)(urb->ep->hcpriv))->ed_desc.device_addr
+ != dev_addr)
+ ((struct ed *)urb->ep->hcpriv)->ed_desc.device_addr
+ = dev_addr;
+ }
+
+ target_ed = (struct ed *)urb->ep->hcpriv;
+
+ if (urb->transfer_flags & URB_SHORT_NOT_OK)
+ trans_flag += USB_TRANS_FLAG_NOT_SHORT;
+
+ if (urb->transfer_flags & URB_ISO_ASAP)
+ trans_flag += USB_TRANS_FLAG_ISO_ASYNCH;
+
+ if (ed_type == ISOCH_TRANSFER) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "ISO not yet supported\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ if (!HC_IS_RUNNING(hcd->state)) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state)\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return -USB_ERR_NODEV;
+ }
+
+ /* in case of unlink-during-submit */
+ if (urb->status != -EINPROGRESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "urb->status is -EINPROGRESS\n");
+ urb->hcpriv = NULL;
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ usb_hcd_giveback_urb(hcd, urb, urb->status);
+ return USB_ERR_SUCCESS;
+ }
+
+ ret_val = issue_transfer(otghost, target_ed, (void *)NULL, (void *)NULL,
+ trans_flag,
+ (usb_pipetype(urb->pipe) == PIPE_CONTROL) ? true : false,
+ (u32)urb->setup_packet, (u32)urb->setup_dma,
+ (u32)urb->transfer_buffer, (u32)urb->transfer_dma,
+ (u32)urb->transfer_buffer_length,
+ (u32)urb->start_frame, (u32)urb->number_of_packets,
+ new_isoch_packet_desc, (void *)urb, &return_td_addr);
+
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "fail to issue_transfer()\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+ urb->hcpriv = (void *)return_td_addr;
+
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int s5pc110_otghcd_urb_dequeue(struct usb_hcd *_hcd, struct urb *_urb )
+ *
+ * @brief dequeue a urb to otg
+ *
+ * @param [in] _hcd : pointer of usb_hcd
+ * [in] _urb : pointer of urb
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * USB_ERR_FAIL : If call fail
+ */
+static int s5pc110_otghcd_urb_dequeue(
+ struct usb_hcd *_hcd, struct urb *_urb, int status)
+{
+ int ret_val = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd);
+
+ unsigned long spin_lock_flag = 0;
+ struct td *cancel_td = (struct td *)_urb->hcpriv;
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "dequeue\n"); */
+
+ if (cancel_td == NULL) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "cancel_td is NULL\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "dequeue status = %d\n", status);
+
+ ret_val = usb_hcd_check_unlink_urb(_hcd, _urb, status);
+ if ((ret_val) && (ret_val != -EIDRM)) {
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "ret_val = %d\n", ret_val);
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return ret_val;
+ }
+
+ if (!HC_IS_RUNNING(_hcd->state)) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state)\n");
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ otg_usbcore_giveback(cancel_td);
+ return USB_ERR_SUCCESS;
+ }
+
+ ret_val = cancel_transfer(otghost, cancel_td->parent_ed_p, cancel_td);
+ if (ret_val != USB_ERR_DEQUEUED && ret_val != USB_ERR_NOELEMENT) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "fail to cancel_transfer()\n");
+/* otg_usbcore_giveback(cancel_td); */
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+/* otg_usbcore_giveback(cancel_td); */
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * void s5pc110_otghcd_endpoint_disable(
+ * struct usb_hcd *hcd,
+ * struct usb_host_endpoint *ep)
+ *
+ * @brief disable a endpoint
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ * [in] ep : pointer of usb_host_endpoint
+ */
+static void s5pc110_otghcd_endpoint_disable(
+ struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+ unsigned long spin_lock_flag = 0;
+ int ret_val = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_endpoint_disable\n");
+
+ if (!((struct ed *)ep->hcpriv))
+ return;
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+ ret_val = delete_ed(otghost, (struct ed *)ep->hcpriv);
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "fail to delete_ed()\n");
+ return;
+ }
+
+ /* ep->hcpriv = NULL; delete_ed coveres it */
+}
+
+/**
+ * int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf)
+ *
+ * @brief get status of root hub
+ *
+ * @param [in] _hcd : pointer of usb_hcd
+ * [inout] _buf : pointer of buffer for write a status data
+ *
+ * @return ret_val : return port status
+ */
+static int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf)
+{
+ int ret_val = 0;
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd);
+
+ /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_status_data\n"); */
+
+ /* if !USB_SUSPEND, root hub timers won't get shut down ... */
+ if (!HC_IS_RUNNING(_hcd->state)) {
+ otg_dbg(OTG_DBG_OTGHCDI_HCD,
+ "_hcd->state is NOT HC_IS_RUNNING\n");
+ return 0;
+ }
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+ ret_val = get_otg_port_status(_hcd, OTG_PORT_NUMBER, _buf);
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+
+ return (int)ret_val;
+}
+
+/**
+ * int s5pc110_otghcd_hub_control()
+ *
+ * @brief control root hub
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ * [in] typeReq : type of control request
+ * [in] value : value
+ * [in] index : index
+ * [in] buf_p : pointer of urb
+ * [in] length : type of gfp_t
+ *
+ * @return ret_val : return root_hub_feature
+ */
+static int
+s5pc110_otghcd_hub_control(
+ struct usb_hcd *hcd,
+ u16 typeReq,
+ u16 value,
+ u16 index,
+ char *buf_p,
+ u16 length)
+{
+ int ret_val = 0;
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_control\n");
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ ret_val = root_hub_feature(hcd, OTG_PORT_NUMBER,
+ typeReq, value, (void *)buf_p);
+
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "fail to root_hub_feature()\n");
+ return ret_val;
+ }
+ return (int)ret_val;
+}
+
+/**
+ * int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd)
+ *
+ * @brief suspend otg hcd
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ *
+ * @return USB_ERR_SUCCESS
+ */
+static int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd)
+{
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_suspend\n");
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+ bus_suspend();
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd)
+ *
+ * @brief resume otg hcd
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ *
+ * @return USB_ERR_SUCCESS
+ */
+static int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd)
+{
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+ unsigned long spin_lock_flag = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_resume\n");
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+
+ if (bus_resume(otghost) != USB_ERR_SUCCESS)
+ return USB_ERR_FAIL;
+
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port)
+ *
+ * @brief reset otg port
+ *
+ * @param [in] hcd : pointer of usb_hcd
+ * [in] port : number of port
+ *
+ * @return USB_ERR_SUCCESS : If success
+ * ret_val : If call fail
+ */
+static int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port)
+{
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+ unsigned long spin_lock_flag = 0;
+ int ret_val = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start_port_reset\n");
+
+ spin_lock_irqsave(&otghost->lock, spin_lock_flag);
+ ret_val = reset_and_enable_port(hcd, OTG_PORT_NUMBER);
+ spin_unlock_irqrestore(&otghost->lock, spin_lock_flag);
+
+ if (ret_val != USB_ERR_SUCCESS) {
+ otg_err(OTG_DBG_OTGHCDI_HCD,
+ "fail to reset_and_enable_port()\n");
+ return ret_val;
+ }
+ return USB_ERR_SUCCESS;
+}
+
+
+/**
+ * @struct hc_driver s5pc110_otg_hc_driver
+ *
+ * @brief implementation of hc_driver for OTG HCD
+ *
+ * describe in detail
+ */
+static const struct hc_driver s5pc110_otg_hc_driver = {
+ .description = "EMSP_OTGHCD",
+ .product_desc = "S3C OTGHCD",
+ .hcd_priv_size = sizeof(struct sec_otghost),
+
+ .irq = s5pc110_otghcd_irq,
+ .flags = HCD_MEMORY | HCD_USB2,
+
+ /** basic lifecycle operations */
+ /* .reset = */
+ .start = s5pc110_otghcd_start,
+ /* .suspend = */
+ /* .resume = */
+ .stop = s5pc110_otghcd_stop,
+ .shutdown = s5pc110_otghcd_shutdown,
+
+ /** managing i/o requests and associated device resources */
+ .urb_enqueue = s5pc110_otghcd_urb_enqueue,
+ .urb_dequeue = s5pc110_otghcd_urb_dequeue,
+ .endpoint_disable = s5pc110_otghcd_endpoint_disable,
+
+ /** scheduling support */
+ .get_frame_number = s5pc110_otghcd_get_frame_number,
+
+ /** root hub support */
+ .hub_status_data = s5pc110_otghcd_hub_status_data,
+ .hub_control = s5pc110_otghcd_hub_control,
+ /* .hub_irq_enable = */
+ .bus_suspend = s5pc110_otghcd_bus_suspend,
+ .bus_resume = s5pc110_otghcd_bus_resume,
+ .start_port_reset = s5pc110_otghcd_start_port_reset,
+};
+
+
diff --git a/drivers/usb/host/shost/shost_kal.h b/drivers/usb/host/shost/shost_kal.h
new file mode 100644
index 0000000..1a213a1
--- /dev/null
+++ b/drivers/usb/host/shost/shost_kal.h
@@ -0,0 +1,361 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-kal.h
+ * @brief header of s3c-otg-hcdi-kal \n
+ * @version
+ * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Creating the initial version of this code \n
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Optimizing for performance \n
+ * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Modifying for successful rmmod & disconnecting \n
+ * @see None
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _SHOST_KAL_H_
+#define _SHOST_KAL_H_
+
+#include <linux/io.h> /* for readl, writel */
+#include <linux/usb/ch9.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <mach/map.h>
+#include <plat/regs-otg.h>
+
+extern volatile u8 *g_pUDCBase;
+extern struct usb_hcd *g_pUsbHcd;
+
+#define ctrlr_base_reg_addr(offset) \
+ ((volatile unsigned int *)((g_pUDCBase) + (offset)))
+/**
+ * otg_kal_make_ep_null
+ *
+ * @brief make ep->hcpriv NULL
+ *
+ * @param [in] pdelete_ed : pointer of ed
+ *
+ * @return void \n
+ */
+static inline void
+otg_kal_make_ep_null(struct ed *pdelete_ed)
+{
+ ((struct usb_host_endpoint *)(pdelete_ed->ed_private))->hcpriv = NULL;
+}
+
+/**
+ * otg_kal_is_ep_null
+ *
+ * @brief check ep->hcpriv is NULL or not
+ *
+ * @param [in] pdelete_ed : pointer of ed
+ *
+ * @return bool \n
+ */
+static inline bool
+otg_kal_is_ep_null(struct ed *pdelete_ed)
+{
+ if (((struct usb_host_endpoint *)
+ (pdelete_ed->ed_private))->hcpriv == NULL)
+ return true;
+ else
+ return false;
+}
+
+
+/**
+ * int otg_usbcore_get_calc_bustime()
+ *
+ * @brief get bus time of usbcore
+ *
+ * @param [in] speed : usb speed
+ * [in] is_input : input or not
+ * [in] is_isoch : isochronous or not
+ * [in] byte_count : bytes
+ *
+ * @return bus time of usbcore \n
+ */
+static inline int
+otg_usbcore_get_calc_bustime(u8 speed, bool is_input,
+ bool is_isoch, unsigned int byte_count)
+{
+ unsigned int convert_speed = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_get_calc_bustime\n");
+/* enum usb_device_speed {
+ USB_SPEED_UNKNOWN = 0,
+ USB_SPEED_LOW, USB_SPEED_FULL,
+ USB_SPEED_HIGH,
+ USB_SPEED_VARIABLE, };*/
+ switch (speed) {
+ case HIGH_SPEED_OTG:
+ convert_speed = USB_SPEED_HIGH; break;
+ case FULL_SPEED_OTG:
+ convert_speed = USB_SPEED_FULL; break;
+ case LOW_SPEED_OTG:
+ convert_speed = USB_SPEED_LOW; break;
+ default:
+ convert_speed = USB_SPEED_UNKNOWN; break;
+ }
+ return usb_calc_bus_time(convert_speed, is_input,
+ (unsigned int)is_isoch, byte_count);
+}
+
+
+/**
+ * void otg_usbcore_giveback(struct td td_p)
+ *
+ * @brief give-back a td as urb
+ *
+ * @param [in] td_p : pointer of struct td to give back
+ *
+ * @return void \n
+ */
+static inline void
+otg_usbcore_giveback(struct td *td_p)
+{
+ struct urb *urb_p = NULL;
+
+ otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_giveback\n");
+
+ if (td_p->td_private == NULL) {
+ otg_err(OTG_DBG_OTGHCDI_KAL,
+ "td_p->td_private == NULL\n");
+ return;
+ }
+
+ urb_p = (struct urb *)td_p->td_private;
+
+ urb_p->actual_length = (int)(td_p->transferred_szie);
+ urb_p->status = (int)(td_p->error_code);
+ urb_p->error_count = (int)(td_p->err_cnt);
+ urb_p->hcpriv = NULL;
+
+ usb_hcd_giveback_urb(g_pUsbHcd, urb_p, urb_p->status);
+}
+
+/**
+ * void otg_usbcore_hc_died(void)
+ *
+ * @brief inform usbcore of hc die
+ *
+ * @return void \n
+ */
+static inline void
+otg_usbcore_hc_died(void)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_hc_died\n");
+ usb_hc_died(g_pUsbHcd);
+}
+
+/**
+ * void otg_usbcore_poll_rh_status(void)
+ *
+ * @brief invoke usbcore's usb_hcd_poll_rh_status
+ *
+ * @param void
+ *
+ * @return void \n
+ */
+static inline void
+otg_usbcore_poll_rh_status(void)
+{
+ usb_hcd_poll_rh_status(g_pUsbHcd);
+}
+
+/**
+ * void otg_usbcore_resume_roothub(void)
+ *
+ * @brief invoke usbcore's usb_hcd_resume_root_hub
+ *
+ * @param void
+ *
+ * @return void \n
+ */
+static inline void
+otg_usbcore_resume_roothub(void)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_KAL,
+ "otg_usbcore_resume_roothub\n");
+ usb_hcd_resume_root_hub(g_pUsbHcd);
+};
+
+/**
+ * int otg_usbcore_inc_usb_bandwidth(u32 band_width)
+ *
+ * @brief increase bandwidth of usb bus
+ *
+ * @param [in] band_width : bandwidth to be increased
+ *
+ * @return USB_ERR_SUCCESS \n
+ */
+static inline int
+otg_usbcore_inc_usb_bandwidth(u32 band_width)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_KAL,
+ "otg_usbcore_inc_usb_bandwidth\n");
+ hcd_to_bus(g_pUsbHcd)->bandwidth_allocated += band_width;
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int otg_usbcore_des_usb_bandwidth(u32 uiBandwidth)
+ *
+ * @brief decrease bandwidth of usb bus
+ *
+ * @param [in] band_width : bandwidth to be decreased
+ *
+ * @return USB_ERR_SUCCESS \n
+ */
+static inline int
+otg_usbcore_des_usb_bandwidth(u32 band_width)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_KAL,
+ "otg_usbcore_des_usb_bandwidth\n");
+ hcd_to_bus(g_pUsbHcd)->bandwidth_allocated -= band_width;
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type)
+ *
+ * @brief increase count of periodic transfer
+ *
+ * @param [in] transfer_type : type of transfer
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+static inline int
+otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_KAL,
+ "otg_usbcore_inc_periodic_transfer_cnt\n");
+
+ switch (transfer_type) {
+ case INT_TRANSFER:
+ hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs++;
+ break;
+ case ISOCH_TRANSFER:
+ hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs++;
+ break;
+ default:
+ otg_err(OTG_DBG_OTGHCDI_KAL,
+ "not proper TransferType\n");
+ return USB_ERR_FAIL;
+ }
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type)
+ *
+ * @brief decrease count of periodic transfer
+ *
+ * @param [in] transfer_type : type of transfer
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+static inline int
+otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_KAL,
+ "otg_usbcore_des_periodic_transfer_cnt\n");
+
+ switch (transfer_type) {
+ case INT_TRANSFER:
+ hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs--;
+ break;
+ case ISOCH_TRANSFER:
+ hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs--;
+ break;
+ default:
+ otg_err(OTG_DBG_OTGHCDI_KAL,
+ "not proper TransferType\n");
+ return USB_ERR_FAIL;
+ }
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * u32 read_reg_32(u32 offset)
+ *
+ * @brief Reads the content of a register.
+ *
+ * @param [in] offset : offset of address of register to read.
+ *
+ * @return contents of the register. \n
+ * @remark call readl()
+ */
+static inline u32 read_reg_32(u32 offset)
+{
+ volatile unsigned int *reg_addr_p = ctrlr_base_reg_addr(offset);
+
+ return *reg_addr_p;
+ /* return readl(reg_addr_p); */
+};
+
+/**
+ * void write_reg_32( u32 offset, const u32 value)
+ *
+ * @brief Writes a register with a 32 bit value.
+ *
+ * @param [in] offset : offset of address of register to write.
+ * @param [in] value : value to write
+ *
+ * @remark call writel()
+ */
+static inline void write_reg_32(u32 offset, const u32 value)
+{
+ volatile unsigned int *reg_addr_p = ctrlr_base_reg_addr(offset);
+
+ *reg_addr_p = value;
+ /* writel( value, reg_addr_p ); */
+};
+
+/**
+ * void update_reg_32(u32 offset, u32 value)
+ *
+ * @brief logic or operation
+ *
+ * @param [in] offset : offset of address of register to write.
+ * @param [in] value : value to or
+ *
+ */
+static inline void update_reg_32(u32 offset, u32 value)
+{
+ write_reg_32(offset, (read_reg_32(offset) | value));
+}
+
+/**
+ * void clear_reg_32(u32 offset, u32 value)
+ *
+ * @brief logic not operation
+ *
+ * @param [in] offset : offset of address of register to write.
+ * @param [in] value : value to not
+ *
+ */
+static inline void clear_reg_32(u32 offset, u32 value)
+{
+ write_reg_32(offset, (read_reg_32(offset) & ~value));
+}
+
+#endif /* _S3C_OTG_HCDI_KAL_H_ */
+
diff --git a/drivers/usb/host/shost/shost_list.h b/drivers/usb/host/shost/shost_list.h
new file mode 100644
index 0000000..7283fb7
--- /dev/null
+++ b/drivers/usb/host/shost/shost_list.h
@@ -0,0 +1,188 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-list.h
+ * @brief list functions for otg
+ * @version
+ * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Creating the initial version of this code
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ * @see None
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _S3C_OTG_HCDI_LIST_H_
+#define _S3C_OTG_HCDI_LIST_H_
+
+#include <linux/list.h>
+
+typedef struct list_head otg_list_head;
+
+#define otg_list_get_node(ptr, type, member) container_of(ptr, type, member)
+
+
+#define otg_list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+
+/**
+ * void otg_list_push_next(otg_list_head *n, otg_list_head *list)
+ *
+ * @brief push a list node into the next of head
+ *
+ * @param [in] n : node to be pushed
+ * @param [in] otg_list_head : target list head
+ *
+ * @return void \n
+ */
+
+static inline
+void otg_list_push_next(otg_list_head *n, otg_list_head *list)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_next\n");
+ list_add(n, list);
+}
+
+/**
+ * void otg_list_push_prev(otg_list_head *n, otg_list_head *list)
+ *
+ * @brief push a list node into the previous of head
+ *
+ * @param [in] n : node to be pushed
+ * @param [in] otg_list_head : target list head
+ *
+ * @return void \n
+ */
+static inline
+void otg_list_push_prev(otg_list_head *n, otg_list_head *list)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_prev\n");
+ list_add_tail(n, list);
+}
+
+/**
+ * void otg_list_pop(otg_list_head *list_entity_p)
+ *
+ * @brief pop a list node
+ *
+ * @param [in] n : node to be poped
+ * @param [in] otg_list_head : target list head
+ *
+ * @return void \n
+ */
+static inline
+void otg_list_pop(otg_list_head *list_entity_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_pop\n");
+
+ if (list_entity_p->prev && list_entity_p->next)
+ list_del(list_entity_p);
+ else
+ pr_info("usb: otg_list_pop error\n");
+}
+
+/**
+ * void otg_list_move_next(otg_list_head *node_p, otg_list_head *list)
+ *
+ * @brief move a list to next of head
+ *
+ * @param [in] n : node to be moved
+ * @param [in] otg_list_head : target list head
+ *
+ * @return void \n
+ */
+static inline
+void otg_list_move_next(otg_list_head *node_p, otg_list_head *list)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_next\n");
+ list_move(node_p, list);
+}
+
+/**
+ * void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list)
+ *
+ * @brief move a list to previous of head
+ *
+ * @param [in] n : node to be moved
+ * @param [in] otg_list_head : target list head
+ *
+ * @return void \n
+ */
+static inline
+void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_prev\n");
+ list_move_tail(node_p, list);
+}
+
+/**
+ * bool otg_list_empty(otg_list_head *list)
+ *
+ * @brief check a list empty or not
+ *
+ * @param [in] list : node to check
+ *
+ * @return true : empty list \n
+ * false : not empty list
+ */
+static inline
+bool otg_list_empty(otg_list_head *list)
+{
+ /* otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_empty\n"); */
+ if (list_empty(list))
+ return true;
+ return false;
+}
+
+/**
+ * void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p)
+ *
+ * @brief merge two list
+ *
+ * @param [in] list_p : a head
+ * @param [in] head_p : target list head
+ *
+ * @return void \n
+ */
+static inline
+void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_merge\n");
+ list_splice(list_p, head_p);
+}
+
+/**
+ * void otg_list_init(otg_list_head *list_p)
+ *
+ * @brief initialize a list
+ *
+ * @param [in] list_p : node to be initialized
+ *
+ * @return void \n
+ */
+static inline
+void otg_list_init(otg_list_head *list_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_init\n");
+ list_p->next = list_p;
+ list_p->prev = list_p;
+}
+
+#endif /* _S3C_OTG_HCDI_LIST_H_ */
+
diff --git a/drivers/usb/host/shost/shost_mem.h b/drivers/usb/host/shost/shost_mem.h
new file mode 100644
index 0000000..ecbbed2
--- /dev/null
+++ b/drivers/usb/host/shost/shost_mem.h
@@ -0,0 +1,155 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-memory.h
+ * @brief header of s3c-otg-hcdi-memory \n
+ * @version
+ * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Creating the initial version of this code \n
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Optimizing for performance \n
+ * @see None
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _S3C_OTG_HCDI_MEMORY_H_
+#define _S3C_OTG_HCDI_MEMORY_H_
+
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+/**
+ * @enum otg_mem_alloc_flag
+ *
+ * @brief enumeration for flag of memory allocation
+ */
+enum otg_mem_type {
+ USB_MEM_SYNC,
+ USB_MEM_ASYNC,
+ USB_MEM_DMA
+};
+
+/**
+ * int otg_mem_alloc(void ** addr_pp, u16 byte_size, u8 ubType);
+ *
+ * @brief allocating momory specified
+ *
+ * @param [inout] addr_pp : address to be assigned
+ * [in] byte_size : size of memory
+ * [in] type : enum otg_mem_type
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+static inline int
+otg_mem_alloc(void **addr_pp, u16 byte_size,
+ enum otg_mem_type type)
+{
+ gfp_t flags;
+ otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_alloc\n");
+
+ switch (type) {
+ case USB_MEM_SYNC:
+ flags = GFP_KERNEL;
+ break;
+ case USB_MEM_ASYNC:
+ flags = GFP_ATOMIC;
+ break;
+ case USB_MEM_DMA:
+ flags = GFP_DMA;
+ break;
+ default:
+ otg_err(OTG_DBG_OTGHCDI_MEM,
+ "not proper enum otg_mem_type\n");
+ return USB_ERR_FAIL;
+ }
+
+ *addr_pp = kmalloc((size_t)byte_size, flags);
+
+ if (*addr_pp == 0) {
+ otg_err(OTG_DBG_OTGHCDI_MEM, "kmalloc failed\n");
+ return USB_ERR_FAIL;
+ }
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int otg_mem_copy(void *to_addr_p, void *from_addr_p, u16 byte_size);
+ *
+ * @brief memory copy
+ *
+ * @param [in] to_addr_p : target address
+ * [in] from_addr_p : source address
+ * [in] byte_size : size
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+static inline int
+otg_mem_copy(void *to_addr_p,
+ void *from_addr_p, u16 byte_size)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_copy\n");
+
+ memcpy(to_addr_p, from_addr_p, (size_t)byte_size);
+
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int otg_mem_free(void * addr_p);
+ *
+ * @brief de-allocating memory
+ *
+ * @param [in] addr_p : target address to be de-allocated
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+static inline int
+otg_mem_free(void *addr_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_free\n");
+ kfree(addr_p);
+ return USB_ERR_SUCCESS;
+}
+
+
+/**
+ * int otg_mem_set(void *addr_p, char value, u16 byte_size)
+ *
+ * @brief writing a value to memory
+ *
+ * @param [in] addr_p : target address
+ * [in] value : value to be written
+ * [in] byte_size : size
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+static inline int
+otg_mem_set(void *addr_p, char value, u16 byte_size)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_set\n");
+ memset(addr_p, value, (size_t)byte_size);
+ return USB_ERR_SUCCESS;
+}
+
+
+#endif /* _S3C_OTG_HCDI_MEMORY_H_ */
diff --git a/drivers/usb/host/shost/shost_oci.c b/drivers/usb/host/shost/shost_oci.c
new file mode 100644
index 0000000..4f47055
--- /dev/null
+++ b/drivers/usb/host/shost/shost_oci.c
@@ -0,0 +1,809 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : OCI.c
+ * [Description] : The file implement the external
+ * and internal functions of OCI
+ * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * [Department] : System LSI Division/Embedded S/W Platform
+ * [Created Date]: 2009/02/10
+ * [Revision History]
+ * (1) 2008/06/12 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * - Created this file and Implement functions of OCI
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+
+static bool ch_enable[16];
+
+/* OCI Internal Functions */
+
+static void oci_set_global_interrupt(bool set)
+{
+ gahbcfg_t ahbcfg;
+
+ otg_dbg(OTG_DBG_OCI, "oci_set_global_interrupt\n");
+
+ ahbcfg.d32 = 0;
+ ahbcfg.b.glblintrmsk = 1;
+
+ if (set)
+ update_reg_32(GAHBCFG, ahbcfg.d32);
+ else
+ clear_reg_32(GAHBCFG, ahbcfg.d32);
+}
+
+static int oci_channel_alloc(u8 *ch_num)
+{
+ hcchar_t hcchar = {.d32 = 0};
+ u8 ch;
+
+ for (ch = 0 ; ch < 16 ; ch++) {
+ if (ch_enable[ch] == true) {
+
+ hcchar.d32 = read_reg_32(HCCHAR(ch));
+
+ if (hcchar.b.chdis == 0) {
+ *ch_num = ch;
+ ch_enable[ch] = false;
+ return USB_ERR_SUCCESS;
+ }
+ }
+ }
+
+ return USB_ERR_FAIL;
+}
+
+
+
+static void oci_flush_tx_fifo(u32 num)
+{
+ grstctl_t greset = {.d32 = 0};
+ u32 count = 0;
+
+ otg_dbg(OTG_DBG_OCI, "oci_flush_tx_fifo\n");
+
+ greset.b.txfflsh = 1;
+ greset.b.txfnum = num;
+ write_reg_32(GRSTCTL, greset.d32);
+
+ /* wait for flush to end */
+ while (greset.b.txfflsh == 1) {
+ greset.d32 = read_reg_32(GRSTCTL);
+ if (++count > MAX_COUNT)
+ break;
+ };
+
+ /* Wait for 3 PHY Clocks*/
+ udelay(30);
+}
+
+static void oci_flush_rx_fifo(void)
+{
+ grstctl_t greset = {.d32 = 0};
+ u32 count = 0;
+
+ otg_dbg(OTG_DBG_OCI, "oci_flush_rx_fifo\n");
+
+ greset.b.rxfflsh = 1;
+ write_reg_32(GRSTCTL, greset.d32);
+
+ do {
+ greset.d32 = read_reg_32(GRSTCTL);
+ if (++count > MAX_COUNT)
+ break;
+ } while (greset.b.rxfflsh == 1);
+
+ /* Wait for 3 PHY Clocks*/
+ udelay(30);
+}
+
+static void oci_config_flush_fifo(u32 mode)
+{
+ ghwcfg2_t hwcfg2 = {.d32 = 0};
+
+ otg_dbg(OTG_DBG_OCI, "oci_config_flush_fifo\n");
+
+ hwcfg2.d32 = read_reg_32(GHWCFG2);
+
+ /* Configure data FIFO sizes */
+ if (hwcfg2.b.dynamic_fifo) {
+ /* Rx FIFO */
+ write_reg_32(GRXFSIZ, 0x0000010D);
+
+ /* Non-periodic Tx FIFO */
+ write_reg_32(GNPTXFSIZ, 0x0080010D);
+
+ if (mode == OTG_HOST_MODE) {
+ /* For Periodic transactions, */
+ /* program HPTXFSIZ */
+ }
+ }
+
+ /* Flush the FIFOs */
+ oci_flush_tx_fifo(0);
+
+ oci_flush_rx_fifo();
+}
+
+static int oci_core_reset(void)
+{
+ grstctl_t greset = {.d32 = 0};
+ u32 count = 0;
+
+ otg_dbg(OTG_DBG_OCI, "oci_core_reset\n");
+
+ /* Wait for AHB master IDLE state. */
+ do {
+ greset.d32 = read_reg_32(GRSTCTL);
+ mdelay(50);
+
+ if (++count > 100) {
+ otg_dbg(OTG_DBG_OCI, "AHB status is not IDLE\n");
+ return USB_ERR_FAIL;
+ }
+
+ } while (greset.b.ahbidle != 1);
+
+ /* Core Soft Reset */
+ greset.b.csftrst = 1;
+ write_reg_32(GRSTCTL, greset.d32);
+
+ /* Wait for 3 PHY Clocks*/
+ mdelay(50);
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int oci_core_init(void)
+ *
+ * @brief process core initialize as s5pc110 otg spec
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+static int oci_core_init(void)
+{
+ gahbcfg_t ahbcfg = {.d32 = 0};
+ gusbcfg_t usbcfg = {.d32 = 0};
+ ghwcfg2_t hwcfg2 = {.d32 = 0};
+ gintmsk_t gintmsk = {.d32 = 0};
+
+ otg_dbg(OTG_DBG_OCI, "oci_core_init\n");
+
+ usbcfg.d32 = read_reg_32(GUSBCFG);
+ otg_dbg(OTG_DBG_OCI, "before - GUSBCFG=0x%x, GOTGCTL=0x%x\n",
+ usbcfg.d32, read_reg_32(GOTGCTL));
+
+ /* PHY parameters */
+ usbcfg.b.physel = 0;
+ usbcfg.b.phyif = 1; /* 16 bit */
+ usbcfg.b.ulpi_utmi_sel = 0; /* UTMI */
+ /* usbcfg.b.ddrsel = 1; */ /* DDR */
+ usbcfg.b.usbtrdtim = 5; /* 16 bit UTMI */
+ usbcfg.b.toutcal = 7;
+ usbcfg.b.forcehstmode = 1;
+ write_reg_32(GUSBCFG, usbcfg.d32);
+
+ otg_dbg(OTG_DBG_OCI,
+ "after - GUSBCFG=0x%x, GOTGCTL=0x%x\n",
+ read_reg_32(GUSBCFG),
+ read_reg_32(GOTGCTL));
+
+ /* Reset after setting the PHY parameters */
+ if (oci_core_reset() == USB_ERR_SUCCESS) {
+ /* Program the GAHBCFG Register.*/
+ hwcfg2.d32 = read_reg_32(GHWCFG2);
+
+ switch (hwcfg2.b.architecture) {
+ case HWCFG2_ARCH_SLAVE_ONLY:
+ otg_dbg(OTG_DBG_OCI, "Slave Only Mode\n");
+ ahbcfg.b.nptxfemplvl = 0;
+ ahbcfg.b.ptxfemplvl = 0;
+ break;
+
+ case HWCFG2_ARCH_EXT_DMA:
+ otg_dbg(OTG_DBG_OCI, "External DMA Mode - TBD!\n");
+ break;
+
+ case HWCFG2_ARCH_INT_DMA:
+ otg_dbg(OTG_DBG_OCI, "Internal DMA Setting\n");
+ ahbcfg.b.dmaenable = true;
+ ahbcfg.b.hburstlen = INT_DMA_MODE_INCR4;
+ break;
+
+ default:
+ otg_dbg(OTG_DBG_OCI, "ERR> hwcfg2\n ");
+ break;
+ }
+ write_reg_32(GAHBCFG, ahbcfg.d32);
+
+ /* Program the GUSBCFG register.*/
+ switch (hwcfg2.b.op_mode) {
+ case MODE_HNP_SRP_CAPABLE:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_HNP_SRP_CAPABLE\n");
+ usbcfg.b.hnpcap = 1;
+ usbcfg.b.srpcap = 1;
+
+ otg_dbg(OTG_DBG_OCI,
+ "OTG_DBG_OCI : use HNP and SRP\n");
+ break;
+
+ case MODE_SRP_ONLY_CAPABLE:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_SRP_ONLY_CAPABLE\n");
+ usbcfg.b.srpcap = 1;
+ break;
+
+ case MODE_NO_HNP_SRP_CAPABLE:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_NO_HNP_SRP_CAPABLE\n");
+ usbcfg.b.hnpcap = 0;
+ break;
+
+ case MODE_SRP_CAPABLE_DEVICE:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_DEVICE\n");
+ usbcfg.b.srpcap = 1;
+ break;
+
+ case MODE_NO_SRP_CAPABLE_DEVICE:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_DEVICE\n");
+ usbcfg.b.srpcap = 0;
+ break;
+
+ case MODE_SRP_CAPABLE_HOST:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_HOST\n");
+ usbcfg.b.srpcap = 1;
+ break;
+
+ case MODE_NO_SRP_CAPABLE_HOST:
+ otg_dbg(OTG_DBG_OCI,
+ "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_HOST\n");
+ usbcfg.b.srpcap = 0;
+ break;
+ default:
+ otg_err(OTG_DBG_OCI, "ERR> hwcfg2\n");
+ break;
+ }
+ write_reg_32(GUSBCFG, usbcfg.d32);
+
+ /* Program the GINTMSK register.*/
+ gintmsk.b.modemismatch = 1;
+ gintmsk.b.sofintr = 1;
+ /*gintmsk.b.otgintr = 1; */
+ gintmsk.b.conidstschng = 1;
+ /*gintmsk.b.wkupintr = 1;*/
+ gintmsk.b.disconnect = 1;
+ /*gintmsk.b.usbsuspend = 1;*/
+ /*gintmsk.b.sessreqintr = 1;*/
+ /*gintmsk.b.portintr = 1;*/
+ /*gintmsk.b.hcintr = 1; */
+ write_reg_32(GINTMSK, gintmsk.d32);
+
+ return USB_ERR_SUCCESS;
+
+ } else {
+ otg_err(OTG_DBG_OCI, "Core Reset FAIL\n");
+ return USB_ERR_FAIL;
+ }
+}
+
+/**
+ * int oci_host_init(void)
+ *
+ * @brief Process host initialize as s5pc110 spec
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+
+static int oci_host_init(void)
+{
+ gintmsk_t gintmsk = {.d32 = 0};
+ hcfg_t hcfg = {.d32 = 0};
+#if 0
+/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/
+ hprt_t hprt;
+ hprt.d32 = read_reg_32(HPRT);
+#endif
+
+ otg_dbg(OTG_DBG_OCI, "oci_host_init\n");
+
+ gintmsk.b.portintr = 1;
+ update_reg_32(GINTMSK, gintmsk.d32);
+
+ hcfg.b.fslspclksel = HCFG_30_60_MHZ;
+ update_reg_32(HCFG, hcfg.d32);
+
+#if 0
+/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/
+ /* turn on vbus */
+ if (!hprt.b.prtpwr) {
+ hprt.b.prtpwr = 1;
+ write_reg_32(HPRT, hprt.d32);
+
+ otg_dbg(true, "turn on Vbus\n");
+ }
+#endif
+
+ oci_config_flush_fifo(OTG_HOST_MODE);
+
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int oci_channel_init( u8 ch_num, struct stransfer *transfer)
+ *
+ * @brief Process channel initialize to prepare starting transfer
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+static int oci_channel_init(u8 ch_num, struct stransfer *transfer)
+{
+ u32 intr_enable = 0;
+ gintmsk_t gintmsk = {.d32 = 0};
+ hcchar_t hcchar = {.d32 = 0};
+ hctsiz_t hctsiz = {.d32 = 0};
+
+ otg_dbg(OTG_DBG_OCI, "oci_channel_init\n");
+
+ /* Clear channel information */
+ write_reg_32(HCTSIZ(ch_num), 0);
+ write_reg_32(HCCHAR(ch_num), 0);
+ write_reg_32(HCINTMSK(ch_num), 0);
+ write_reg_32(HCINT(ch_num), INT_ALL);/*write clear*/
+ write_reg_32(HCDMA(ch_num), 0);
+
+ /* enable host channel interrupt in GINTSTS */
+ gintmsk.b.hcintr = 1;
+ update_reg_32(GINTMSK, gintmsk.d32);
+ /* Enable the top level host channel interrupt in HAINT */
+ intr_enable = (1 << ch_num);
+ update_reg_32(HAINTMSK, intr_enable);
+ /* unmask the down level host channel interrupt in HCINT */
+ write_reg_32(HCINTMSK(ch_num), transfer->hc_reg.hc_int_msk.d32);
+
+ /* Program the HCSIZn register with the endpoint characteristics for */
+ hctsiz.b.xfersize = transfer->buf_size;
+ hctsiz.b.pktcnt = transfer->packet_cnt;
+
+ /* Program the HCCHARn register with the endpoint characteristics for */
+ hcchar.b.mps = transfer->ed_desc_p->max_packet_size;
+ hcchar.b.epnum = transfer->ed_desc_p->endpoint_num;
+ hcchar.b.epdir = transfer->ed_desc_p->is_ep_in;
+ hcchar.b.lspddev = (transfer->ed_desc_p->dev_speed == LOW_SPEED_OTG);
+ hcchar.b.eptype = transfer->ed_desc_p->endpoint_type;
+ hcchar.b.multicnt = transfer->ed_desc_p->mc;
+ hcchar.b.devaddr = transfer->ed_desc_p->device_addr;
+
+ if (transfer->ed_desc_p->endpoint_type == INT_TRANSFER ||
+ transfer->ed_desc_p->endpoint_type == ISOCH_TRANSFER) {
+ u32 uiFrameNum = 0;
+ uiFrameNum = oci_get_frame_num();
+
+ hcchar.b.oddfrm = (uiFrameNum % 2) ? 1 : 0;
+
+ /*
+ * if transfer type is periodic transfer,
+ * must support sof interrupt
+ */
+
+ /*
+ gintmsk.b.sofintr = 1;
+ update_reg_32(GINTMSK, gintmsk.d32);
+ */
+ }
+
+ if (transfer->ed_desc_p->endpoint_type == CONTROL_TRANSFER) {
+
+ struct td *td_p;
+ td_p = (struct td *)transfer->parent_td;
+
+ switch (td_p->standard_dev_req_info.conrol_transfer_stage) {
+
+ case SETUP_STAGE:
+ hctsiz.b.pid = transfer->ed_status_p->
+ control_data_toggle.setup;
+ hcchar.b.epdir = EP_OUT;
+ break;
+
+ case DATA_STAGE:
+ hctsiz.b.pid =
+ transfer->ed_status_p->control_data_toggle.data;
+ hcchar.b.epdir = transfer->ed_desc_p->is_ep_in;
+ break;
+
+ case STATUS_STAGE:
+ hctsiz.b.pid = transfer->ed_status_p->
+ control_data_toggle.status;
+
+ if (td_p->standard_dev_req_info.is_data_stage) {
+ hcchar.b.epdir =
+ ~(transfer->ed_desc_p->is_ep_in);
+ } else
+ hcchar.b.epdir = EP_IN;
+ break;
+ default:
+ break;
+ }
+
+ } else
+ hctsiz.b.pid = transfer->ed_status_p->data_toggle;
+
+ otg_dbg(OTG_DBG_OCI, "eptype %d, stage %d, dir %d\n",
+ transfer->ed_desc_p->endpoint_type,
+ ((struct td *)transfer->parent_td)->
+ standard_dev_req_info.conrol_transfer_stage,
+ hcchar.b.epdir);
+
+ hctsiz.b.dopng = transfer->ed_status_p->is_ping_enable;
+ write_reg_32(HCTSIZ(ch_num), hctsiz.d32);
+ transfer->ed_status_p->is_ping_enable = false;
+
+ /* Write DMA Address */
+ write_reg_32(HCDMA(ch_num), transfer->phy_addr);
+
+ /* Wrote HCCHAR Register */
+ write_reg_32(HCCHAR(ch_num), hcchar.d32);
+
+ return USB_ERR_SUCCESS;
+}
+
+static int oci_dev_init(void)
+{
+ otg_dbg(OTG_DBG_OCI, "oci_dev_init - do nothing.\n");
+ /* return USB_ERR_FAIL; */
+ return USB_ERR_SUCCESS;
+}
+
+static int oci_init_mode(void)
+{
+ gintsts_t gintsts;
+ gintsts.d32 = read_reg_32(GINTSTS);
+
+ otg_dbg(OTG_DBG_OCI,
+ "GINSTS = 0x%x,GINMSK = 0x%x.\n",
+ (unsigned int)gintsts.d32,
+ (unsigned int)read_reg_32(GINTMSK));
+
+ if (gintsts.b.curmode == OTG_HOST_MODE) {
+ otg_dbg(OTG_DBG_OCI, "HOST Mode\n");
+
+ if (oci_host_init() == USB_ERR_SUCCESS) {
+ return USB_ERR_SUCCESS;
+
+ } else {
+ otg_dbg(OTG_DBG_OCI, "oci_host_init() Fail\n");
+ return USB_ERR_FAIL;
+ }
+
+ } else { /* Device Mode */
+ otg_dbg(OTG_DBG_OCI, "DEVICE Mode\n");
+
+ if (oci_dev_init() == USB_ERR_SUCCESS) {
+ return USB_ERR_SUCCESS;
+
+ } else {
+ otg_err(OTG_DBG_OCI, "oci_dev_init() Fail\n");
+ return USB_ERR_FAIL;
+ }
+ }
+
+ return USB_ERR_SUCCESS;
+}
+
+
+
+/**
+ * int oci_start(void)
+ *
+ * @brief start to operate oci module by calling oci_core_init function
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+static int oci_start(void)
+{
+ otg_dbg(OTG_DBG_OCI, "oci_start\n");
+
+ if (oci_core_init() == USB_ERR_SUCCESS) {
+ mdelay(50);
+
+ if (oci_init_mode() == USB_ERR_SUCCESS) {
+ oci_set_global_interrupt(true);
+ return USB_ERR_SUCCESS;
+
+ } else {
+ otg_dbg(OTG_DBG_OCI, "oci_init_mode() Fail\n");
+ return USB_ERR_FAIL;
+ }
+
+ } else {
+ otg_dbg(OTG_DBG_OCI, "oci_core_init() Fail\n");
+ return USB_ERR_FAIL;
+ }
+}
+
+/**
+ * int oci_stop(void)
+ *
+ * @brief stop to opearte otg core
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+static int oci_stop(void)
+{
+ gintmsk_t gintmsk = {.d32 = 0};
+
+ otg_dbg(OTG_DBG_OCI, "oci_stop\n");
+
+ /* sometimes, port interrupt occured after removed
+ * otg host driver. so, we have to mask port interrupt. */
+ write_reg_32(GINTMSK, gintmsk.d32);
+
+ oci_set_global_interrupt(false);
+
+ return USB_ERR_SUCCESS;
+}
+
+
+static int oci_sys_init(struct sec_otghost *otghost)
+{
+ struct sec_otghost_data *pdata = otghost->otg_data;
+
+ if (pdata && pdata->phy_init && pdata->pdev) {
+ pr_info("otg phy_init\n");
+ clk_enable(pdata->clk);
+ pdata->phy_init(0);
+
+ writel(0, OTG_PHYPWR);
+ writel(0x87, OTG_PHYCLK);
+ } else
+ pr_info("otg phy_init failed\n");
+
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int oci_init(struct sec_otghost *otghost)
+ *
+ * @brief Initialize oci module.
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+static int oci_init(struct sec_otghost *otghost)
+{
+ otg_mem_set((void *)ch_enable, true, sizeof(bool)*16);
+ otghost->ch_halt = false;
+
+ if (oci_sys_init(otghost) == USB_ERR_SUCCESS) {
+ if (oci_core_reset() == USB_ERR_SUCCESS) {
+
+ oci_set_global_interrupt(false);
+ return USB_ERR_SUCCESS;
+
+ } else {
+ otg_dbg(OTG_DBG_OCI, "oci_core_reset() Fail\n");
+ return USB_ERR_FAIL;
+ }
+ }
+
+ return USB_ERR_FAIL;
+}
+
+/***********************************************************************
+ * API
+ */
+
+/* also called by ischeduler.c */
+int oci_channel_dealloc(u8 ch_num)
+{
+ if (ch_num < 16 && ch_enable[ch_num] == false) {
+ ch_enable[ch_num] = true;
+
+ write_reg_32(HCTSIZ(ch_num), 0);
+ write_reg_32(HCCHAR(ch_num), 0);
+ write_reg_32(HCINTMSK(ch_num), 0);
+ write_reg_32(HCINT(ch_num), INT_ALL);
+ write_reg_32(HCDMA(ch_num), 0);
+ return USB_ERR_SUCCESS;
+ }
+
+ return USB_ERR_FAIL;
+}
+
+/**
+ * oci_start_transfer(struct sec_otghost *otghost, struct stransfer *transfer)
+ *
+ * @brief start transfer by using transfer information to receive from scheduler
+ *
+ * @param [IN] *transfer - information about transfer to write register by calling oci_channel_init function
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+
+u8 oci_start_transfer(struct sec_otghost *otghost, struct stransfer *transfer)
+{
+ hcchar_t hcchar = {.d32 = 0};
+
+ otg_dbg(OTG_DBG_OCI, "oci_start_transfer\n");
+
+ if (transfer->alloc_chnum == CH_NONE) {
+
+ if (oci_channel_alloc(&(transfer->alloc_chnum))
+ != USB_ERR_SUCCESS) {
+
+ otg_dbg(OTG_DBG_OCI,
+ "Fail - Channel Allocation Error\n");
+ return CH_NONE;
+ }
+ }
+
+ oci_channel_init(transfer->alloc_chnum, transfer);
+
+ hcchar.b.chen = 1;
+ update_reg_32(HCCHAR(transfer->alloc_chnum), hcchar.d32);
+
+ return transfer->alloc_chnum;
+}
+
+/**
+ * int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num)
+ *
+ * @brief stop to transfer even if transfering
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num)
+{
+ hcchar_t hcchar = {.d32 = 0};
+ hcintmsk_t hcintmsk = {.d32 = 0};
+ int count = 0, max_error_count = 10000;
+
+ otg_dbg(OTG_DBG_OCI,
+ "step1: oci_stop_transfer ch=%d, hcchar=0x%x\n",
+ ch_num, read_reg_32(HCCHAR(ch_num)));
+
+ if (ch_num > 16)
+ return USB_ERR_FAIL;
+
+ otghost->ch_halt = true;
+
+ hcintmsk.b.chhltd = 1;
+ update_reg_32(HCINTMSK(ch_num), hcintmsk.d32);
+
+ hcchar.b.chdis = 1;
+ hcchar.b.chen = 1;
+ update_reg_32(HCCHAR(ch_num), hcchar.d32);
+
+ /* wait for Channel Disabled Interrupt */
+ do {
+ hcchar.d32 = read_reg_32(HCCHAR(ch_num));
+
+ if (count > max_error_count) {
+ otg_dbg(OTG_DBG_OCI,
+ "Warning!! oci_stop_transfer()"
+ "ChDis is not cleared! ch=%d, hcchar=0x%x\n",
+ ch_num, hcchar.d32);
+ break;
+ }
+ count++;
+
+ } while (hcchar.b.chdis);
+
+ oci_channel_dealloc(ch_num);
+
+ clear_reg_32(HAINTMSK, ch_num);
+ write_reg_32(HCINT(ch_num), INT_ALL);
+ clear_reg_32(HCINTMSK(ch_num), INT_ALL);
+
+ otghost->ch_halt = false;
+ otg_dbg(OTG_DBG_OCI,
+ "step2 : oci_stop_transfer ch=%d, hcchar=0x%x\n",
+ ch_num, read_reg_32(HCCHAR(ch_num)));
+
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * u32 oci_get_frame_num(void)
+ *
+ * @brief Get current frame number by reading register.
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+u32 oci_get_frame_num(void)
+{
+ hfnum_t hfnum;
+ hfnum.d32 = read_reg_32(HFNUM);
+
+ otg_dbg(OTG_DBG_OCI, "oci_get_frame_num=%d\n", hfnum.b.frnum);
+
+ return hfnum.b.frnum;
+}
+
+/**
+ * u16 oci_get_frame_interval(void)
+ *
+ * @brief Get current frame interval by reading register.
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+u16 oci_get_frame_interval(void)
+{
+ hfir_t hfir;
+ hfir.d32 = read_reg_32(HFIR);
+ return hfir.b.frint;
+}
+
+void oci_set_frame_interval(u16 interval)
+{
+ hfir_t hfir = {.d32 = 0};
+ hfir.b.frint = interval;
+ write_reg_32(HFIR, hfir.d32);
+}
+
diff --git a/drivers/usb/host/shost/shost_oci.h b/drivers/usb/host/shost/shost_oci.h
new file mode 100644
index 0000000..8fa6bbb
--- /dev/null
+++ b/drivers/usb/host/shost/shost_oci.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] :s3c-otg-oci.h
+ * [Description] : The Header file defines the external
+ * and internal functions of OCI.
+ * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * [Department] : System LSI Division/Embedded S/W Platform
+ * [Created Date]: 2008/06/18
+ * [Revision History]
+ * (1) 2008/06/25 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * - Added some functions and data structure of OCI
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _OCI_H_
+#define _OCI_H_
+
+
+#include <mach/map.h> /* virtual address*/
+
+extern void otg_host_phy_init(void);
+#include <mach/regs-clock.h>
+
+int oci_channel_dealloc(u8 ch_num);
+
+u8 oci_start_transfer(struct sec_otghost *otghost, struct stransfer *st_t);
+int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num);
+
+u32 oci_get_frame_num(void);
+u16 oci_get_frame_interval(void);
+void oci_set_frame_interval(u16 intervl);
+
+
+#endif /* _OCI_H_ */
diff --git a/drivers/usb/host/shost/shost_readyq.c b/drivers/usb/host/shost/shost_readyq.c
new file mode 100644
index 0000000..9e41da5
--- /dev/null
+++ b/drivers/usb/host/shost/shost_readyq.c
@@ -0,0 +1,240 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : TransferReadyQ.c
+ * [Description] : The source file implements the internal
+ * functions of TransferReadyQ.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/04
+ * [Revision History]
+ * (1) 2008/06/04 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of TransferReadyQ.
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Optimizing for performance \n
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+static struct trans_ready_q periodic_trans_ready_q;
+static struct trans_ready_q nonperiodic_trans_ready_q;
+
+/******************************************************************************/
+/*!
+ * @name void init_transfer_ready_q(void)
+ *
+ * @brief this function initiates PeriodicTransferReadyQ
+ * and NonPeriodicTransferReadyQ.
+ *
+ *
+ * @param void
+ *
+ * @return void.
+ */
+/******************************************************************************/
+static void init_transfer_ready_q(void)
+{
+ otg_dbg(OTG_DBG_SCHEDULE, "start init_transfer_ready_q\n");
+
+ otg_list_init(&periodic_trans_ready_q.entity_list);
+ periodic_trans_ready_q.is_periodic = true;
+ periodic_trans_ready_q.entity_num = 0;
+ periodic_trans_ready_q.total_alloc_chnum = 0;
+ periodic_trans_ready_q.total_perio_bus_bandwidth = 0;
+
+ otg_list_init(&nonperiodic_trans_ready_q.entity_list);
+ nonperiodic_trans_ready_q.is_periodic = false;
+ nonperiodic_trans_ready_q.entity_num = 0;
+ nonperiodic_trans_ready_q.total_alloc_chnum = 0;
+ nonperiodic_trans_ready_q.total_perio_bus_bandwidth = 0;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int insert_ed_to_ready_q(struct ed *insert_ed,
+ * bool is_first)
+ *
+ * @brief this function inserts ed_t * to TransferReadyQ.
+ *
+ *
+ * @param [IN] insert_ed
+ * = indicates the ed_t to be inserted to TransferReadyQ.
+ * [IN] is_first
+ * = indicates whether the insert_ed is inserted
+ * as first entry of TransferReadyQ.
+ *
+ * @return
+ * USB_ERR_SUCCESS -if successes to insert the insert_ed to TransferReadyQ.
+ * USB_ERR_FAILl -if fails to insert the insert_ed to TransferReadyQ.
+ */
+/******************************************************************************/
+static int insert_ed_to_ready_q(struct ed *insert_ed, bool is_first)
+{
+ otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td_id %d, %d\n",
+ insert_ed->ed_id, insert_ed->num_td, is_first);
+
+ if (insert_ed->ed_desc.endpoint_type == BULK_TRANSFER ||
+ insert_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) {
+
+ if (is_first) {
+ otg_list_push_next(&insert_ed->readyq_list,
+ &nonperiodic_trans_ready_q.entity_list);
+ } else {
+ otg_list_push_prev(&insert_ed->readyq_list,
+ &nonperiodic_trans_ready_q.entity_list);
+ }
+ nonperiodic_trans_ready_q.entity_num++;
+
+ } else {
+ if (is_first) {
+ otg_list_push_next(&insert_ed->readyq_list,
+ &periodic_trans_ready_q.entity_list);
+ } else {
+ otg_list_push_prev(&insert_ed->readyq_list,
+ &periodic_trans_ready_q.entity_list);
+ }
+ periodic_trans_ready_q.entity_num++;
+ }
+ return USB_ERR_SUCCESS;
+}
+
+
+static u32 get_periodic_ready_q_entity_num(void)
+{
+ return periodic_trans_ready_q.entity_num;
+}
+
+/******************************************************************************/
+/*!
+ * @name int remove_ed_from_ready_q(ed_t *remove_ed)
+ *
+ * @brief this function removes ed_t * from TransferReadyQ.
+ *
+ *
+ * @param [IN] remove_ed
+ * = indicate the ed_t to be removed from TransferReadyQ.
+ *
+ * @return
+ * USB_ERR_SUCCESS -if successes to remove the remove_ed from TransferReadyQ.
+ * USB_ERR_FAILl -if fails to remove the remove_ed from TransferReadyQ.
+ */
+/******************************************************************************/
+static int remove_ed_from_ready_q(struct ed *remove_ed)
+{
+ otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td_id %d\n",
+ remove_ed->ed_id, remove_ed->num_td);
+
+ otg_list_pop(&remove_ed->readyq_list);
+
+ if (remove_ed->ed_desc.endpoint_type == BULK_TRANSFER ||
+ remove_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) {
+
+ nonperiodic_trans_ready_q.entity_num--;
+
+ } else
+ periodic_trans_ready_q.entity_num--;
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name int get_ed_from_ready_q(bool is_periodic,
+ * struct td **get_ed)
+ *
+ * @brief this function returns the first entity of TransferReadyQ.
+ * if there are some ed_t on TransferReadyQ,
+ * this function pops first ed_t from TransferReadyQ.
+ * So, the TransferReadyQ don's has the poped ed_t.
+ *
+ * @param [IN] is_periodic
+ * = indicate whether Periodic or not
+ * [OUT] get_ed
+ * = indicate the double pointer to store the address of first entity
+ * on TransferReadyQ.
+ *
+ * @return
+ * USB_ERR_SUCCESS -if successes to get frist ed_t from TransferReadyQ.
+ * USB_ERR_NO_ENTITY -if fails to get frist ed_t from TransferReadyQ
+ * because there is no entity on TransferReadyQ.
+ */
+/******************************************************************************/
+
+static int get_ed_from_ready_q(struct ed **get_ed, bool is_periodic)
+{
+ otg_list_head *qlist = NULL;
+
+ /* periodic transfer : control and bulk */
+ if (is_periodic) {
+
+ if (periodic_trans_ready_q.entity_num == 0)
+ return USB_ERR_NO_ENTITY;
+
+ qlist = periodic_trans_ready_q.entity_list.next;
+
+ if (!otg_list_empty(&periodic_trans_ready_q.entity_list)) {
+
+ *get_ed = otg_list_get_node(qlist,
+ struct ed, readyq_list);
+
+ if (qlist->prev == LIST_POISON2 ||
+ qlist->next == LIST_POISON1) {
+
+ printk(KERN_ERR "shost scheduler: get_ed_from_ready_q error\n");
+ periodic_trans_ready_q.entity_num = 0;
+
+ } else {
+ otg_list_pop(qlist);
+ periodic_trans_ready_q.entity_num--;
+ }
+ return USB_ERR_SUCCESS;
+
+ } else
+ return USB_ERR_NO_ENTITY;
+
+ /* non-periodic transfer : interrupt and ischo */
+ } else {
+
+ if (nonperiodic_trans_ready_q.entity_num == 0)
+ return USB_ERR_NO_ENTITY;
+
+ qlist = nonperiodic_trans_ready_q.entity_list.next;
+
+ if (!otg_list_empty(&nonperiodic_trans_ready_q.entity_list)) {
+
+ *get_ed = otg_list_get_node(qlist,
+ struct ed, readyq_list);
+
+ if (qlist->prev == LIST_POISON2 ||
+ qlist->next == LIST_POISON1) {
+
+ printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n");
+ nonperiodic_trans_ready_q.entity_num = 0;
+
+ } else {
+ otg_list_pop(qlist);
+ nonperiodic_trans_ready_q.entity_num--;
+ }
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_NO_ENTITY;
+ }
+}
+
+
diff --git a/drivers/usb/host/shost/shost_regs.h b/drivers/usb/host/shost/shost_regs.h
new file mode 100644
index 0000000..d560567
--- /dev/null
+++ b/drivers/usb/host/shost/shost_regs.h
@@ -0,0 +1,747 @@
+/****************************************************************************
+* (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+*
+* [File Name] : s3c-otg-common-regdef.h
+* [Description] :
+*
+* [Author] : Kyu Hyeok Jang { kyuhyeok.jang@samsung.com }
+* [Department] : System LSI Division/Embedded Software Center
+* [Created Date]: 2007/12/15
+* [Revision History]
+* (1) 2007/12/15 by Kyu Hyeok Jang { kyuhyeok.jang@samsung.com }
+* - Created
+*
+****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _OTG_REG_DEF_H
+#define _OTG_REG_DEF_H
+
+
+#define GOTGCTL 0x000 /* OTG Control & Status */
+#define GOTGINT 0x004 /* OTG Interrupt */
+#define GAHBCFG 0x008 /* Core AHB Configuration */
+#define GUSBCFG 0x00C /* Core USB Configuration */
+#define GRSTCTL 0x010 /* Core Reset */
+#define GINTSTS 0x014 /* Core Interrupt */
+#define GINTMSK 0x018 /* Core Interrupt Mask */
+#define GRXSTSR 0x01C /* Receive Status Debug Read/Status Read */
+#define GRXSTSP 0x020 /* Receive Status Debug Pop/Status Pop */
+#define GRXFSIZ 0x024 /* Receive FIFO Size */
+#define GNPTXFSIZ 0x028 /* Non-Periodic Transmit FIFO Size */
+#define GNPTXSTS 0x02C /* Non-Periodic Transmit FIFO/Queue Status */
+#define GPVNDCTL 0x034 /* PHY Vendor Control */
+#define GGPIO 0x038 /* General Purpose I/O */
+#define GUID 0x03C /* User ID */
+#define GSNPSID 0x040 /* Synopsys ID */
+#define GHWCFG1 0x044 /* User HW Config1 */
+#define GHWCFG2 0x048 /* User HW Config2 */
+#define GHWCFG3 0x04C /* User HW Config3 */
+#define GHWCFG4 0x050 /* User HW Config4 */
+
+#define HPTXFSIZ 0x100 /* Host Periodic Transmit FIFO Size */
+#define DPTXFSIZ1 0x104 /* Device Periodic Transmit FIFO-1 Size */
+#define DPTXFSIZ2 0x108 /* Device Periodic Transmit FIFO-2 Size */
+#define DPTXFSIZ3 0x10C /* Device Periodic Transmit FIFO-3 Size */
+#define DPTXFSIZ4 0x110 /* Device Periodic Transmit FIFO-4 Size */
+#define DPTXFSIZ5 0x114 /* Device Periodic Transmit FIFO-5 Size */
+#define DPTXFSIZ6 0x118 /* Device Periodic Transmit FIFO-6 Size */
+#define DPTXFSIZ7 0x11C /* Device Periodic Transmit FIFO-7 Size */
+#define DPTXFSIZ8 0x120 /* Device Periodic Transmit FIFO-8 Size */
+#define DPTXFSIZ9 0x124 /* Device Periodic Transmit FIFO-9 Size */
+#define DPTXFSIZ10 0x128 /* Device Periodic Transmit FIFO-10 Size */
+#define DPTXFSIZ11 0x12C /* Device Periodic Transmit FIFO-11 Size */
+#define DPTXFSIZ12 0x130 /* Device Periodic Transmit FIFO-12 Size */
+#define DPTXFSIZ13 0x134 /* Device Periodic Transmit FIFO-13 Size */
+#define DPTXFSIZ14 0x138 /* Device Periodic Transmit FIFO-14 Size */
+#define DPTXFSIZ15 0x13C /* Device Periodic Transmit FIFO-15 Size */
+
+/*********************************************************************
+ * Host Mode Registers
+ *********************************************************************/
+/* Host Global Registers */
+
+/* Channel specific registers */
+#define HCCHAR_ADDR (0x500)
+#define HCCHAR(n) (0x500 + ((n)*0x20))
+#define HCSPLT(n) (0x504 + ((n)*0x20))
+#define HCINT(n) (0x508 + ((n)*0x20))
+#define HCINTMSK(n) (0x50C + ((n)*0x20))
+#define HCTSIZ(n) (0x510 + ((n)*0x20))
+#define HCDMA(n) (0x514 + ((n)*0x20))
+
+#define HCFG 0x400 /* Host Configuration */
+#define HFIR 0x404 /* Host Frame Interval */
+#define HFNUM 0x408 /* Host Frame Number/Frame Time Remaining */
+#define HPTXSTS 0x410 /* Host Periodic Transmit FIFO/Queue Status */
+#define HAINT 0x414 /* Host All Channels Interrupt */
+#define HAINTMSK 0x418 /* Host All Channels Interrupt Mask */
+
+/* Host Port Control & Status Registers */
+#define HPRT 0x440 /* Host Port Control & Status */
+
+#define EP_FIFO(n) (0x1000 + ((n)*0x1000))
+
+#define PCGCCTL 0x0E00
+
+#define BASE_REGISTER_OFFSET 0x0
+#define REGISTER_SET_SIZE 0x200
+
+/* Power Reg Bits */
+#define USB_RESET 0x8
+#define MCU_RESUME 0x4
+#define SUSPEND_MODE 0x2
+#define SUSPEND_MODE_ENABLE_CTRL 0x1
+
+/* EP0 CSR */
+#define EP0_OUT_PACKET_RDY 0x1
+#define EP0_IN_PACKET_RDY 0x2
+#define EP0_SENT_STALL 0x4
+#define DATA_END 0x8
+#define SETUP_END 0x10
+#define EP0_SEND_STALL 0x20
+#define SERVICED_OUT_PKY_RDY 0x40
+#define SERVICED_SETUP_END 0x80
+
+/* IN_CSR1_REG Bit definitions */
+#define IN_PACKET_READY 0x1
+#define UNDER_RUN 0x4 /* Iso Mode Only */
+#define FLUSH_IN_FIFO 0x8
+#define IN_SEND_STALL 0x10
+#define IN_SENT_STALL 0x20
+#define IN_CLR_DATA_TOGGLE 0x40
+
+/* OUT_CSR1_REG Bit definitions */
+#define OUT_PACKET_READY 0x1
+#define FLUSH_OUT_FIFO 0x10
+#define OUT_SEND_STALL 0x20
+#define OUT_SENT_STALL 0x40
+#define OUT_CLR_DATA_TOGGLE 0x80
+
+/* IN_CSR2_REG Bit definitions */
+#define IN_DMA_INT_DISABLE 0x10
+#define SET_MODE_IN 0x20
+
+#define EPTYPE (0x3<<18)
+#define SET_TYPE_CONTROL (0x0<<18)
+#define SET_TYPE_ISO (0x1<<18)
+#define SET_TYPE_BULK (0x2<<18)
+#define SET_TYPE_INTERRUPT (0x3<<18)
+
+#define AUTO_MODE 0x80
+
+/* OUT_CSR2_REG Bit definitions */
+#define AUTO_CLR 0x40
+#define OUT_DMA_INT_DISABLE 0x20
+
+/* Can be used for Interrupt and Interrupt Enable Reg - common bit def */
+#define EP0_IN_INT (0x1<<0)
+#define EP1_IN_INT (0x1<<1)
+#define EP2_IN_INT (0x1<<2)
+#define EP3_IN_INT (0x1<<3)
+#define EP4_IN_INT (0x1<<4)
+#define EP5_IN_INT (0x1<<5)
+#define EP6_IN_INT (0x1<<6)
+#define EP7_IN_INT (0x1<<7)
+#define EP8_IN_INT (0x1<<8)
+#define EP9_IN_INT (0x1<<9)
+#define EP10_IN_INT (0x1<<10)
+#define EP11_IN_INT (0x1<<11)
+#define EP12_IN_INT (0x1<<12)
+#define EP13_IN_INT (0x1<<13)
+#define EP14_IN_INT (0x1<<14)
+#define EP15_IN_INT (0x1<<15)
+#define EP0_OUT_INT (0x1<<16)
+#define EP1_OUT_INT (0x1<<17)
+#define EP2_OUT_INT (0x1<<18)
+#define EP3_OUT_INT (0x1<<19)
+#define EP4_OUT_INT (0x1<<20)
+#define EP5_OUT_INT (0x1<<21)
+#define EP6_OUT_INT (0x1<<22)
+#define EP7_OUT_INT (0x1<<23)
+#define EP8_OUT_INT (0x1<<24)
+#define EP9_OUT_INT (0x1<<25)
+#define EP10_OUT_INT (0x1<<26)
+#define EP11_OUT_INT (0x1<<27)
+#define EP12_OUT_INT (0x1<<28)
+#define EP13_OUT_INT (0x1<<29)
+#define EP14_OUT_INT (0x1<<30)
+#define EP15_OUT_INT (0x1<<31)
+
+/* GOTGINT */
+#define SesEndDet (0x1<<2)
+
+/* GRSTCTL */
+#define TxFFlsh (0x1<<5)
+#define RxFFlsh (0x1<<4)
+#define INTknQFlsh (0x1<<3)
+#define FrmCntrRst (0x1<<2)
+#define HSftRst (0x1<<1)
+#define CSftRst (0x1<<0)
+
+#define CLEAR_ALL_EP_INTRS 0xffffffff
+
+/* Bits to write to EP_INT_EN_REG - Use CLEAR */
+#define EP_INTERRUPT_DISABLE_ALL 0x0
+
+/* DMA control register bit definitions */
+#define RUN_OB 0x80
+#define STATE 0x70
+#define DEMAND_MODE 0x8
+#define OUT_DMA_RUN 0x4
+#define IN_DMA_RUN 0x2
+#define DMA_MODE_EN 0x1
+
+
+#define REAL_PHYSICAL_ADDR_EP0_FIFO (0x520001c0) /*Endpoint 0 FIFO */
+#define REAL_PHYSICAL_ADDR_EP1_FIFO (0x520001c4) /*Endpoint 1 FIFO */
+#define REAL_PHYSICAL_ADDR_EP2_FIFO (0x520001c8) /*Endpoint 2 FIFO */
+#define REAL_PHYSICAL_ADDR_EP3_FIFO (0x520001cc) /*Endpoint 3 FIFO */
+#define REAL_PHYSICAL_ADDR_EP4_FIFO (0x520001d0) /*Endpoint 4 FIFO */
+
+/* GAHBCFG */
+#define MODE_DMA (1<<5)
+#define MODE_SLAVE (0<<5)
+#define BURST_SINGLE (0<<1)
+#define BURST_INCR (1<<1)
+#define BURST_INCR4 (3<<1)
+#define BURST_INCR8 (5<<1)
+#define BURST_INCR16 (7<<1)
+#define GBL_INT_MASK (0<<0)
+#define GBL_INT_UNMASK (1<<0)
+
+
+/*********************************************************************
+ * Global CSR
+ *********************************************************************/
+
+/* GAHBCFG
+ * OTG AHB Configuration Register
+ * p1359
+ * d201
+ */
+typedef union _gahbcfg_t {
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ unsigned glblintrmsk:1;
+#define GAHBCFG_GLBINT_ENABLE 1
+ unsigned hburstlen:4;
+#define INT_DMA_MODE_SINGLE 0
+#define INT_DMA_MODE_INCR 1
+#define INT_DMA_MODE_INCR4 3
+#define INT_DMA_MODE_INCR8 5
+#define INT_DMA_MODE_INCR16 7
+ unsigned dmaenable:1;
+#define GAHBCFG_DMAENABLE 1
+ unsigned reserved:1;
+ unsigned nptxfemplvl:1;
+ unsigned ptxfemplvl:1;
+#define GAHBCFG_TXFEMPTYLVL_EMPTY 1
+#define GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0
+ unsigned reserved9_31:22;
+ } b;
+} gahbcfg_t;
+
+
+/* GUSBCFG
+ * OTG USB Configuration Register
+ * p1360
+ * d202
+ */
+typedef union _gusbcfg_t {
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ unsigned toutcal:3;
+ unsigned phyif:1;
+ unsigned ulpi_utmi_sel:1;
+ unsigned fsintf:1;
+ unsigned physel:1;
+ unsigned ddrsel:1;
+ unsigned srpcap:1;
+ unsigned hnpcap:1;
+ unsigned usbtrdtim:4;
+ unsigned nptxfrwnden:1;
+ unsigned phylpwrclksel:1;
+ unsigned reserved:13;
+ unsigned forcehstmode:1;
+ unsigned reserved2:2;
+ } b;
+} gusbcfg_t;
+
+
+/* GRSTCTL
+ * Core Reset Register
+ * p1362
+ * d208
+ */
+typedef union grstctl_t {
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ unsigned csftrst:1;
+ unsigned hsftrst:1;
+ unsigned hstfrm:1;
+ unsigned intknqflsh:1;
+ unsigned rxfflsh:1;
+ unsigned txfflsh:1;
+ unsigned txfnum:5;
+ unsigned reserved11_29:19;
+ unsigned dmareq:1;
+ unsigned ahbidle:1;
+ } b;
+} grstctl_t;
+
+
+/* GINTSTS
+ * Core Interrupt Register
+ * p1364
+ * d212
+ */
+typedef union _gintsts_t {
+ /** raw register data */
+ u32 d32;
+#define SOF_INTR_MASK 0x0008
+ /** register bits */
+ struct {
+#define HOST_MODE 1
+#define DEVICE_MODE 0
+ unsigned curmode:1;
+#define OTG_HOST_MODE 1
+#define OTG_DEVICE_MODE 0
+
+ unsigned modemismatch:1;
+ unsigned otgintr:1;
+ unsigned sofintr:1;
+ unsigned rxstsqlvl:1;
+ unsigned nptxfempty:1;
+ unsigned ginnakeff:1;
+ unsigned goutnakeff:1;
+ unsigned reserved8:1;
+ unsigned i2cintr:1;
+ unsigned erlysuspend:1;
+ unsigned usbsuspend:1;
+ unsigned usbreset:1;
+ unsigned enumdone:1;
+ unsigned isooutdrop:1;
+ unsigned eopframe:1;
+ unsigned intokenrx:1;
+ unsigned epmismatch:1;
+ unsigned inepint:1;
+ unsigned outepintr:1;
+ unsigned incompisoin:1;
+ unsigned incompisoout:1;
+ unsigned reserved22_23:2;
+ unsigned portintr:1;
+ unsigned hcintr:1;
+ unsigned ptxfempty:1;
+ unsigned reserved27:1;
+ unsigned conidstschng:1;
+ unsigned disconnect:1;
+ unsigned sessreqintr:1;
+ unsigned wkupintr:1;
+ } b;
+} gintsts_t;
+
+
+/* GINTMSK
+ * Core Interrupt Mask Register
+ * p1369
+ * d217
+ */
+typedef union _gintmsk_t {
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ unsigned reserved0:1;
+ unsigned modemismatch:1;
+ unsigned otgintr:1;
+ unsigned sofintr:1;
+ unsigned rxstsqlvl:1;
+ unsigned nptxfempty:1;
+ unsigned ginnakeff:1;
+ unsigned goutnakeff:1;
+ unsigned reserved8:1;
+ unsigned i2cintr:1;
+ unsigned erlysuspend:1;
+ unsigned usbsuspend:1;
+ unsigned usbreset:1;
+ unsigned enumdone:1;
+ unsigned isooutdrop:1;
+ unsigned eopframe:1;
+ unsigned reserved16:1;
+ unsigned epmismatch:1;
+ unsigned inepintr:1;
+ unsigned outepintr:1;
+ unsigned incompisoin:1;
+ unsigned incompisoout:1;
+ unsigned reserved22_23:2;
+ unsigned portintr:1;
+ unsigned hcintr:1;
+ unsigned ptxfempty:1;
+ unsigned reserved27:1;
+ unsigned conidstschng:1;
+ unsigned disconnect:1;
+ unsigned sessreqintr:1;
+ unsigned wkupintr:1;
+ } b;
+} gintmsk_t;
+
+
+/* GRXSTSR/GRXSTSP
+ * Host Mode Receive Status Debug Read/Status Read and Pop Registers
+ * p1370
+ * d219
+ */
+typedef union _grxstsr_t {
+ /* raw register data */
+ u32 d32;
+
+ /* register bits */
+ struct {
+ unsigned chnum:4;
+ unsigned bcnt:11;
+ unsigned dpid:2;
+ unsigned pktsts:4;
+ unsigned Reserved:11;
+ } b;
+} grxstsr_t;
+
+
+/* User HW Config2 Register
+ * d230
+ */
+typedef union _ghwcfg2_t {
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ /* GHWCFG2 */
+ unsigned op_mode:3;
+#define MODE_HNP_SRP_CAPABLE 0
+#define MODE_SRP_ONLY_CAPABLE 1
+#define MODE_NO_HNP_SRP_CAPABLE 2
+#define MODE_SRP_CAPABLE_DEVICE 3
+#define MODE_NO_SRP_CAPABLE_DEVICE 4
+#define MODE_SRP_CAPABLE_HOST 5
+#define MODE_NO_SRP_CAPABLE_HOST 6
+
+ unsigned architecture:2;
+#define HWCFG2_ARCH_SLAVE_ONLY 0x00
+#define HWCFG2_ARCH_EXT_DMA 0x01
+#define HWCFG2_ARCH_INT_DMA 0x02
+
+ unsigned point2point:1;
+ unsigned hs_phy_type:2;
+ unsigned fs_phy_type:2;
+ unsigned num_dev_ep:4;
+ unsigned num_host_chan:4;
+ unsigned perio_ep_supported:1;
+ unsigned dynamic_fifo:1;
+ unsigned rx_status_q_depth:2;
+ unsigned nonperio_tx_q_depth:2;
+ unsigned host_perio_tx_q_depth:2;
+ unsigned dev_token_q_depth:5;
+ unsigned reserved31:1;
+ } b;
+} ghwcfg2_t;
+
+
+/*********************************************************************
+ * Host Mode Registers
+ *********************************************************************/
+
+/* Host Configuration Register
+ * d247
+ */
+typedef union _hcfg_t {
+ /** raw register data */
+ u32 d32;
+
+ /** register bits */
+ struct {
+ /** FS/LS Phy Clock Select */
+ unsigned fslspclksel:2;
+#define HCFG_30_60_MHZ 0
+#define HCFG_48_MHZ 1
+#define HCFG_6_MHZ 2
+
+ /** FS/LS Only Support */
+ unsigned fslssupp:1;
+ unsigned reserved3_31:29;
+ } b;
+} hcfg_t;
+
+
+/* Host Frame Interval Register
+ * d247
+ */
+typedef union _hfir_t {
+ /* raw register data */
+ u32 d32;
+
+ /* register bits */
+ struct {
+ unsigned frint:16;
+ unsigned Reserved:16;
+ } b;
+} hfir_t;
+
+/* Host Frame Number/Frame Time Remaining Register
+ * d248
+ */
+typedef union _hfnum_t {
+ /* raw register data */
+ u32 d32;
+
+ /* register bits */
+ struct {
+ unsigned frnum:16;
+#define HFNUM_MAX_FRNUM 0x3FFF
+ unsigned frrem:16;
+ } b;
+} hfnum_t;
+
+
+/* Host Channel Interrupt Mask Register
+ d257
+ */
+typedef union _hcintmsk_t {
+ /* raw register data */
+ u32 d32;
+
+ /* register bits */
+ struct {
+ unsigned xfercompl:1;
+ unsigned chhltd:1;
+ unsigned ahberr:1;
+ unsigned stall:1;
+ unsigned nak:1;
+ unsigned ack:1;
+ unsigned nyet:1;
+ unsigned xacterr:1;
+ unsigned bblerr:1;
+ unsigned frmovrun:1;
+ unsigned datatglerr:1;
+ unsigned reserved:21;
+ } b;
+} hcintmsk_t;
+
+
+/* Host Channel Interrupt Register
+ d254
+ */
+typedef union _hcintn_t {
+ u32 d32;
+ struct {
+ u32 xfercompl:1;
+ u32 chhltd:1;
+ u32 abherr:1;
+ u32 stall:1;
+ u32 nak:1;
+ u32 ack:1;
+ u32 nyet:1;
+ u32 xacterr:1;
+ u32 bblerr:1;
+ u32 frmovrun:1;
+ u32 datatglerr:1;
+ u32 reserved:21;
+ } b;
+} hcintn_t;
+
+
+/* Host All Channels Interrupt Register
+ d250
+ */
+
+#define MAX_COUNT 10000
+#define INT_ALL 0xffffffff
+
+typedef union _haint_t {
+ u32 d32;
+ struct {
+ u32 channel_intr_0:1;
+ u32 channel_intr_1:1;
+ u32 channel_intr_2:1;
+ u32 channel_intr_3:1;
+ u32 channel_intr_4:1;
+ u32 channel_intr_5:1;
+ u32 channel_intr_6:1;
+ u32 channel_intr_7:1;
+ u32 channel_intr_8:1;
+ u32 channel_intr_9:1;
+ u32 channel_intr_10:1;
+ u32 channel_intr_11:1;
+ u32 channel_intr_12:1;
+ u32 channel_intr_13:1;
+ u32 channel_intr_14:1;
+ u32 channel_intr_15:1;
+ u32 reserved1:16;
+ } b;
+} haint_t;
+
+
+/* Host Port Control and Status Register
+ * d250
+ */
+typedef union _hprt_t {
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ unsigned prtconnsts:1;
+ unsigned prtconndet:1;
+ unsigned prtena:1;
+ unsigned prtenchng:1;
+ unsigned prtovrcurract:1;
+ unsigned prtovrcurrchng:1;
+ unsigned prtres:1;
+ unsigned prtsusp:1;
+ unsigned prtrst:1;
+ unsigned reserved9:1;
+ unsigned prtlnsts:2;
+ unsigned prtpwr:1;
+ unsigned prttstctl:4;
+ unsigned prtspd:2;
+#define HPRT0_PRTSPD_HIGH_SPEED 0
+#define HPRT0_PRTSPD_FULL_SPEED 1
+#define HPRT0_PRTSPD_LOW_SPEED 2
+ unsigned reserved19_31:13;
+ } b;
+} hprt_t;
+
+
+/* Port status for the HC */
+#define HCD_DRIVE_RESET 0x0001
+#define HCD_SEND_SETUP 0x0002
+
+#define HC_MAX_PKT_COUNT 511
+#define HC_MAX_TRANSFER_SIZE 65535
+#define MAXP_SIZE_64BYTE 64
+#define MAXP_SIZE_512BYTE 512
+#define MAXP_SIZE_1024BYTE 1024
+
+/* Host Channel-n Charracteristics Register
+ * d253
+ */
+typedef union _hcchar_t {
+ /* raw register data */
+ u32 d32;
+
+ /* register bits */
+ struct {
+ /* Maximum packet size in bytes */
+ unsigned mps:11;
+
+ /* Endpoint number */
+ unsigned epnum:4;
+
+ /* 0: OUT, 1: IN */
+ unsigned epdir:1;
+#define HCDIR_OUT 0
+#define HCDIR_IN 1
+
+ unsigned reserved:1;
+
+ /* 0: Full/high speed device, 1: Low speed device */
+ unsigned lspddev:1;
+
+ /* 0: Control, 1: Isoc, 2: Bulk, 3: Intr */
+ unsigned eptype:2;
+#define OTG_EP_TYPE_CONTROL 0
+#define OTG_EP_TYPE_ISOC 1
+#define OTG_EP_TYPE_BULK 2
+#define OTG_EP_TYPE_INTR 3
+
+ /* Packets per frame for periodic transfers. 0 is reserved. */
+ unsigned multicnt:2;
+
+ /* Device address */
+ unsigned devaddr:7;
+
+ /* Frame to transmit periodic transaction. */
+ /* 0: even, 1: odd */
+ unsigned oddfrm:1;
+
+ /* Channel disable */
+ unsigned chdis:1;
+
+ /* Channel enable */
+ unsigned chen:1;
+ } b;
+} hcchar_t;
+
+/* Host Channel-n Transfer Size Register
+ * d257
+ */
+typedef union _hctsiz_t {
+ /* raw register data */
+ u32 d32;
+
+ /* register bits */
+ struct {
+ /* Total transfer size in bytes */
+ unsigned xfersize:19;
+
+ /* Data packets to transfer */
+ unsigned pktcnt:10;
+
+ /* Packet ID for next data packet */
+ /* 0: DATA0 */
+ /* 1: DATA2 */
+ /* 2: DATA1 */
+ /* 3: MDATA (non-Control), SETUP (Control) */
+ unsigned pid:2;
+#define HCTSIZ_DATA0 0
+#define HCTSIZ_DATA1 2
+#define HCTSIZ_DATA2 1
+#define HCTSIZ_MDATA 3
+#define HCTSIZ_SETUP 3
+
+ /* Do PING protocol when 1 */
+ unsigned dopng:1;
+ } b;
+} hctsiz_t;
+
+
+/* The Power and Clock Gating Control Register
+ d292
+ */
+typedef union _pcgcctl_t {
+
+ /** raw register data */
+ u32 d32;
+ /** register bits */
+ struct {
+ unsigned stoppclk:1;
+ unsigned gatehclk:1;
+ unsigned pwrclmp:1;
+ unsigned rstpdwnmodule:1;
+ unsigned physuspended:1;
+ unsigned Reserved5_31:27;
+ } b;
+} pcgcctl_t;
+
+
+#endif
+
+
diff --git a/drivers/usb/host/shost/shost_roothub.c b/drivers/usb/host/shost/shost_roothub.c
new file mode 100644
index 0000000..3e2541d
--- /dev/null
+++ b/drivers/usb/host/shost/shost_roothub.c
@@ -0,0 +1,579 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : RootHub.c
+ * [Description] : The file implement the external
+ * and internal functions of RootHub
+ * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * [Department] : System LSI Division/Embedded S/W Platform
+ * [Created Date]: 2009/02/10
+ * [Revision History]
+ * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * - Created this file and implements functions of RootHub
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+
+/**
+ * void bus_suspend(void)
+ *
+ * @brief Make suspend status when this platform support PM Mode
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void bus_suspend(void)
+{
+ hprt_t hprt;
+ pcgcctl_t pcgcctl;
+
+ otg_dbg(OTG_DBG_ROOTHUB, " bus_suspend\n");
+
+ hprt.d32 = 0;
+ pcgcctl.d32 = 0;
+
+ hprt.b.prtsusp = 1;
+ update_reg_32(HPRT, hprt.d32);
+
+ pcgcctl.b.pwrclmp = 1;
+ update_reg_32(PCGCCTL, pcgcctl.d32);
+ udelay(1);
+
+ pcgcctl.b.rstpdwnmodule = 1;
+ update_reg_32(PCGCCTL, pcgcctl.d32);
+ udelay(1);
+
+ pcgcctl.b.stoppclk = 1;
+ update_reg_32(PCGCCTL, pcgcctl.d32);
+ udelay(1);
+}
+
+/**
+ * int bus_resume(struct sec_otghost *otghost)
+ *
+ * @brief Make resume status when this platform support PM Mode
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ *
+ * @remark
+ *
+ */
+static int bus_resume(struct sec_otghost *otghost)
+{
+ /*
+ hprt_t hprt;
+ pcgcctl_t pcgcctl;
+ hprt.d32 = 0;
+ pcgcctl.d32 = 0;
+
+ pcgcctl.b.stoppclk = 1;
+ clear_reg_32(PCGCCTL,pcgcctl.d32);
+ udelay(1);
+
+ pcgcctl.b.pwrclmp = 1;
+ clear_reg_32(PCGCCTL,pcgcctl.d32);
+ udelay(1);
+
+ pcgcctl.b.rstpdwnmodule = 1;
+ clear_reg_32(PCGCCTL,pcgcctl.d32);
+ udelay(1);
+
+ hprt.b.prtres = 1;
+ update_reg_32(HPRT, hprt.d32);
+ mdelay(20);
+
+ clear_reg_32(HPRT, hprt.d32);
+ */
+ otg_dbg(OTG_DBG_ROOTHUB, "bus_resume()......\n");
+ otg_dbg(OTG_DBG_ROOTHUB, "wait for 50 ms...\n");
+
+ mdelay(50);
+
+ if (oci_init(otghost) == USB_ERR_SUCCESS) {
+ if (oci_start() == USB_ERR_SUCCESS) {
+ otg_dbg(OTG_DBG_ROOTHUB, "OTG Init Success\n");
+ return USB_ERR_SUCCESS;
+ }
+ }
+
+ return USB_ERR_FAIL;
+}
+
+static void setPortPower(bool on)
+{
+#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS
+ hprt_t hprt = {.d32 = 0};
+
+ if (on) {
+ hprt.d32 = read_reg_32(HPRT);
+
+ if (!hprt.b.prtpwr) {
+ /*hprt.d32 = 0;*/
+ hprt.b.prtpwr = 1;
+ write_reg_32(HPRT, hprt.d32);
+ }
+ } else {
+ hprt.b.prtpwr = 1;
+ clear_reg_32(HPRT, hprt.d32);
+ }
+#else
+ otg_dbg(true, "turn %s Vbus\n", on ? "on" : "off");
+#endif
+}
+
+/**
+ * int get_otg_port_status(const u8 port, char* status)
+ *
+ * @brief Get port change bitmap information
+ *
+ * @param [IN] port : port number
+ * [OUT] status : buffer to store bitmap information
+ *
+ * @returnUSB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ *
+ * @remark
+ *
+ */
+static inline int get_otg_port_status(
+ struct usb_hcd *hcd, const u8 port, char *status)
+{
+
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+ /* return root_hub_feature(port, GetPortStatus, NULL, status); */
+#if 0
+ /* for debug */
+ hprt_t hprt;
+
+ hprt.d32 = read_reg_32(HPRT);
+
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "HPRT:spd=%d,pwr=%d,lnsts=%d,rst=%d,susp=%d,res=%d,"
+ "ovrcurract=%d,ena=%d,connsts=%d\n",
+ hprt.b.prtspd,
+ hprt.b.prtpwr,
+ hprt.b.prtlnsts,
+ hprt.b.prtrst,
+ hprt.b.prtsusp,
+ hprt.b.prtres,
+ hprt.b.prtovrcurract,
+ hprt.b.prtena,
+ hprt.b.prtconnsts);
+
+#endif
+ status[port] = 0;
+ status[port] |= (otghost->port_flag.b.port_connect_status_change ||
+ otghost->port_flag.b.port_reset_change ||
+ otghost->port_flag.b.port_enable_change ||
+ otghost->port_flag.b.port_suspend_change ||
+ otghost->port_flag.b.port_over_current_change) << 1;
+
+#if 0
+ /* for debug */
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "connect:%d,reset:%d,enable:%d,suspend:%d,over_current:%d\n",
+ otghost->port_flag.b.port_connect_status_change,
+ otghost->port_flag.b.port_reset_change,
+ otghost->port_flag.b.port_enable_change,
+ otghost->port_flag.b.port_suspend_change,
+ otghost->port_flag.b.port_over_current_change);
+#endif
+
+ if (status[port]) {
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " Root port status changed\n");
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " port_connect_status_change: %d\n",
+ otghost->port_flag.b.port_connect_status_change);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " port_reset_change: %d\n",
+ otghost->port_flag.b.port_reset_change);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " port_enable_change: %d\n",
+ otghost->port_flag.b.port_enable_change);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " port_suspend_change: %d\n",
+ otghost->port_flag.b.port_suspend_change);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " port_over_current_change: %d\n",
+ otghost->port_flag.b.port_over_current_change);
+ }
+
+ return (status[port] != 0);
+}
+
+/**
+ * int reset_and_enable_port(struct usb_hcd *hcd, const u8 port)
+ *
+ * @brief Reset port and make enable status the specific port
+ *
+ * @param [IN] port : port number
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ *
+ * @remark
+ *
+ */
+static int reset_and_enable_port(struct usb_hcd *hcd, const u8 port)
+{
+ hprt_t hprt;
+ u32 count = 0;
+ u32 max_error_count = 10000;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ hprt.d32 = read_reg_32(HPRT);
+
+ otg_dbg(OTG_DBG_ROOTHUB,
+ " reset_and_enable_port\n");
+
+ if (hprt.b.prtconnsts == 0) {
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "No Attached Device, HPRT = 0x%x\n", hprt.d32);
+
+ otghost->port_flag.b.port_connect_status_change = 1;
+ otghost->port_flag.b.port_connect_status = 0;
+
+ return USB_ERR_FAIL;
+ }
+
+ if (!hprt.b.prtena) {
+ hprt.b.prtrst = 1; /* drive reset */
+ write_reg_32(HPRT, hprt.d32);
+
+ mdelay(60);
+ hprt.b.prtrst = 0;
+ write_reg_32(HPRT, hprt.d32);
+
+ do {
+ hprt.d32 = read_reg_32(HPRT);
+
+ if (count > max_error_count) {
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "Port Reset Fail : HPRT : 0x%x\n", hprt.d32);
+ return USB_ERR_FAIL;
+ }
+ count++;
+
+ } while (!hprt.b.prtena);
+
+ }
+ return USB_ERR_SUCCESS;
+}
+
+/**
+ * int root_hub_feature(
+ * struct usb_hcd *hcd,
+ * const u8 port,
+ * const u16 type_req,
+ * const u16 feature,
+ * void* buf)
+ *
+ * @brief Get port change bitmap information
+ *
+ * @param [IN] port : port number
+ * [IN] type_req : request type of hub feature as usb 2.0 spec
+ * [IN] feature : hub feature as usb 2.0 spec
+ * [OUT] status : buffer to store results
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ *
+ * @remark
+ *
+ */
+static inline int root_hub_feature(
+ struct usb_hcd *hcd,
+ const u8 port,
+ const u16 type_req,
+ const u16 feature,
+ void *buf)
+{
+ int retval = USB_ERR_SUCCESS;
+ struct hub_descriptor *desc = NULL;
+ u32 port_status = 0;
+ hprt_t hprt = {.d32 = 0};
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_ROOTHUB, " root_hub_feature\n");
+
+ switch (type_req) {
+ case ClearHubFeature:
+ otg_dbg(OTG_DBG_ROOTHUB, "case ClearHubFeature\n");
+ switch (feature) {
+ case C_HUB_LOCAL_POWER:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearHubFeature -C_HUB_LOCAL_POWER\n");
+ break;
+ case C_HUB_OVER_CURRENT:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearHubFeature -C_HUB_OVER_CURRENT\n");
+ /* Nothing required here */
+ break;
+ default:
+ retval = USB_ERR_FAIL;
+ }
+ break;
+
+ case ClearPortFeature:
+ otg_dbg(OTG_DBG_ROOTHUB, "case ClearPortFeature\n");
+ switch (feature) {
+ case USB_PORT_FEAT_ENABLE:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_ENABLE\n");
+ hprt.b.prtena = 1;
+ update_reg_32(HPRT, hprt.d32);
+ break;
+
+ case USB_PORT_FEAT_SUSPEND:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_SUSPEND\n");
+ bus_resume(otghost);
+ break;
+
+ case USB_PORT_FEAT_POWER:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_POWER\n");
+ setPortPower(false);
+ break;
+
+ case USB_PORT_FEAT_INDICATOR:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_INDICATOR\n");
+ /* Port inidicator not supported */
+ break;
+
+ case USB_PORT_FEAT_C_CONNECTION:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_C_CONNECTION\n");
+ /* Clears drivers internal connect status change flag */
+ otghost->port_flag.b.port_connect_status_change = 0;
+ break;
+
+ case USB_PORT_FEAT_C_RESET:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_C_RESET\n");
+ /* Clears the driver's internal Port Reset Change flag*/
+ otghost->port_flag.b.port_reset_change = 0;
+ break;
+
+ case USB_PORT_FEAT_C_ENABLE:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_C_ENABLE\n");
+ /* Clears Port Enable/Disable Change flag */
+ otghost->port_flag.b.port_enable_change = 0;
+ break;
+
+ case USB_PORT_FEAT_C_SUSPEND:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature -USB_PORT_FEAT_C_SUSPEND\n");
+ /* Clears the driver's internal Port Suspend
+ * Change flag, which is set when resume signaling on
+ * the host port is complete */
+ otghost->port_flag.b.port_suspend_change = 0;
+ break;
+
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature - USB_PORT_FEAT_C_OVER_CURRENT\n");
+ otghost->port_flag.b.port_over_current_change = 0;
+ break;
+
+ default:
+ retval = USB_ERR_FAIL;
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case ClearPortFeature - FAIL\n");
+ }
+ break;
+
+ case GetHubDescriptor:
+ otg_dbg(OTG_DBG_ROOTHUB, "case GetHubDescriptor\n");
+ desc = (struct hub_descriptor *)buf;
+ desc->desc_length = 9;
+ desc->desc_type = 0x29;
+ desc->port_number = 1;
+ desc->hub_characteristics = 0x08;
+ desc->power_on_to_power_good = 1;
+ desc->hub_control_current = 0;
+ desc->DeviceRemovable[0] = 0;
+ desc->DeviceRemovable[1] = 0xff;
+ break;
+
+ case GetHubStatus:
+ otg_dbg(OTG_DBG_ROOTHUB, "case GetHubStatus\n");
+ otg_mem_set(buf, 0, 4);
+ break;
+
+ case GetPortStatus:
+ otg_dbg(OTG_DBG_ROOTHUB, "case GetPortStatus\n");
+
+ if (otghost->port_flag.b.port_connect_status_change) {
+ port_status |= (1 << USB_PORT_FEAT_C_CONNECTION);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case GetPortStatus - USB_PORT_FEAT_C_CONNECTION\n");
+ }
+
+ if (otghost->port_flag.b.port_enable_change) {
+ port_status |= (1 << USB_PORT_FEAT_C_ENABLE);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case GetPortStatus - USB_PORT_FEAT_C_ENABLE\n");
+ }
+
+ if (otghost->port_flag.b.port_suspend_change) {
+ port_status |= (1 << USB_PORT_FEAT_C_SUSPEND);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case GetPortStatus - USB_PORT_FEAT_C_SUSPEND\n");
+ }
+
+ if (otghost->port_flag.b.port_reset_change) {
+ port_status |= (1 << USB_PORT_FEAT_C_RESET);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case GetPortStatus - USB_PORT_FEAT_C_RESET\n");
+ }
+
+ if (otghost->port_flag.b.port_over_current_change) {
+ port_status |= (1 << USB_PORT_FEAT_C_OVER_CURRENT);
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case GetPortStatus - USB_PORT_FEAT_C_OVER_CURRENT\n");
+ }
+
+ if (!otghost->port_flag.b.port_connect_status) {
+ /*
+ * The port is disconnected, which means the core is
+ * either in device mode or it soon will be. Just
+ * return 0's for the remainder of the port status
+ * since the port register can't be read if the core
+ * is in device mode.
+ */
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case GetPortStatus - disconnected\n");
+
+ *((__le32 *)buf) = cpu_to_le32(port_status);
+ /*break;*/
+ }
+
+ hprt.d32 = read_reg_32(HPRT);
+
+ if (hprt.b.prtconnsts)
+ port_status |= (1 << USB_PORT_FEAT_CONNECTION);
+
+ if (hprt.b.prtena)
+ port_status |= (1 << USB_PORT_FEAT_ENABLE);
+
+ if (hprt.b.prtsusp)
+ port_status |= (1 << USB_PORT_FEAT_SUSPEND);
+
+ if (hprt.b.prtovrcurract)
+ port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT);
+
+ if (hprt.b.prtrst)
+ port_status |= (1 << USB_PORT_FEAT_RESET);
+
+ if (hprt.b.prtpwr)
+ port_status |= (1 << USB_PORT_FEAT_POWER);
+
+ if (hprt.b.prtspd == 0) {
+ port_status |= USB_PORT_STAT_HIGH_SPEED;
+ } else {
+ if (hprt.b.prtspd == 2)
+ port_status |= USB_PORT_STAT_LOW_SPEED;
+ }
+
+ if (hprt.b.prttstctl)
+ port_status |= (1 << USB_PORT_FEAT_TEST);
+
+ *((__le32 *)buf) = cpu_to_le32(port_status);
+
+ otg_dbg(OTG_DBG_ROOTHUB, "port_status=0x%x.\n", port_status);
+ break;
+
+ case SetHubFeature:
+ otg_dbg(OTG_DBG_ROOTHUB, "case SetHubFeature\n");
+ /* No HUB features supported */
+ break;
+
+ case SetPortFeature:
+ otg_dbg(OTG_DBG_ROOTHUB, "case SetPortFeature\n");
+ if (!otghost->port_flag.b.port_connect_status) {
+ /*
+ * The port is disconnected, which means the core is
+ * either in device mode or it soon will be. Just
+ * return without doing anything since the port
+ * register can't be written if the core is in device
+ * mode.
+ */
+ otg_dbg(OTG_DBG_ROOTHUB,
+ "case SetPortFeature - disconnected\n");
+
+ /*break;*/
+ }
+
+ switch (feature) {
+ case USB_PORT_FEAT_SUSPEND:
+ otg_dbg(true,
+ "case SetPortFeature -USB_PORT_FEAT_SUSPEND\n");
+ bus_suspend();
+ break;
+
+ case USB_PORT_FEAT_POWER:
+ otg_dbg(true,
+ "case SetPortFeature -USB_PORT_FEAT_POWER\n");
+ setPortPower(true);
+ break;
+
+ case USB_PORT_FEAT_RESET:
+ otg_dbg(true,
+ "case SetPortFeature -USB_PORT_FEAT_RESET\n");
+ retval = reset_and_enable_port(hcd, port);
+ /*
+ if(retval == USB_ERR_SUCCESS)
+ wake_lock(&otghost->wake_lock);
+ */
+ break;
+
+ case USB_PORT_FEAT_INDICATOR:
+ otg_dbg(true,
+ "case SetPortFeature -USB_PORT_FEAT_INDICATOR\n");
+ break;
+
+ default:
+ otg_dbg(true, "case SetPortFeature -USB_ERR_FAIL\n");
+ retval = USB_ERR_FAIL;
+ break;
+ }
+ break;
+
+ default:
+ retval = USB_ERR_FAIL;
+ otg_err(true, "root_hub_feature() Function Error\n");
+ break;
+ }
+
+ if (retval != USB_ERR_SUCCESS)
+ retval = USB_ERR_FAIL;
+
+ return retval;
+}
+
diff --git a/drivers/usb/host/shost/shost_scheduler.c b/drivers/usb/host/shost/shost_scheduler.c
new file mode 100644
index 0000000..1739298
--- /dev/null
+++ b/drivers/usb/host/shost/shost_scheduler.c
@@ -0,0 +1,420 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : Scheduler.c
+ * [Description] : The source file implements the internal
+ * functions of Scheduler.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2009/2/10
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of Scheduler
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Optimizing for performance \n
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+****************************************************************************/
+
+#include "shost.h"
+
+#include "shost_readyq.c"
+#include "shost_schedulerlib.c"
+
+void init_scheduler(void)
+{
+ /*init_scheduling();*/
+ init_transfer_ready_q();
+}
+
+/******************************************************************************/
+/*!
+ * @name int reserve_used_resource_for_periodic(u32 usb_time)
+ *
+ * @brief this function reserves the necessary resource of USB Transfer
+ * for Periodic Transfer.
+ * So, this function firstly checks there ares some available USB Time
+ * and Channel resource for USB Transfer.
+ * if there exists necessary resources for Periodic Transfer,
+ * then reserves the resource.
+ *
+ * @param [IN] usb_time
+ * - indicates the USB Time for the USB Transfer.
+ *
+ * @return USB_ERR_SUCCESS - if success to insert pInsertED to S3CScheduler.
+ * USB_ERR_NO_BANDWIDTH - if fail to reserve the USB Bandwidth.
+ * USB_ERR_NO_CHANNEL - if fail to reserve the Channel.
+ */
+/******************************************************************************/
+
+int reserve_used_resource_for_periodic(u32 usb_time,
+ u8 dev_speed, u8 trans_type)
+{
+ if (inc_perio_bus_time(usb_time, dev_speed) == USB_ERR_SUCCESS) {
+
+ if (inc_perio_chnum() == USB_ERR_SUCCESS) {
+
+ otg_usbcore_inc_usb_bandwidth(usb_time);
+ otg_usbcore_inc_periodic_transfer_cnt(trans_type);
+
+ return USB_ERR_SUCCESS;
+
+ } else {
+ dec_perio_bus_time(usb_time);
+ return USB_ERR_NO_CHANNEL;
+ }
+
+ } else
+ return USB_ERR_NO_BANDWIDTH;
+}
+
+/******************************************************************************/
+/*!
+ * @name int free_usb_resource_for_periodic(ed_t *pFreeED)
+ *
+ * @brief this function frees the resources to be allocated
+ * to pFreeED at S3CScheduler.
+ * that is, this functions only releases the resources
+ * to be allocated by S3C6400Scheduler.
+ *
+ * @param [IN] pFreeED
+ * - indicates ed_t to have the information of the resource to be released.
+ *
+ * @return USB_ERR_SUCCESS - if success to free the USB Resource.
+ * USB_ERR_FAIL - if fail to free the USB Resrouce.
+ */
+/******************************************************************************/
+int free_usb_resource_for_periodic(
+ u32 free_usb_time, u8 free_chnum, u8 trans_type)
+{
+ if (dec_perio_bus_time(free_usb_time) == USB_ERR_SUCCESS) {
+
+ if (dec_perio_chnum() == USB_ERR_SUCCESS) {
+
+ if (free_chnum != CH_NONE) {
+ oci_channel_dealloc(free_chnum);
+ set_transferring_td_array(free_chnum, 0);
+ }
+
+ otg_usbcore_des_usb_bandwidth(free_usb_time);
+ otg_usbcore_des_periodic_transfer_cnt(trans_type);
+
+ return USB_ERR_SUCCESS;
+ }
+ }
+ return USB_ERR_FAIL;
+}
+
+/******************************************************************************/
+/*!
+ * @name int remove_ed_from_scheduler(ed_t *remove_ed)
+ *
+ * @brief this function just remove the remove_ed from TransferReadyQ.
+ * So if you want to stop the USB Tranfer of remove_ed
+ * or release the releated resources.
+ * you should call another functions of S3CScheduler.
+ *
+ * @param [IN] remove_ed
+ * - indicates ed_t to be removed from TransferReadyQ.
+ *
+ * @return
+ * USB_ERR_SUCCESS -if success to remove the remove_ed from TransferReadyQ.
+ * USB_ERR_FAIL -if fail to remove the remove_ed from TransferReadyQ.
+ */
+/******************************************************************************/
+int remove_ed_from_scheduler(struct ed *remove_ed)
+{
+ if (remove_ed->ed_status.is_in_transfer_ready_q) {
+ remove_ed_from_ready_q(remove_ed);
+ remove_ed->ed_status.is_in_transfer_ready_q = false;
+
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_FAIL;
+}
+
+/******************************************************************************/
+/*!
+ * @name int cancel_to_transfer_td(struct sec_otghost *otghost,
+ * struct td *cancel_td)
+ *
+ * @brief this function stop to execute the USB Transfer of cancel_td and
+ * release the Channel Resources to be allocated the cancel_td ,
+ * if the Transfer Type of cancel_td is NonPeriodic Transfer.
+ * this function don't release any usb resources(Channel, USB Bandwidth)
+ * for Periodic Transfer.
+ * if you want to release some usb resources for a periodic Transfer,
+ * you should call the free_usb_resource_for_periodic()
+ *
+ * @param [IN] cancel_td = indicates the struct td to be canceled.
+ *
+ * @return
+ * USB_ERR_SUCCESS-if success to cancel the USB Transfer of cancel_td.
+ * USB_ERR_FAIL -if fail to cancel the USB Transfer of cancel_td.
+ */
+/******************************************************************************/
+int cancel_to_transfer_td(struct sec_otghost *otghost, struct td *cancel_td)
+{
+ if (cancel_td->is_transfer_done)
+ return USB_ERR_FAIL;
+
+ if (cancel_td->is_transferring) {
+ int err;
+ err = oci_stop_transfer(otghost, cancel_td->cur_stransfer.
+ alloc_chnum);
+
+ if (err == USB_ERR_SUCCESS) {
+
+ set_transferring_td_array(cancel_td->cur_stransfer.
+ alloc_chnum, 0);
+
+ cancel_td->cur_stransfer.alloc_chnum = CH_NONE;
+ cancel_td->is_transferring = false;
+ cancel_td->parent_ed_p->ed_status.is_in_transferring =
+ false;
+ cancel_td->parent_ed_p->ed_status.in_transferring_td =
+ 0;
+ cancel_td->parent_ed_p->is_need_to_insert_scheduler =
+ true;
+
+ if (cancel_td->cur_stransfer.ed_desc_p->
+ endpoint_type == BULK_TRANSFER ||
+ cancel_td->cur_stransfer.ed_desc_p->
+ endpoint_type == CONTROL_TRANSFER) {
+
+ dec_nonperio_chnum();
+ }
+ return err;
+ } else
+ return err;
+ } else
+ return USB_ERR_FAIL;
+
+ return USB_ERR_SUCCESS;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int retransmit(struct sec_otghost *otghost, struct td *retrasmit_td)
+ *
+ * @brief this function retransmits the retrasmit_td immediately.
+ * So, the Channel of pRetransmitted is reused for retransmittion.
+ *
+ * @param [IN] retrasmit_td
+ * - indicates the pointer ot the struct td to be retransmitted.
+ *
+ * @return USB_ERR_SUCCESS - if success to retransmit the retrasmit_td.
+ * USB_ERR_FAIL - if fail to retransmit the retrasmit_td.
+ */
+/******************************************************************************/
+int retransmit(struct sec_otghost *otghost, struct td *retrasmit_td)
+{
+ u32 td_addr = 0;
+
+ if (get_transferring_td_array(retrasmit_td->cur_stransfer.
+ alloc_chnum, &td_addr) == USB_ERR_SUCCESS) {
+
+ if (td_addr == (u32)retrasmit_td) {
+
+ if (oci_start_transfer(otghost,
+ &retrasmit_td->cur_stransfer) ==
+ retrasmit_td->cur_stransfer.alloc_chnum) {
+
+ retrasmit_td->is_transferring = true;
+ retrasmit_td->parent_ed_p->ed_status.
+ in_transferring_td = (u32)retrasmit_td;
+ retrasmit_td->parent_ed_p->ed_status.
+ is_in_transfer_ready_q = false;
+ retrasmit_td->parent_ed_p->ed_status.
+ is_in_transferring = true;
+ }
+
+ } else
+ return USB_ERR_FAIL;
+ } else
+ return USB_ERR_FAIL;
+
+ return USB_ERR_SUCCESS;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name int reschedule(struct td *reschedule_td)
+ *
+ * @brief this function re-schedules the reschedule_td.
+ * So, the Channel of pRescheuleTD is released
+ * and reschedule_td is inserted to TransferReadyQ.
+ *
+ * @param [IN] reschedule_td
+ * - indicates the pointer ot the struct td to be rescheduled.
+ *
+ * @return USB_ERR_SUCCESS -if success to re-schedule the reschedule_td.
+ * USB_ERR_FAIL -if fail to re-schedule the reschedule_td.
+ */
+/******************************************************************************/
+int reschedule(struct td *reschedule_td)
+{
+ u32 td_addr;
+
+ if (get_transferring_td_array(reschedule_td->cur_stransfer.
+ alloc_chnum, &td_addr) == USB_ERR_SUCCESS) {
+ if ((u32)reschedule_td == td_addr) {
+ set_transferring_td_array(reschedule_td->cur_stransfer.
+ alloc_chnum, 0);
+ oci_channel_dealloc(reschedule_td->cur_stransfer.
+ alloc_chnum);
+
+ reschedule_td->cur_stransfer.alloc_chnum = CH_NONE;
+ reschedule_td->parent_ed_p->
+ is_need_to_insert_scheduler = true;
+ reschedule_td->parent_ed_p->ed_status.
+ in_transferring_td = 0;
+
+ if (reschedule_td->parent_ed_p->ed_desc.
+ endpoint_type == BULK_TRANSFER ||
+ reschedule_td->parent_ed_p->ed_desc.
+ endpoint_type == CONTROL_TRANSFER) {
+ /* Increase the available Channel */
+ dec_nonperio_chnum();
+
+ }
+
+ insert_ed_to_ready_q(reschedule_td->parent_ed_p, false);
+ reschedule_td->parent_ed_p->ed_status.
+ is_in_transfer_ready_q = true;
+
+ }
+ /* this case is not support....
+ else {
+ }
+ */
+ }
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name int deallocate(struct td *deallocate_td)
+ *
+ * @brief this function frees resources to be allocated deallocate_td
+ * by S3CScheduler.
+ * this function just free the resource by S3CScheduler.
+ * that is, Channel Resource.
+ * if there are another struct td at ed_t,
+ * deallocate() insert the ed_t to TransferReadyQ.
+ *
+ * @param [IN] deallocate_td
+ * - indicates the pointer ot the struct td to be deallocated.
+ *
+ * @return
+ * USB_ERR_SUCCESS -if success to dealloate the resources for the deallocate_td.
+ * USB_ERR_FAIL -if fail to dealloate the resources for the deallocate_td.
+ */
+/******************************************************************************/
+int deallocate(struct td *deallocate_td)
+{
+ u32 td_addr;
+
+ if (get_transferring_td_array(deallocate_td->cur_stransfer.alloc_chnum,
+ &td_addr) == USB_ERR_SUCCESS) {
+ if ((u32)deallocate_td == td_addr) {
+
+ set_transferring_td_array(deallocate_td->
+ cur_stransfer.alloc_chnum, 0);
+
+ oci_channel_dealloc(deallocate_td->cur_stransfer.
+ alloc_chnum);
+
+ deallocate_td->cur_stransfer.alloc_chnum = CH_NONE;
+
+ if (deallocate_td->parent_ed_p->ed_desc.
+ endpoint_type == BULK_TRANSFER ||
+ deallocate_td->parent_ed_p->ed_desc.
+ endpoint_type == CONTROL_TRANSFER) {
+ /* Increase the available Channel */
+ dec_nonperio_chnum();
+ }
+
+ deallocate_td->parent_ed_p->is_need_to_insert_scheduler
+ = true;
+
+ if (deallocate_td->parent_ed_p->num_td) {
+ /* insert ed_t to TransferReadyQ. */
+ insert_ed_to_ready_q(deallocate_td->parent_ed_p,
+ false);
+ deallocate_td->parent_ed_p->ed_status.
+ is_in_transfer_ready_q = true;
+ deallocate_td->parent_ed_p->
+ is_need_to_insert_scheduler = false;
+ }
+ return USB_ERR_SUCCESS;
+
+ } else
+ return USB_ERR_FAIL;
+ } else
+ return USB_ERR_FAIL;
+
+}
+
+/* TBD.... */
+/* transferchecker-common.c
+ hcd.c */
+void do_schedule(struct sec_otghost *otghost)
+{
+ if (get_avail_chnum()) {
+ do_periodic_schedule(otghost);
+ do_nonperiodic_schedule(otghost);
+ }
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int get_td_info(u8 chnum,
+ * unsigned int *td_addr_p)
+ *
+ * @brief this function returns the pointer of
+ * struct td at TransferringTDArray[chnum]
+ *
+ * @param [IN] chnum
+ * - indicates the index of TransferringTDArray
+ * to include the address of struct td which we gets
+ * [OUT] td_addr_p
+ * - indicate pointer to store the address of struct td.
+ *
+ * @return
+ * USB_ERR_SUCCESS -if success to get the address of struct td.
+ * USB_ERR_FAIL-if fail to get the address of struct td.
+ */
+/******************************************************************************/
+/* transferchecker-common.c */
+int get_td_info(u8 chnum, unsigned int *td_addr_p)
+{
+ u32 td_addr;
+
+ if (get_transferring_td_array(chnum, &td_addr) == USB_ERR_SUCCESS) {
+ *td_addr_p = td_addr;
+ return USB_ERR_SUCCESS;
+ }
+
+ return USB_ERR_FAIL;
+}
+
+
diff --git a/drivers/usb/host/shost/shost_scheduler.h b/drivers/usb/host/shost/shost_scheduler.h
new file mode 100644
index 0000000..fed5035
--- /dev/null
+++ b/drivers/usb/host/shost/shost_scheduler.h
@@ -0,0 +1,56 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : Scheduler.h
+ * [Description] : The Header file defines the external
+ * and internal functions of Scheduler.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/03
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines functions of Scheduler
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _SCHEDULER_H
+#define _SCHEDULER_H
+
+/* hcd.c */
+extern void init_scheduler(void);
+
+extern int reserve_used_resource_for_periodic(u32 usb_time,
+ u8 dev_speed, u8 trans_type);
+extern int free_usb_resource_for_periodic(u32 free_usb_time,
+ u8 free_chnum, u8 trans_type);
+extern int remove_ed_from_scheduler(struct ed *remove_ed);
+extern int cancel_to_transfer_td(struct sec_otghost *otghost,
+ struct td *cancel_td);
+extern int retransmit(struct sec_otghost *otghost, struct td *retransmit_td);
+extern int reschedule(struct td *resched_td);
+extern int deallocate(struct td *dealloc_td);
+extern void do_schedule(struct sec_otghost *otghost);
+extern int get_td_info(u8 chnum, unsigned int *td_addr);
+
+/* transfer-common.c */
+int insert_ed_to_scheduler(struct sec_otghost *otghost, struct ed *insert_ed);
+
+#endif
+
diff --git a/drivers/usb/host/shost/shost_schedulerlib.c b/drivers/usb/host/shost/shost_schedulerlib.c
new file mode 100644
index 0000000..de17401
--- /dev/null
+++ b/drivers/usb/host/shost/shost_schedulerlib.c
@@ -0,0 +1,426 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : Scheduler.c
+ * [Description] : The source file implements the internal
+ * functions of Scheduler.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/04
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of Scheduler
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+/* the max periodic bus time is 80%*125us on High Speed Mode */
+static const u32 perio_highbustime_threshold = 100;
+
+/* the max periodic bus time is 90%*1000us(1ms) on Full/Low Speed Mode. */
+static const u32 perio_fullbustime_threshold = 900;
+
+static const u8 perio_chnum_threshold = 14;
+/* static const u8 total_chnum_threshold = 16; */
+static u8 total_chnum_threshold = 16;
+
+static u32 perio_used_bustime;
+static u8 perio_used_chnum;
+static u8 nonperio_used_chnum;
+static u8 total_used_chnum;
+static u32 transferring_td_array[16];
+
+
+static int inc_perio_bus_time(u32 bus_time, u8 dev_speed)
+{
+ switch (dev_speed) {
+
+ case HIGH_SPEED_OTG:
+ if ((bus_time + perio_used_bustime) <=
+ perio_highbustime_threshold) {
+
+ perio_used_bustime += bus_time;
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_FAIL;
+
+ case LOW_SPEED_OTG:
+ case FULL_SPEED_OTG:
+ if ((bus_time + perio_used_bustime) <=
+ perio_fullbustime_threshold) {
+
+ perio_used_bustime += bus_time;
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_FAIL;
+
+ case SUPER_SPEED_OTG:
+ break;
+ default:
+ break;
+ }
+ return USB_ERR_FAIL;
+}
+
+static int dec_perio_bus_time(u32 bus_time)
+{
+ if (perio_used_bustime >= bus_time) {
+ perio_used_bustime -= bus_time;
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_FAIL;
+}
+
+static int inc_perio_chnum(void)
+{
+ if (perio_used_chnum < perio_chnum_threshold) {
+ if (total_used_chnum < total_chnum_threshold) {
+ perio_used_chnum++;
+ total_used_chnum++;
+ return USB_ERR_SUCCESS;
+ }
+ }
+ return USB_ERR_FAIL;
+}
+
+static u8 get_avail_chnum(void)
+{
+ return total_chnum_threshold - total_used_chnum;
+}
+
+static int dec_perio_chnum(void)
+{
+ if (perio_used_chnum > 0) {
+ if (total_used_chnum > 0) {
+ perio_used_chnum--;
+ total_used_chnum--;
+ return USB_ERR_SUCCESS;
+ }
+ }
+ return USB_ERR_FAIL;
+}
+
+static int inc_non_perio_chnum(void)
+{
+ if (nonperio_used_chnum < total_chnum_threshold) {
+ if (total_used_chnum < total_chnum_threshold) {
+ nonperio_used_chnum++;
+ total_used_chnum++;
+ return USB_ERR_SUCCESS;
+ }
+ }
+ return USB_ERR_FAIL;
+}
+
+static int dec_nonperio_chnum(void)
+{
+ if (nonperio_used_chnum > 0) {
+ if (total_used_chnum > 0) {
+ nonperio_used_chnum--;
+ total_used_chnum--;
+ return USB_ERR_SUCCESS;
+ }
+ }
+ return USB_ERR_FAIL;
+}
+
+static int get_transferring_td_array(u8 chnum, unsigned int *td_addr)
+{
+ if (transferring_td_array[chnum] != 0) {
+ *td_addr = transferring_td_array[chnum];
+ return USB_ERR_SUCCESS;
+ }
+ return USB_ERR_FAIL;
+}
+
+static int set_transferring_td_array(u8 chnum, u32 td_addr)
+{
+ if (td_addr == 0) {
+ transferring_td_array[chnum] = td_addr;
+ return USB_ERR_SUCCESS;
+ }
+
+ if (transferring_td_array[chnum] == 0) {
+ transferring_td_array[chnum] = td_addr;
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_FAIL;
+}
+
+/******************************************************************************/
+/*!
+ * @name int do_periodic_schedule(struct sec_otghost *otghost)
+ *
+ * @brief this function schedules PeriodicTransferReadyQ.
+ * this function checks whether PeriodicTransferReadyQ has some ed_t.
+ * if there are some ed_t on PeriodicTransferReadyQ
+ * , this function request to start USB Trasnfer to S3C6400OCI.
+ *
+ *
+ * @param void
+ *
+ * @return void
+ */
+/******************************************************************************/
+static void do_periodic_schedule(struct sec_otghost *otghost)
+{
+ struct ed *scheduling_ed = NULL;
+ int err_sched = USB_ERR_SUCCESS;
+ u32 sched_cnt = 0;
+
+ otg_list_head *td_list_entry;
+ struct td *td;
+ u32 cur_frame_num = 0;
+ u8 alloc_ch;
+
+ otg_dbg(OTG_DBG_SCHEDULE, "***** Start periodicSchedul *****\n");
+
+ sched_cnt = get_periodic_ready_q_entity_num();
+
+ while (sched_cnt) {
+ /* in periodic transfser,
+ the channel resource was already reserved.
+ So, we don't need this routine... */
+
+start_sched_perio_transfer:
+ if (!sched_cnt)
+ goto end_sched_perio_transfer;
+
+ otg_dbg(OTG_DBG_SCHEDULE, "sched_cnt : %d\n", sched_cnt);
+
+ err_sched = get_ed_from_ready_q(&scheduling_ed, true);
+
+ if (err_sched != USB_ERR_SUCCESS) {
+ /* there is no ED on PeriodicTransferQ.
+ So we finish scheduling.*/
+ otg_dbg(OTG_DBG_SCHEDULE, "no ed\n");
+ goto end_sched_perio_transfer;
+ }
+
+ cur_frame_num = 0;
+
+ otg_dbg(OTG_DBG_SCHEDULE,
+ "the %d ed to be scheduled\n", (int)scheduling_ed);
+
+ sched_cnt--;
+ td_list_entry = scheduling_ed->td_list_entry.next;
+
+ if (td_list_entry == &scheduling_ed->td_list_entry) {
+ /* scheduling_ed has no struct td.
+ so we schedules another ed on
+ PeriodicTransferReadyQ. */
+ goto start_sched_perio_transfer;
+ }
+
+ if (scheduling_ed->ed_status.is_in_transferring) {
+ /* scheduling_ed is already Scheduled.
+ so we schedules another ed on
+ PeriodicTransferReadyQ. */
+ goto start_sched_perio_transfer;
+ }
+
+ cur_frame_num = oci_get_frame_num();
+
+ if (((cur_frame_num - scheduling_ed->ed_desc.sched_frame) &
+ HFNUM_MAX_FRNUM) > (HFNUM_MAX_FRNUM >> 1)) {
+
+ insert_ed_to_ready_q(scheduling_ed, false);
+ goto start_sched_perio_transfer;
+ }
+
+ td = otg_list_get_node(td_list_entry,
+ struct td, td_list_entry);
+
+ if ((td->is_transferring) || (td->is_transfer_done)) {
+ /* the selected struct td was already transferring
+ or completed to transfer.
+ we should decide how to control this case.*/
+ goto end_sched_perio_transfer;
+ }
+
+ otg_dbg(OTG_DBG_SCHEDULE,
+ "the struct td to be scheduled :%d", (int)td);
+
+ alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer);
+
+ if (alloc_ch < total_chnum_threshold) {
+
+ td->cur_stransfer.alloc_chnum = alloc_ch;
+ transferring_td_array[alloc_ch] = (u32)td;
+
+ scheduling_ed->ed_status.is_in_transferring = true;
+ scheduling_ed->ed_status.is_in_transfer_ready_q = false;
+ scheduling_ed->ed_status.in_transferring_td = (u32)td;
+
+ td->is_transferring = true;
+
+ } else {
+ /* we should insert the ed_t to TransferReadyQ,
+ because the USB Transfer of current ed is failed.*/
+ scheduling_ed->ed_status.is_in_transferring = false;
+ scheduling_ed->ed_status.is_in_transfer_ready_q = true;
+ scheduling_ed->ed_status.in_transferring_td = 0;
+
+ insert_ed_to_ready_q(scheduling_ed, true);
+
+ scheduling_ed->is_need_to_insert_scheduler = false;
+ goto end_sched_perio_transfer;
+ }
+
+ }
+
+end_sched_perio_transfer:
+
+ return;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int do_nonperiodic_schedule(struct sec_otghost *otghost)
+ *
+ * @brief this function start to schedule thie NonPeriodicTransferReadyQ.
+ *
+ * this function checks whether NonPeriodicTransferReadyQ has some ed.
+ * if there are some ed on NonPeriodicTransferReadyQ,
+ * this function request to start USB Trasnfer to S3C6400OCI.
+ *
+ * @param void
+ * @return void
+ */
+/******************************************************************************/
+static void do_nonperiodic_schedule(struct sec_otghost *otghost)
+{
+ struct ed *scheduling_ed;
+ int err_sched;
+
+ otg_list_head *td_list_entry;
+ struct td *td;
+ u8 alloc_ch;
+
+ if (total_used_chnum >= total_chnum_threshold) {
+ pr_info("shost nonperiodic: used_chnum >= total_chnum\n");
+ return;
+ }
+
+ while (1) {
+
+start_nonperiodic:
+ /* check there is available channel resource
+ for Non-Periodic Transfer. */
+ if (total_used_chnum == total_chnum_threshold) {
+ pr_info("shost nonperiodic: used_chnum == total_chnum\n");
+ goto end_nonperiodic;
+ }
+
+ err_sched = get_ed_from_ready_q(&scheduling_ed, false);
+
+ if (err_sched != USB_ERR_SUCCESS) {
+ /* pr_info("shost: non: nothing to schedule\n"); */
+ goto end_nonperiodic;
+ }
+
+ td_list_entry = scheduling_ed->td_list_entry.next;
+
+ if (otg_list_empty(&scheduling_ed->td_list_entry)) {
+ /* scheduling_ed has no td.
+ so we schedules another ed
+ on PeriodicTransferReadyQ.*/
+ goto start_nonperiodic;
+ }
+
+ if (scheduling_ed->ed_status.is_in_transferring) {
+ /* scheduling_ed is already Scheduled.
+ so we schedules another ed
+ on PeriodicTransferReadyQ.*/
+ goto start_nonperiodic;
+ }
+
+ td = otg_list_get_node(td_list_entry, struct td, td_list_entry);
+
+ if ((td->is_transferring) || (td->is_transfer_done)) {
+ pr_info("shost nonperiodic: td is busy\n");
+ goto end_nonperiodic;
+ }
+
+ alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer);
+
+ if (alloc_ch < total_chnum_threshold) {
+
+ td->cur_stransfer.alloc_chnum = alloc_ch;
+ transferring_td_array[alloc_ch] = (u32)td;
+
+ inc_non_perio_chnum();
+
+ scheduling_ed->ed_status.is_in_transferring = true;
+ scheduling_ed->ed_status.is_in_transfer_ready_q = false;
+ scheduling_ed->ed_status.in_transferring_td = (u32)td;
+ td->is_transferring = true;
+
+ } else {
+ /* we must insert the ed to queue,
+ because the USB Transfer of the ed is failed.*/
+ scheduling_ed->ed_status.is_in_transferring = false;
+ scheduling_ed->ed_status.in_transferring_td = 0;
+ insert_ed_to_ready_q(scheduling_ed, true);
+ scheduling_ed->ed_status.is_in_transfer_ready_q = true;
+
+ goto end_nonperiodic;
+ }
+ } /* while */
+
+end_nonperiodic:
+ return;
+}
+
+/******************************************************************************/
+/*!
+ * @name int insert_ed_to_scheduler(struct sec_otghost *otghost,
+ * ed_t *insert_ed)
+ *
+ * @brief this function transfers the insert_ed to S3C6400Scheduler,
+ * and after that, the insert_ed is inserted to TransferReadyQ
+ * and scheduled by Scheduler.
+ *
+ * @param [IN] insert_ed
+ * = indicates pointer of ed_t to be inserted to TransferReadyQ.
+ *
+ * @return
+ * USB_ERR_ALREADY_EXIST - if the insert_ed is already existed.
+ * USB_ERR_SUCCESS - if success to insert insert_ed to S3CScheduler.
+ */
+/******************************************************************************/
+int insert_ed_to_scheduler(struct sec_otghost *otghost, struct ed *insert_ed)
+{
+ if (!insert_ed->is_need_to_insert_scheduler)
+ return USB_ERR_ALREADY_EXIST;
+
+ otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td %d\n",
+ insert_ed->ed_id, insert_ed->num_td);
+
+ insert_ed_to_ready_q(insert_ed, false);
+ insert_ed->is_need_to_insert_scheduler = false;
+ insert_ed->ed_status.is_in_transfer_ready_q = true;
+
+ do_periodic_schedule(otghost);
+ do_nonperiodic_schedule(otghost);
+
+ return USB_ERR_SUCCESS;
+}
+
diff --git a/drivers/usb/host/shost/shost_struct.h b/drivers/usb/host/shost/shost_struct.h
new file mode 100644
index 0000000..638c6f2
--- /dev/null
+++ b/drivers/usb/host/shost/shost_struct.h
@@ -0,0 +1,189 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : s3c-otg-common-datastruct.h
+ * [Description] : The Header file defines Data Structures
+ * to be used at sub-modules of S3C6400HCD.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/03
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines Data Structure to be managed by Transfer.
+ * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com )
+ * - modifying ED structure
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _DATA_STRUCT_DEF_H
+#define _DATA_STRUCT_DEF_H
+
+
+struct isoch_packet_desc {
+ /* start address of buffer is buffer address + uiOffsert.*/
+ u32 isoch_packiet_start_addr;
+ u32 buf_size;
+ u32 transferred_szie;
+ u32 isoch_status;
+};
+
+struct standard_dev_req_info {
+ bool is_data_stage;
+ u8 conrol_transfer_stage;
+ u32 vir_standard_dev_req_addr;
+ u32 phy_standard_dev_req_addr;
+};
+
+struct control_data_toggle {
+ u8 setup;
+ u8 data;
+ u8 status;
+};
+
+struct ed_status {
+ struct control_data_toggle control_data_toggle;
+ u8 data_toggle;
+ bool is_ping_enable;
+ bool is_in_transfer_ready_q;
+ bool is_in_transferring;
+ u32 in_transferring_td;
+ bool is_alloc_resource_for_ed;
+};
+
+struct ed_desc {
+ u8 device_addr;
+ u8 endpoint_num;
+ bool is_ep_in;
+ u8 dev_speed;
+ u8 endpoint_type;
+ u16 max_packet_size;
+ u8 mc;
+ u8 interval;
+ u32 sched_frame;
+ u32 used_bus_time;
+ u8 hub_addr;
+ u8 hub_port;
+ bool is_do_split;
+};
+
+struct hc_reg {
+ hcintmsk_t hc_int_msk;
+ hcintn_t hc_int;
+ u32 dma_addr;
+
+};
+
+struct stransfer {
+ u32 stransfer_id;
+ u32 parent_td;
+ struct ed_desc *ed_desc_p;
+ struct ed_status *ed_status_p;
+ u32 vir_addr;
+ u32 phy_addr;
+ u32 buf_size;
+ u32 packet_cnt;
+ u8 alloc_chnum;
+ struct hc_reg hc_reg;
+};
+
+struct ed {
+ u32 ed_id;
+ bool is_halted;
+ bool is_need_to_insert_scheduler;
+ struct ed_desc ed_desc;
+ struct ed_status ed_status;
+ otg_list_head ed_list_entry;
+ otg_list_head td_list_entry;
+ otg_list_head readyq_list;
+ u32 num_td;
+ void *ed_private;
+};
+
+struct td {
+ u32 td_id;
+ struct ed *parent_ed_p;
+ void *call_back_func_p;
+ void *call_back_func_param_p;
+ bool is_transferring;
+ bool is_transfer_done;
+ u32 transferred_szie;
+ bool is_standard_dev_req;
+
+ struct standard_dev_req_info standard_dev_req_info;
+ u32 vir_buf_addr;
+ u32 phy_buf_addr;
+ u32 buf_size;
+ u32 transfer_flag;
+
+ struct stransfer cur_stransfer;
+ u32 error_code;
+ u32 err_cnt;
+ otg_list_head td_list_entry;
+
+ /* Isochronous Transfer Specific */
+ u32 isoch_packet_num;
+ struct isoch_packet_desc *isoch_packet_desc_p;
+ u32 isoch_packet_index;
+ u32 isoch_packet_position;
+ u32 sched_frame;
+ u32 interval;
+ u32 used_total_bus_time;
+
+ /* the private data can be used by S3C6400Interface. */
+ void *td_private;
+};
+
+struct trans_ready_q {
+ bool is_periodic;
+ otg_list_head entity_list;
+ u32 entity_num;
+
+ /* In case of Periodic Transfer */
+ u32 total_perio_bus_bandwidth;
+ u8 total_alloc_chnum;
+};
+
+struct hc_info {
+ hcintmsk_t hc_int_msk;
+ hcintn_t hc_int;
+ u32 dma_addr;
+ hcchar_t hc_char;
+ hctsiz_t hc_size;
+};
+
+#ifndef USB_MAXCHILDREN
+ #define USB_MAXCHILDREN (31)
+#endif
+
+/* TODO: use usb_hub_descriptor
+ * <linux/usb/ch11.h>
+ */
+struct hub_descriptor {
+ u8 desc_length;
+ u8 desc_type;
+ u8 port_number;
+ u16 hub_characteristics;
+ u8 power_on_to_power_good;
+ u8 hub_control_current;
+
+ /* add 1 bit for hub status change; round to bytes */
+ u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8];
+ u8 port_pwr_ctrl_mask[(USB_MAXCHILDREN + 1 + 7) / 8];
+} __packed;
+
+#endif
diff --git a/drivers/usb/host/shost/shost_transfer.c b/drivers/usb/host/shost/shost_transfer.c
new file mode 100644
index 0000000..122ce65
--- /dev/null
+++ b/drivers/usb/host/shost/shost_transfer.c
@@ -0,0 +1,1025 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : Commons3c-otg-transfer-transfer.h
+ * [Description] : This source file implements the functions
+ * to be defined at CommonTransfer Module.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/03
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements some functions of CommonTransfer.
+ * (2) 2008/07/15 by SeungSoo Yang ( ss1.yang@samsung.com )
+ * - Optimizing for performance
+ * (3) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com )
+ * - Modifying for successful rmmod & disconnecting
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#include "shost_transfer.h"
+
+/* the header pointer to indicate the ED_list
+ * to manage the struct ed to be created and initiated.
+ */
+static otg_list_head ed_list_head;
+static u32 ref_periodic_transfer;
+
+/**
+ * void enable_sof(void)
+ *
+ * @brief Generate SOF Interrupt.
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void enable_sof(void)
+{
+ gintmsk_t gintmsk = {.d32 = 0};
+ gintmsk.b.sofintr = 1;
+ update_reg_32(GINTMSK, gintmsk.d32);
+}
+
+/**
+ * void disable_sof(void)
+ *
+ * @brief Stop to generage SOF interrupt
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void disable_sof(void)
+{
+ gintmsk_t gintmsk = {.d32 = 0};
+ gintmsk.b.sofintr = 1;
+ clear_reg_32(GINTMSK, gintmsk.d32);
+}
+
+
+
+/******************************************************************************/
+/*!
+ * @name int cancel_transfer(struct sec_otghost *otghost,
+ * struct ed *parent_ed,
+ * struct td *cancel_td)
+ *
+ * @brief this function cancels to transfer USB Transfer of cancel_td.
+ * this function firstly check whether this cancel_td
+ * is transferring or not. if the cancel_td is transferring, the this
+ * function requests to cancel the USB Transfer
+ * to S3CScheduler. if the parent_ed is for Periodic Transfer, and
+ * there is not any struct td at parent_ed, then this function requests
+ * to release some usb resources for the struct ed to S3CScheduler.
+ * finally this function deletes the cancel_td.
+ *
+ * @param [IN] pUpdateTD = indicates the pointer ot the struct td
+ * to have STransfer to be updated.
+ *
+ * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD.
+ * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD.
+ */
+/******************************************************************************/
+static int cancel_transfer(struct sec_otghost *otghost,
+ struct ed *parent_ed, struct td *cancel_td)
+{
+ otg_list_head *tmp_list_p, *tmp_list2_p;
+ int err = USB_ERR_DEQUEUED;
+ bool cond_found = false;
+
+ if (parent_ed == NULL || cancel_td == NULL) {
+ otg_err(1, "%s is null.\n", parent_ed ?
+ "cancel_td" : "parent_ed");
+ return USB_ERR_NOELEMENT;
+ }
+
+ otg_list_for_each_safe(tmp_list_p, tmp_list2_p,
+ &parent_ed->td_list_entry) {
+
+ if (&cancel_td->td_list_entry == tmp_list_p) {
+ cond_found = true;
+ break;
+ }
+ }
+
+ if (cond_found != true) {
+ otg_dbg(OTG_DBG_TRANSFER, "cond_found != true\n");
+ cancel_td->error_code = USB_ERR_NOELEMENT;
+ otg_usbcore_giveback(cancel_td);
+ return cancel_td->error_code;
+ }
+
+
+ if (cancel_td->is_transferring) {
+ if (!parent_ed->ed_status.is_in_transfer_ready_q) {
+ err = cancel_to_transfer_td(otghost, cancel_td);
+
+ parent_ed->ed_status.in_transferring_td = 0;
+
+ if (err != USB_ERR_SUCCESS) {
+ otg_dbg(OTG_DBG_TRANSFER,
+ "cancel_to_transfer_td\n");
+ cancel_td->error_code = err;
+ otg_usbcore_giveback(cancel_td);
+ goto ErrorStatus;
+ }
+
+ otg_list_pop(&cancel_td->td_list_entry);
+ parent_ed->num_td--;
+ }
+
+ } else {
+
+ otg_list_pop(&cancel_td->td_list_entry);
+ parent_ed->num_td--;
+
+ if (parent_ed->num_td == 0) {
+ remove_ed_from_scheduler(parent_ed);
+ parent_ed->is_need_to_insert_scheduler = true;
+ }
+ }
+
+ if (parent_ed->num_td) {
+ parent_ed->is_need_to_insert_scheduler = true;
+ insert_ed_to_scheduler(otghost, parent_ed);
+
+ } else {
+
+ if (parent_ed->ed_desc.endpoint_type == INT_TRANSFER ||
+ parent_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) {
+
+ /* Release channel and usb bus resource for
+ * this struct ed. but, not release memory for this ed.
+ */
+ free_usb_resource_for_periodic(
+ parent_ed->ed_desc.used_bus_time,
+ cancel_td->cur_stransfer.alloc_chnum,
+ cancel_td->parent_ed_p->ed_desc.endpoint_type);
+
+ parent_ed->ed_status.is_alloc_resource_for_ed = false;
+ }
+ }
+ /* the caller of this functions should call
+ otg_usbcore_giveback(cancel_td); */
+ cancel_td->error_code = USB_ERR_DEQUEUED;
+ otg_usbcore_giveback(cancel_td);
+
+ /* TODO: recursive call occured. FIX */
+ delete_td(otghost, cancel_td);
+
+ErrorStatus:
+
+ return err;
+}
+
+
+
+
+/******************************************************************************/
+/*!
+ * @name int cancel_all_td(struct sec_otghost *otghost, struct ed *parent_ed)
+ *
+ * @brief this function cancels all Transfer which parent_ed manages.
+ *
+ * @param [IN] parent_ed = indicates the pointer ot the struct ed
+ * to manage TD_ts to be canceled.
+ *
+ * @return
+ * USB_ERR_SUCCESS - if success to cancel all TD_ts of pParentsED.
+ * USB_ERR_FAIL - if fail to cancel all TD_ts of pParentsED.
+ */
+/******************************************************************************/
+static int cancel_all_td(struct sec_otghost *otghost, struct ed *parent_ed)
+{
+ otg_list_head *cancel_td_list_entry;
+ struct td *cancel_td;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "cancel_all_td\n");
+ do {
+ cancel_td_list_entry = parent_ed->td_list_entry.next;
+
+ cancel_td = otg_list_get_node(cancel_td_list_entry,
+ struct td, td_list_entry);
+
+ cancel_transfer(otghost, parent_ed, cancel_td);
+
+ } while (parent_ed->num_td);
+
+ return USB_ERR_SUCCESS;
+}
+
+
+
+
+/******************************************************************************/
+/*!
+ * @name int delete_ed(struct ed *delete_ed)
+ *
+ * @brief this function delete the delete_ed.
+ * if there is some available TD_ts on delete_ed,
+ * then this function also deletes these struct td
+ *
+ * @param [IN] delete_ed = indicates the address of struct ed to be deleted.
+ *
+ * @return USB_ERR_SUCCESS -if successes to delete the struct ed.
+ * USB_ERR_FAILl -if fails to delete the ed.
+ */
+/******************************************************************************/
+static int delete_ed(struct sec_otghost *otghost, struct ed *delete_ed)
+{
+ otg_kal_make_ep_null(delete_ed);
+
+ if (delete_ed->num_td) {
+ cancel_all_td(otghost, delete_ed);
+ /**
+ * need to giveback of td's urb with considering life-cycle of
+ * TD, ED, urb->hcpriv, td->private, ep->hcpriv, td->parentED
+ * (commented by ss1.yang)
+ */
+ }
+
+ otg_list_pop(&delete_ed->ed_list_entry);
+
+ if (delete_ed->ed_desc.endpoint_type == INT_TRANSFER ||
+ delete_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) {
+ ref_periodic_transfer--;
+ }
+
+ if (ref_periodic_transfer == 0)
+ disable_sof();
+
+ otg_mem_free(delete_ed);
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name void init_transfer(void)
+ *
+ * @brief this function initiates the S3CTranfer module.
+ * that is, this functions initiates
+ * the ED_list_head OTG List which manages the all ed to be existed.
+ *
+ * @param void
+ * @return void
+ */
+/******************************************************************************/
+
+static void init_transfer(void)
+{
+ otg_dbg(OTG_DBG_TRANSFER, "start to init_transfer\n");
+ otg_list_init(&ed_list_head);
+ ref_periodic_transfer = 0;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name void DeInitTransfer(void)
+ *
+ * @brief this function Deinitiates the S3CTranfer module.
+ * this functions check which there are
+ * some ed on ED_list_head. if some ed exists,
+ * deinit_transfer() deletes the ed.
+ *
+ * @param void
+ * @return void
+ */
+/******************************************************************************/
+static void deinit_transfer(struct sec_otghost *otghost)
+{
+ otg_list_head *ed_list_member;
+ struct ed *delete_ed_p;
+
+ while (otg_list_empty(&ed_list_head) != true) {
+
+ ed_list_member = ed_list_head.next;
+
+ /* otg_list_pop(ed_list_member); */
+
+ delete_ed_p = otg_list_get_node(ed_list_member,
+ struct ed, ed_list_entry);
+
+ delete_ed(otghost, delete_ed_p);
+ }
+}
+
+/******************************************************************************/
+/*!
+ * @name int delete_td(struct sec_otghost *otghost, struct td *delete_td)
+ *
+ * @brief this function frees memory resource for the delete_td.
+ * and if delete_td is transferring USB Transfer, then
+ * this function request to cancel the USB Transfer to scheduler.
+ *
+ *
+ * @param [OUT] new_td_p = returns the address of the new struct td.
+ *
+ * @return USB_ERR_SUCCESS -if successes to create the new struct td.
+ * USB_ERR_FAILl -if fails to create to new struct td.
+ */
+/******************************************************************************/
+int delete_td(struct sec_otghost *otghost, struct td *delete_td)
+{
+ if (delete_td->is_transferring) {
+ /* at this case, we should cancel the USB Transfer. */
+ cancel_to_transfer_td(otghost, delete_td);
+ }
+
+ otg_mem_free(delete_td);
+ return USB_ERR_SUCCESS;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int create_ed(struct ed **new_ed)
+ *
+ * @brief this function creates a new ed and returns the ed to Caller
+ *
+ * @param [OUT] new_ed = returns the address of the new ed .
+ *
+ * @return USB_ERR_SUCCESS -if successes to create the new ed.
+ * USB_ERR_FAILl -if fails to create to new ed.
+ */
+/******************************************************************************/
+static int create_ed(struct ed **new_ed)
+{
+ int err_code = USB_ERR_SUCCESS;
+
+ err_code = otg_mem_alloc((void **)new_ed,
+ (u16)sizeof(struct ed), USB_MEM_ASYNC);
+ otg_mem_set(*new_ed, 0, sizeof(struct ed));
+ return err_code;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int init_ed(struct ed *init_ed,
+ * u8 dev_addr,
+ * u8 ep_num,
+ * bool f_is_ep_in,
+ * u8 dev_speed,
+ * u8 ep_type,
+ * u32 max_packet_size,
+ * u8 multi_count,
+ * u8 interval,
+ * u32 sched_frame,
+ * u8 hub_addr,
+ * u8 hub_port,
+ * bool f_is_do_split)
+ *
+ * @brief this function initiates the init_ed by using the another parameters.
+ *
+ * @param [OUT] init_ed = returns the struct ed to be initiated.
+ * [IN] dev_addr = inidcates the address of USB Device.
+ * [IN] ep_num = inidcates the number of the specific endpoint on USB Device.
+ * [IN] f_is_ep_in = inidcates whether the endpoint is IN or not
+ * [IN] dev_speed = inidcates the speed of USB Device.
+ * [IN] max_packet_size = inidcates the maximum packet size of a specific
+ * endpoint on USB Device.
+ * [IN] multi_count = if the endpoint supports periodic transfer
+ * , this indicates the multiple packet to be transferred on a uframe
+ * [IN] interval= if the endpoint support periodic transfer, this indicates
+ * the polling rate.
+ * [IN] sched_frame= if the endpoint supports periodic transfer, this indicates
+ * the start frame number.
+ * [IN] hub_addr= indicate the address of hub which the USB device attachs to.
+ * [IN] hub_port= inidcates the port number of the hub which the USB
+ * device attachs to.
+ * [IN] f_is_do_split= inidcates whether this tranfer is
+ * split transaction or not.
+ *
+ * @return USB_ERR_SUCCESS -if successes to initiate the ed.
+ * USB_ERR_FAILl -if fails to initiate the ed.
+ * USB_ERR_NOSPACE -if fails to initiate the ed
+ * because there is no USB Resource for this init_ed.
+ */
+/******************************************************************************/
+static int init_ed(struct ed *init_ed,
+ u8 dev_addr,
+ u8 ep_num,
+ bool f_is_ep_in,
+ u8 dev_speed,
+ u8 ep_type,
+ u16 max_packet_size,
+ u8 multi_count,
+ u8 interval,
+ u32 sched_frame,
+ u8 hub_addr,
+ u8 hub_port,
+ bool f_is_do_split,
+ void *ep)
+{
+ init_ed->is_halted = false;
+ init_ed->is_need_to_insert_scheduler = true;
+ init_ed->ed_id = (u32)init_ed;
+ init_ed->num_td = 0;
+ init_ed->ed_private = ep;
+
+ otg_list_init(&init_ed->td_list_entry);
+
+ /* start to initiate struct ed_desc.... */
+ init_ed->ed_desc.is_do_split = f_is_do_split;
+ init_ed->ed_desc.is_ep_in = f_is_ep_in;
+ init_ed->ed_desc.dev_speed = dev_speed;
+ init_ed->ed_desc.hub_addr = hub_addr;
+ init_ed->ed_desc.hub_port = hub_port;
+ init_ed->ed_desc.mc = multi_count;
+ init_ed->ed_desc.device_addr = dev_addr;
+ init_ed->ed_desc.endpoint_num = ep_num;
+ init_ed->ed_desc.endpoint_type = ep_type;
+ init_ed->ed_desc.max_packet_size = max_packet_size;
+ init_ed->ed_desc.sched_frame = sched_frame;
+
+ if (init_ed->ed_desc.endpoint_type == INT_TRANSFER) {
+
+ if (init_ed->ed_desc.dev_speed == LOW_SPEED_OTG ||
+ init_ed->ed_desc.dev_speed == FULL_SPEED_OTG) {
+
+ init_ed->ed_desc.interval = interval;
+
+ } else if (init_ed->ed_desc.dev_speed == HIGH_SPEED_OTG) {
+
+ u8 count = 0;
+ u8 cal_interval = 1;
+
+ for (count = 0;
+ count < (init_ed->ed_desc.interval-1); count++)
+ cal_interval *= 2;
+
+ init_ed->ed_desc.interval = cal_interval;
+
+ } else {
+ otg_dbg(OTG_DBG_TRANSFER,
+ "Super-Speed is not supported\n");
+ }
+
+ init_ed->ed_desc.sched_frame =
+ (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM;
+ ref_periodic_transfer++;
+ }
+
+ if (init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) {
+ u8 count = 0;
+ u8 cal_interval = 1;
+
+ for (count = 0; count < (init_ed->ed_desc.interval-1); count++)
+ cal_interval *= 2;
+
+ init_ed->ed_desc.interval = cal_interval;
+ init_ed->ed_desc.sched_frame =
+ (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM;
+ ref_periodic_transfer++;
+ }
+
+ /* start to initiate struct ed_status.... */
+
+ /* initiates PID */
+ switch (ep_type) {
+ case BULK_TRANSFER:
+ case INT_TRANSFER:
+ init_ed->ed_status.data_toggle = DATA0;
+ break;
+
+ case CONTROL_TRANSFER:
+ init_ed->ed_status.control_data_toggle.setup = SETUP;
+ init_ed->ed_status.control_data_toggle.data = DATA1;
+ init_ed->ed_status.control_data_toggle.status = DATA1;
+ break;
+
+ case ISOCH_TRANSFER:
+ if (f_is_ep_in) {
+ switch (multi_count) {
+ case MULTI_COUNT_ZERO:
+ init_ed->ed_status.data_toggle = DATA0;
+ break;
+ case MULTI_COUNT_ONE:
+ init_ed->ed_status.data_toggle = DATA1;
+ break;
+ case MULTI_COUNT_TWO:
+ init_ed->ed_status.data_toggle = DATA2;
+ break;
+ default:
+ break;
+ }
+
+ } else {
+ switch (multi_count) {
+ case MULTI_COUNT_ZERO:
+ init_ed->ed_status.data_toggle = DATA0;
+ break;
+ case MULTI_COUNT_ONE:
+ init_ed->ed_status.data_toggle = MDATA;
+ break;
+ case MULTI_COUNT_TWO:
+ init_ed->ed_status.data_toggle = MDATA;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (init_ed->ed_desc.endpoint_type == INT_TRANSFER ||
+ init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) {
+
+ u32 usb_time = 0, byte_count = 0;
+
+ /* calculates the bytes to be transferred
+ at one (uframe)frame.*/
+ byte_count = (init_ed->ed_desc.mc+1) *
+ init_ed->ed_desc.max_packet_size;
+
+ usb_time = (u32)otg_usbcore_get_calc_bustime(
+ init_ed->ed_desc.dev_speed,
+ init_ed->ed_desc.is_ep_in,
+ (init_ed->ed_desc.endpoint_type ==
+ ISOCH_TRANSFER ? true : false), byte_count);
+
+ usb_time /= 1000; /* convert nanosec unit to usec unit */
+
+ if (reserve_used_resource_for_periodic(usb_time,
+ init_ed->ed_desc.dev_speed,
+ init_ed->ed_desc.endpoint_type)
+ != USB_ERR_SUCCESS) {
+ return USB_ERR_NOSPACE;
+ }
+
+ init_ed->ed_status.is_alloc_resource_for_ed = true;
+ init_ed->ed_desc.used_bus_time = usb_time;
+ init_ed->ed_desc.mc = multi_count+1;
+ }
+
+ init_ed->ed_status.is_in_transfer_ready_q = false;
+ init_ed->ed_status.is_in_transferring = false;
+ init_ed->ed_status.is_ping_enable = false;
+ init_ed->ed_status.in_transferring_td = 0;
+
+ /* push the ed to ED_list. */
+ otg_list_push_prev(&init_ed->ed_list_entry, &ed_list_head);
+
+ if (ref_periodic_transfer)
+ enable_sof();
+
+ return USB_ERR_SUCCESS;
+}
+
+
+/*****************************************************************************/
+/*!
+ * @name int create_td(struct td **new_td)
+ *
+ * @brief this function creates a new struct td and returns the struct td to Caller
+ *
+ *
+ * @param [OUT] new_td = returns the address of the new struct td .
+ *
+ * @return USB_ERR_SUCCESS -if successes to create the new struct td.
+ * USB_ERR_FAILl -if fails to create to new struct td.
+ */
+/*****************************************************************************/
+static int create_td(struct td **new_td)
+{
+ int err_code = USB_ERR_SUCCESS;
+
+ err_code = otg_mem_alloc((void **)new_td,
+ (u16)sizeof(struct td), USB_MEM_ASYNC);
+ otg_mem_set(*new_td, 0, sizeof(struct td));
+ return err_code;
+}
+
+
+/*****************************************************************************/
+/*!
+ * @name int init_perio_stransfer( bool f_is_isoch_transfer,
+ * struct td *parent_td)
+ *
+ * @brief this function initiates the parent_td->cur_stransfer for Periodic
+ * Transfer and inserts this init_td_p to init_td_p->parent_ed_p.
+ *
+ * @param [IN] f_is_isoch_transfer = indicates whether this transfer
+ * is Isochronous or not.
+ * [IN] parent_td = indicates the address of struct td to be initiated.
+ *
+ * @return USB_ERR_SUCCESS -if success to update the STranfer of pUpdateTD.
+ * USB_ERR_FAIL -if fail to update the STranfer of pUpdateTD.
+ */
+/*****************************************************************************/
+static int init_perio_stransfer(bool f_is_isoch_transfer,
+ struct td *parent_td)
+{
+ parent_td->cur_stransfer.ed_desc_p =
+ &parent_td->parent_ed_p->ed_desc;
+ parent_td->cur_stransfer.ed_status_p =
+ &parent_td->parent_ed_p->ed_status;
+ parent_td->cur_stransfer.alloc_chnum = CH_NONE;
+ parent_td->cur_stransfer.parent_td =
+ (u32)parent_td;
+ parent_td->cur_stransfer.stransfer_id =
+ (u32)&parent_td->cur_stransfer;
+
+ otg_mem_set(&parent_td->cur_stransfer.hc_reg, 0, sizeof(struct hc_reg));
+
+ parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1;
+
+ if (f_is_isoch_transfer) {
+ /* initiates the STransfer usinb the IsochPacketDesc[0]. */
+ parent_td->cur_stransfer.buf_size =
+ parent_td->isoch_packet_desc_p[0].buf_size;
+
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->phy_buf_addr +
+ parent_td->isoch_packet_desc_p[0].
+ isoch_packiet_start_addr;
+
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->vir_buf_addr +
+ parent_td->isoch_packet_desc_p[0].
+ isoch_packiet_start_addr;
+
+ } else {
+ parent_td->cur_stransfer.buf_size =
+ (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) ?
+ MAX_CH_TRANSFER_SIZE : parent_td->buf_size;
+
+ parent_td->cur_stransfer.phy_addr = parent_td->phy_buf_addr;
+ parent_td->cur_stransfer.vir_addr = parent_td->vir_buf_addr;
+ }
+
+ parent_td->cur_stransfer.packet_cnt =
+ calc_packet_cnt(parent_td->cur_stransfer.buf_size,
+ parent_td->parent_ed_p->ed_desc.max_packet_size);
+
+ return USB_ERR_SUCCESS;
+}
+
+
+/*****************************************************************************/
+/*!
+ * @name int init_nonperio_stransfer(bool f_is_standard_dev_req,
+ * struct td *parent_td)
+ *
+ * @brief this function initiates the parent_td->cur_stransfer
+ * for NonPeriodic Transfer and inserts this init_td_p
+ * to init_td_p->parent_ed_p.
+ *
+ * @param [IN] f_is_standard_dev_req- indicates whether this is Control or not.
+ * [IN] parent_td- indicates the address of struct td to be initiated.
+ *
+ * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD.
+ * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD.
+ */
+/******************************************************************************/
+static int init_nonperio_stransfer(bool f_is_standard_dev_req,
+ struct td *parent_td)
+{
+ parent_td->cur_stransfer.ed_desc_p =
+ &parent_td->parent_ed_p->ed_desc;
+ parent_td->cur_stransfer.ed_status_p =
+ &parent_td->parent_ed_p->ed_status;
+ parent_td->cur_stransfer.alloc_chnum = CH_NONE;
+ parent_td->cur_stransfer.parent_td = (u32)parent_td;
+ parent_td->cur_stransfer.stransfer_id =
+ (u32)&parent_td->cur_stransfer;
+
+ otg_mem_set(&(parent_td->cur_stransfer.hc_reg),
+ 0, sizeof(struct hc_reg));
+
+ parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1;
+
+ if (f_is_standard_dev_req) {
+ parent_td->cur_stransfer.buf_size =
+ USB_20_STAND_DEV_REQUEST_SIZE;
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->standard_dev_req_info.
+ phy_standard_dev_req_addr;
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->standard_dev_req_info.
+ vir_standard_dev_req_addr;
+ } else {
+ parent_td->cur_stransfer.buf_size
+ = (parent_td->buf_size > MAX_CH_TRANSFER_SIZE)
+ ? MAX_CH_TRANSFER_SIZE
+ : parent_td->buf_size;
+
+ parent_td->cur_stransfer.phy_addr
+ = parent_td->phy_buf_addr;
+ parent_td->cur_stransfer.vir_addr
+ = parent_td->vir_buf_addr;
+ }
+
+ parent_td->cur_stransfer.packet_cnt =
+ calc_packet_cnt(parent_td->cur_stransfer.buf_size,
+ parent_td->parent_ed_p->ed_desc.max_packet_size);
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name int init_td(struct td *init_td,
+ * struct ed *parent_ed,
+ * void *call_back_fun,
+ * void *call_back_param,
+ * u32 transfer_flag,
+ * bool f_is_standard_dev_req,
+ * u32 phy_setup,
+ * u32 vir_setup,
+ * u32 vir_buf_addr,
+ * u32 phy_buf_addr,
+ * u32 buf_size,
+ * u32 isoch_start_frame,
+ * isoch_packet_desc_t *isoch_packet_desc,
+ * u32 isoch_packet_num,
+ * void *td_priv)
+ *
+ * @brief this function initiates the init_td by using another parameter.
+ *
+ *
+ * @param [IN] init_td- indicate the struct td to be initiated.
+ * [IN] parent_ed- indicate the ed to manage this init_td
+ * [IN] call_back_func- indicate the call-back function of application.
+ * [IN] call_back_param- indicate the parameter of the call-back function.
+ * [IN] transfer_flag- indicate the transfer flag.
+ * [IN] f_is_standard_dev_req- indicates the issue transfer request is Request
+ * [IN] phy_setup- the physical address of buffer to store the Request.
+ * [IN] vir_setup- the virtual address of buffer to store the Request.
+ * [IN] vir_buf_addr- the virtual address of buffer to store the data
+ * to be transferred or received.
+ * [IN] phy_buf_addr- the physical address of buffer to store the data
+ * to be transferred or received.
+ * [IN] buf_size- indicates the buffer size.
+ * [IN] isoch_start_frame- if this usb transfer is isochronous transfer
+ * , this indicates the start frame to start the usb transfer.
+ * [IN] isoch_packet_desc- if the usb transfer is isochronous transfer
+ * , this indicates the structure to describe the isochronous transfer.
+ * [IN] isoch_packet_num- if the usb transfer is isochronous transfer
+ * , this indicates the number of packet to consist of the usb transfer.
+ * [IN] td_priv- indicate the private data to be delivered from usb core.
+ * td_priv stores the urb of linux.
+ *
+ * @return USB_ERR_SUCCESS -if successes to initiate the new struct td.
+ * USB_ERR_FAILl -if fails to create to new struct td.
+ */
+/******************************************************************************/
+static int init_td(struct td *init_td,
+ struct ed *parent_ed,
+ void *call_back_fun,
+ void *call_back_param,
+ u32 transfer_flag,
+ bool f_is_standard_dev_req,
+ u32 phy_setup,
+ u32 vir_setup,
+ u32 vir_buf_addr,
+ u32 phy_buf_addr,
+ u32 buf_size,
+ u32 isoch_start_frame,
+ struct isoch_packet_desc *isoch_packet_desc,
+ u32 isoch_packet_num,
+ void *td_priv)
+{
+ if (f_is_standard_dev_req) {
+
+ if ((phy_buf_addr > 0) && (buf_size > 0))
+ init_td->standard_dev_req_info.is_data_stage = true;
+ else
+ init_td->standard_dev_req_info.is_data_stage = false;
+
+ init_td->standard_dev_req_info.conrol_transfer_stage
+ = SETUP_STAGE;
+ init_td->standard_dev_req_info.phy_standard_dev_req_addr
+ = phy_setup;
+ init_td->standard_dev_req_info.vir_standard_dev_req_addr
+ = vir_setup;
+ }
+
+ init_td->call_back_func_p = call_back_fun;
+ init_td->call_back_func_param_p = call_back_param;
+ init_td->error_code = USB_ERR_SUCCESS;
+ init_td->is_standard_dev_req = f_is_standard_dev_req;
+ init_td->is_transfer_done = false;
+ init_td->is_transferring = false;
+ init_td->td_private = td_priv;
+ init_td->err_cnt = 0;
+ init_td->parent_ed_p = parent_ed;
+ init_td->phy_buf_addr = phy_buf_addr;
+ init_td->vir_buf_addr = vir_buf_addr;
+ init_td->buf_size = buf_size;
+ init_td->isoch_packet_desc_p = isoch_packet_desc;
+ init_td->isoch_packet_num = isoch_packet_num;
+ init_td->isoch_packet_index = 0;
+ init_td->isoch_packet_position = 0;
+ init_td->sched_frame = isoch_start_frame;
+ init_td->used_total_bus_time = parent_ed->ed_desc.used_bus_time;
+ init_td->td_id = (u32)init_td;
+ init_td->transfer_flag = transfer_flag;
+ init_td->transferred_szie = 0;
+
+ switch (parent_ed->ed_desc.endpoint_type) {
+ case CONTROL_TRANSFER:
+ init_nonperio_stransfer(true, init_td);
+ break;
+
+ case BULK_TRANSFER:
+ init_nonperio_stransfer(false, init_td);
+ break;
+
+ case INT_TRANSFER:
+ init_perio_stransfer(false, init_td);
+ break;
+
+ case ISOCH_TRANSFER:
+ init_perio_stransfer(true, init_td);
+ break;
+
+ default:
+ return USB_ERR_FAIL;
+ }
+
+ /* insert the struct td to parent_ed->td_list_entry. */
+ otg_list_push_prev(&init_td->td_list_entry, &parent_ed->td_list_entry);
+ parent_ed->num_td++;
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name int issue_transfer(struct sec_otghost *otghost,
+ * struct ed *parent_ed,
+ * void *call_back_func,
+ * void *call_back_param,
+ * u32 transfer_flag,
+ * bool f_is_standard_dev_req,
+ * u32 setup_vir_addr,
+ * u32 setup_phy_addr,
+ * u32 vir_buf_addr,
+ * u32 phy_buf_addr,
+ * u32 buf_size,
+ * u32 start_frame,
+ * u32 isoch_packet_num,
+ * isoch_packet_desc_t *isoch_packet_desc,
+ * void *td_priv,
+ * unsigned int *return_td_addr)
+ *
+ * @brief this function start USB Transfer
+ *
+ *
+ * @param [IN] parent_ed - indicate the ed to manage this issue transfer.
+ * [IN] call_back_func - indicate the call-back function of application.
+ * [IN] call_back_param - indicate the parameter of the call-back function.
+ * [IN] transfer_flag - indicate the transfer flag.
+ * [IN] f_is_standard_dev_req - indicates the issue transfer request
+ * is USB Standard Request
+ * [IN] setup_vir_addr - the virtual address of buffer to store the Request.
+ * [IN] setup_phy_addr - the physical address of buffer to store the Request.
+ * [IN] vir_buf_addr - the virtual address of buffer to store the data
+ * to be transferred or received.
+ * [IN] phy_buf_addr - the physical address of buffer to store the data
+ * to be transferred or received.
+ * [IN] buf_siz- indicates the buffer size.
+ * [IN] start_frame - if this usb transfer is isochronous transfer,
+ * this indicates the start frame to start the usb transfer.
+ * [IN] isoch_packet_num - if the usb transfer is isochronous transfer,
+ * this indicates the number of packet to consist of the usb transfer.
+ * [IN] isoch_packet_desc - if the usb transfer is isochronous transfer
+ * this indicates the structure to describe the isochronous transfer.
+ * [IN] td_priv - indicate the private data to be delivered from usb core.
+ * td_priv stores the urb of linux.
+ * [OUT] return_td_addr - indicates the variable address
+ * to store the new struct td for this transfer
+ *
+ * @return USB_ERR_SUCCESS - if successes to initiate the new struct td.
+ * USB_ERR_FAILl - if fails to create to new struct td.
+ */
+/******************************************************************************/
+static int issue_transfer(struct sec_otghost *otghost,
+ struct ed *parent_ed,
+ void *call_back_func,
+ void *call_back_param,
+ u32 transfer_flag,
+ bool f_is_standard_dev_req,
+ u32 setup_vir_addr,
+ u32 setup_phy_addr,
+ u32 vir_buf_addr,
+ u32 phy_buf_addr,
+ u32 buf_size,
+ u32 start_frame,
+ u32 isoch_packet_num,
+ struct isoch_packet_desc *isoch_packet_desc,
+ void *td_priv,
+ unsigned int *return_td_addr)
+{
+ struct td *new_td_p = NULL;
+ int err = USB_ERR_SUCCESS;
+
+ if (create_td(&new_td_p) == USB_ERR_SUCCESS) {
+ err = init_td(new_td_p,
+ parent_ed,
+ call_back_func,
+ call_back_param,
+ transfer_flag,
+ f_is_standard_dev_req,
+ setup_phy_addr,
+ setup_vir_addr,
+ vir_buf_addr,
+ phy_buf_addr,
+ buf_size,
+ start_frame,
+ isoch_packet_desc,
+ isoch_packet_num,
+ td_priv);
+
+ if (err != USB_ERR_SUCCESS)
+ return USB_ERR_NOMEM;
+
+ if (parent_ed->is_need_to_insert_scheduler)
+ insert_ed_to_scheduler(otghost, parent_ed);
+
+ *return_td_addr = (u32)new_td_p;
+
+ return USB_ERR_SUCCESS;
+ } else
+ return USB_ERR_NOMEM;
+}
+
+
+/* TODO: not used. removed */
+#if 0
+static int create_isoch_packet_desc(isoch_packet_desc_t **new_isoch_packet_desc,
+ u32 isoch_packet_num)
+{
+ return otg_mem_alloc((void **)new_isoch_packet_desc,
+ (u16)sizeof(isoch_packet_desc_t)*isoch_packet_num,
+ USB_MEM_SYNC);
+}
+
+static int delete_isoch_packet_desc(isoch_packet_desc_t *del_isoch_packet_desc,
+ u32 isoch_packet_num)
+{
+ return otg_mem_free(del_isoch_packet_desc);
+}
+
+/******************************************************************************/
+/*!
+ * @name
+ * void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc,
+ * u32 isoch_packet_start_addr,
+ * u32 isoch_packet_size,
+ * u32 index)
+ *
+ * @brief this function initiates the isoch_packet_desc_t[index].
+ *
+ * @param
+ * [OUT] init_isoch_packet_desc = indicates the pointer of
+ * IsochPackDesc_t to be initiated.
+ * [IN] isoch_packet_start_addr = indicates the start address of the buffer
+ * to be used at USB Isochronous Transfer.
+ * [IN] isoch_packet_size = indicates the size of Isochronous packet.
+ * [IN] index = indicates the index to be mapped with this.
+ *
+ * @return void
+ */
+/******************************************************************************/
+static void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc,
+ u32 isoch_packet_start_addr,
+ u32 isoch_packet_size,
+ u32 index)
+{
+ init_isoch_packet_desc[index].buf_size = isoch_packet_size;
+ init_isoch_packet_desc[index].isoch_packiet_start_addr
+ = isoch_packet_start_addr;
+ init_isoch_packet_desc[index].isoch_status = 0;
+ init_isoch_packet_desc[index].transferred_szie = 0;
+}
+#endif
+
+
diff --git a/drivers/usb/host/shost/shost_transfer.h b/drivers/usb/host/shost/shost_transfer.h
new file mode 100644
index 0000000..397fdae
--- /dev/null
+++ b/drivers/usb/host/shost/shost_transfer.h
@@ -0,0 +1,52 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : s3c-otg-transfer-transfer.h
+ * [Description] : The Header file defines the external
+ * and internal functions of Transfer.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/03
+ * [Revision History]
+ * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines functions of Transfer
+ * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com)
+ * : Optimizing for performance
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#ifndef _TRANSFER_H
+#define _TRANSFER_H
+
+/*
+ transfer-common.c
+ transferchecker_common.c
+ */
+int delete_td(struct sec_otghost *otghost, struct td * delete_td);
+
+static inline u32 calc_packet_cnt(u32 data_size, u16 max_packet_size)
+{
+ if (data_size != 0) {
+ return (data_size % max_packet_size == 0) ?
+ data_size/max_packet_size :
+ data_size/max_packet_size + 1;
+ }
+ return 1;
+}
+#endif
+
diff --git a/drivers/usb/host/shost/shost_transferchecker.c b/drivers/usb/host/shost/shost_transferchecker.c
new file mode 100644
index 0000000..855164c
--- /dev/null
+++ b/drivers/usb/host/shost/shost_transferchecker.c
@@ -0,0 +1,503 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : CommonTransferChecker.c
+ * [Description] : The Source file implements the external
+ * and internal functions of CommonTransferChecker.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2009/01/12
+ * [Revision History]
+ * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of CommonTransferChecker
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+#include "shost.h"
+
+/**
+ * void mask_channel_interrupt(u32 ch_num, u32 mask_info)
+ *
+ * @brief Mask specific channel interrupt
+ *
+ * @param [IN] chnum : channel number for masking
+ * [IN] mask_info : mask information to write register
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void mask_channel_interrupt(u32 ch_num, u32 mask_info)
+{
+ clear_reg_32(HCINTMSK(ch_num), mask_info);
+}
+
+/**
+ * void unmask_channel_interrupt(u32 ch_num, u32 mask_info)
+ *
+ * @brief Unmask specific channel interrupt
+ *
+ * @param [IN] chnum : channel number for unmasking
+ * [IN] mask_info : mask information to write register
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void unmask_channel_interrupt(u32 ch_num, u32 mask_info)
+{
+ update_reg_32(HCINTMSK(ch_num), mask_info);
+}
+
+/**
+ * int get_ch_info(struct hc_info * hc_reg, u8 ch_num)
+ *
+ * @brief Get current channel information about specific channel
+ *
+ * @param [OUT] hc_reg : structure to write channel inforamtion value
+ * [IN] ch_num : channel number for unmasking
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static int get_ch_info(struct hc_info *hc_reg, u8 ch_num)
+{
+ if (hc_reg != NULL) {
+ hc_reg->hc_int_msk.d32 = read_reg_32(HCINTMSK(ch_num));
+ hc_reg->hc_int.d32 = read_reg_32(HCINT(ch_num));
+ hc_reg->dma_addr = read_reg_32(HCDMA(ch_num));
+ hc_reg->hc_char.d32 = read_reg_32(HCCHAR(ch_num));
+ hc_reg->hc_size.d32 = read_reg_32(HCTSIZ(ch_num));
+
+ return USB_ERR_SUCCESS;
+ }
+ return USB_ERR_FAIL;
+}
+
+/**
+ * void get_intr_ch(u32* haint, u32* haintmsk)
+ *
+ * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register
+ *
+ * @param [OUT] haint : HAINT register value
+ * [OUT] haintmsk : HAINTMSK register value
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void get_intr_ch(u32 *haint, u32 *haintmsk)
+{
+ *haint = read_reg_32(HAINT);
+ *haintmsk = read_reg_32(HAINTMSK);
+}
+
+/**
+ * void clear_ch_intr(u8 ch_num, u32 clear_bit)
+ *
+ * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register
+ *
+ * @param [IN] haint : HAINT register value
+ * [IN] haintmsk : HAINTMSK register value
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+static void clear_ch_intr(u8 ch_num, u32 clear_bit)
+{
+ update_reg_32(HCINT(ch_num), clear_bit);
+}
+
+
+static int
+release_trans_resource(struct sec_otghost *otghost,
+ struct td *done_td)
+{
+ /* remove the pDeallocateTD from parent_ed_p. */
+ otg_list_pop(&done_td->td_list_entry);
+ done_td->parent_ed_p->num_td--;
+
+ /* Call deallocate to release the channel
+ and bandwidth resource of S3CScheduler. */
+ deallocate(done_td);
+ delete_td(otghost, done_td);
+ return USB_ERR_SUCCESS;
+}
+
+static u32 calc_transferred_size(bool f_is_complete,
+ struct td *td, struct hc_info *hc_info)
+{
+ if (f_is_complete) {
+ if (td->parent_ed_p->ed_desc.is_ep_in) {
+ return td->cur_stransfer.buf_size -
+ hc_info->hc_size.b.xfersize;
+
+ } else
+ return td->cur_stransfer.buf_size;
+
+ } else {
+ return (td->cur_stransfer.packet_cnt -
+ hc_info->hc_size.b.pktcnt)*td->
+ parent_ed_p->ed_desc.max_packet_size;
+ }
+}
+
+static void update_frame_number(struct td *pResultTD)
+{
+ u32 cur_frame_num = 0;
+
+ if (pResultTD->parent_ed_p->ed_desc.
+ endpoint_type == CONTROL_TRANSFER ||
+ pResultTD->parent_ed_p->ed_desc.
+ endpoint_type == BULK_TRANSFER) {
+ return;
+ }
+
+ pResultTD->parent_ed_p->ed_desc.sched_frame +=
+ pResultTD->parent_ed_p->ed_desc.interval;
+ pResultTD->parent_ed_p->ed_desc.sched_frame &=
+ HFNUM_MAX_FRNUM;
+
+ cur_frame_num = oci_get_frame_num();
+
+ if (((cur_frame_num - pResultTD->parent_ed_p->ed_desc.sched_frame)
+ &HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM >> 1)) {
+ pResultTD->parent_ed_p->ed_desc.sched_frame = cur_frame_num;
+ }
+}
+
+static void update_data_toggle(struct td *td, u8 toggle)
+{
+ switch (td->parent_ed_p->ed_desc.endpoint_type) {
+ case CONTROL_TRANSFER:
+ if (td->standard_dev_req_info.conrol_transfer_stage
+ == DATA_STAGE) {
+ td->parent_ed_p->ed_status.
+ control_data_toggle.data = toggle;
+ }
+ break;
+ case BULK_TRANSFER:
+ case INT_TRANSFER:
+ td->parent_ed_p->ed_status.data_toggle = toggle;
+ break;
+
+ case ISOCH_TRANSFER:
+ break;
+ default:
+ break;
+ }
+}
+
+/******************************************************************************/
+/*!
+ * @name void update_perio_stransfer(struct td *parent_td)
+ *
+ * @brief this function updates the parent_td->cur_stransfer
+ * to be used by oci. the STransfer of parent_td is for Periodic Transfer.
+ *
+ * @param [IN/OUT]parent_td = indicates the pointer of struct td
+ * to store the STranser to be updated.
+ *
+ * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer.
+ * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer.
+ */
+/******************************************************************************/
+static void update_perio_stransfer(struct td *parent_td)
+{
+ switch (parent_td->parent_ed_p->ed_desc.endpoint_type) {
+ case INT_TRANSFER:
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->phy_buf_addr +
+ parent_td->transferred_szie;
+
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->vir_buf_addr +
+ parent_td->transferred_szie;
+
+ parent_td->cur_stransfer.buf_size =
+ (parent_td->buf_size > MAX_CH_TRANSFER_SIZE)
+ ? MAX_CH_TRANSFER_SIZE : parent_td->buf_size;
+ break;
+
+ case ISOCH_TRANSFER:
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->phy_buf_addr +
+ parent_td->isoch_packet_desc_p[parent_td->
+ isoch_packet_index].
+ isoch_packiet_start_addr +
+ parent_td->isoch_packet_position;
+
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->vir_buf_addr +
+ parent_td->isoch_packet_desc_p[parent_td->
+ isoch_packet_index].
+ isoch_packiet_start_addr +
+ parent_td->isoch_packet_position;
+
+ parent_td->cur_stransfer.buf_size =
+ (parent_td->isoch_packet_desc_p
+ [parent_td->isoch_packet_index].buf_size -
+ parent_td->isoch_packet_position) >
+ MAX_CH_TRANSFER_SIZE
+ ? MAX_CH_TRANSFER_SIZE
+ : parent_td->isoch_packet_desc_p
+ [parent_td->isoch_packet_index].buf_size -
+ parent_td->isoch_packet_position;
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+/****************************************************************************/
+/*!
+ * @name void update_nonperio_stransfer(struct td *parent_td)
+ *
+ * @brief this function updates the parent_td->cur_stransfer
+ * to be used by S3COCI.
+ *
+ * @param [IN/OUT]parent_td = indicates the pointer of struct td
+ * to store the STranser to be updated.
+ *
+ * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer.
+ * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer.
+ */
+/****************************************************************************/
+static void update_nonperio_stransfer(struct td *parent_td)
+{
+ switch (parent_td->parent_ed_p->ed_desc.endpoint_type) {
+
+ case BULK_TRANSFER:
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->phy_buf_addr +
+ parent_td->transferred_szie;
+
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->vir_buf_addr +
+ parent_td->transferred_szie;
+
+ parent_td->cur_stransfer.buf_size =
+ ((parent_td->buf_size - parent_td->transferred_szie)
+ > MAX_CH_TRANSFER_SIZE)
+ ? MAX_CH_TRANSFER_SIZE
+ : parent_td->buf_size -
+ parent_td->transferred_szie;
+ break;
+
+ case CONTROL_TRANSFER:
+ if (parent_td->standard_dev_req_info.
+ conrol_transfer_stage == SETUP_STAGE) {
+ /* but, this case will not be occured...... */
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->standard_dev_req_info.
+ phy_standard_dev_req_addr;
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->standard_dev_req_info.
+ vir_standard_dev_req_addr;
+ parent_td->cur_stransfer.buf_size = 8;
+
+ } else if (parent_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE) {
+
+ parent_td->cur_stransfer.phy_addr =
+ parent_td->phy_buf_addr +
+ parent_td->transferred_szie;
+
+ parent_td->cur_stransfer.vir_addr =
+ parent_td->vir_buf_addr +
+ parent_td->transferred_szie;
+
+ parent_td->cur_stransfer.buf_size =
+ ((parent_td->buf_size - parent_td->
+ transferred_szie) > MAX_CH_TRANSFER_SIZE)
+ ? MAX_CH_TRANSFER_SIZE
+ : parent_td->buf_size -
+ parent_td->transferred_szie;
+
+ } else {
+ parent_td->cur_stransfer.phy_addr = 0;
+ parent_td->cur_stransfer.vir_addr = 0;
+ parent_td->cur_stransfer.buf_size = 0;
+ }
+ break;
+ default:
+ break;
+ }
+
+ parent_td->cur_stransfer.packet_cnt =
+ calc_packet_cnt(parent_td->cur_stransfer.buf_size,
+ parent_td->parent_ed_p->ed_desc.max_packet_size);
+
+}
+
+#include "shost_transferchecker_control.c"
+#include "shost_transferchecker_interrupt.c"
+#include "shost_transferchecker_bulk.c"
+
+/******************************************************************************/
+/*!
+ * @name void do_transfer_checker(struct sec_otghost *otghost)
+ *
+ * @brief this function processes the result of USB Transfer.
+ * So, do_transfer_checker fistly
+ * check which channel occurs OTG Interrupt and gets the status
+ * information of the channel.
+ * do_transfer_checker requests the information of td to scheduler.
+ * To process the interrupt of the channel, do_transfer_checker
+ * calls the sub-modules of
+ * S3CDoneTransferChecker, for example,
+ * ControlTransferChecker, BulkTransferChecker.
+ * according to the process result of the channel interrupt,
+ * do_transfer_checker decides
+ * the USB Transfer will be done or retransmitted.
+ *
+ *
+ * @param void
+ *
+ * @return void
+ */
+/*****************************************************************************/
+void do_transfer_checker(struct sec_otghost *otghost)
+__releases(&otghost->lock)
+__acquires(&otghost->lock)
+{
+ u32 hc_intr = 0;
+ u32 hc_intr_msk = 0;
+ u8 do_try_cnt = 0;
+
+ struct hc_info ch_info;
+ struct td *done_td = {0};
+
+ u32 td_addr = 0;
+ u8 proc_result = 0;
+
+ otg_mem_set((void *)&ch_info, 0, sizeof(struct hc_info));
+
+ /* Get value of HAINT... */
+ get_intr_ch(&hc_intr, &hc_intr_msk);
+
+start_do_transfer_checker:
+
+ while (do_try_cnt < MAX_CH_NUMBER) {
+ /* checks the channel number to be masked or not. */
+ if (!(hc_intr & hc_intr_msk & (1 << do_try_cnt))) {
+ do_try_cnt++;
+ goto start_do_transfer_checker;
+ }
+
+ /* Gets the address of the struct td
+ to have the channel to be interrupted. */
+ if (!(get_td_info(do_try_cnt, &td_addr))) {
+
+ done_td = (struct td *)td_addr;
+
+ if (do_try_cnt != done_td->cur_stransfer.alloc_chnum) {
+ do_try_cnt++;
+ goto start_do_transfer_checker;
+ }
+
+ } else {
+ do_try_cnt++;
+ goto start_do_transfer_checker;
+ }
+
+ /* Gets the informationof channel to be interrupted. */
+ get_ch_info(&ch_info, do_try_cnt);
+
+ switch (done_td->parent_ed_p->ed_desc.endpoint_type) {
+ case CONTROL_TRANSFER:
+ proc_result =
+ process_control_transfer(done_td, &ch_info);
+ break;
+ case BULK_TRANSFER:
+ proc_result = process_bulk_transfer(done_td, &ch_info);
+ break;
+ case INT_TRANSFER:
+ proc_result = process_intr_transfer(done_td, &ch_info);
+ break;
+ case ISOCH_TRANSFER:
+ /* proc_result =
+ ProcessIsochTransfer(done_td, &ch_info); */
+ break;
+ default:
+ break;
+ }
+
+ if ((proc_result == RE_TRANSMIT) ||
+ (proc_result == RE_SCHEDULE)) {
+
+ done_td->parent_ed_p->ed_status.
+ is_in_transferring = false;
+ done_td->is_transfer_done = false;
+ done_td->is_transferring = false;
+
+ if (done_td->parent_ed_p->ed_desc.endpoint_type
+ == CONTROL_TRANSFER ||
+ done_td->parent_ed_p->ed_desc.endpoint_type
+ == BULK_TRANSFER) {
+
+ update_nonperio_stransfer(done_td);
+
+ } else
+ update_perio_stransfer(done_td);
+
+ if (proc_result == RE_TRANSMIT)
+ retransmit(otghost, done_td);
+ else
+ reschedule(done_td);
+ }
+
+ else if (proc_result == DE_ALLOCATE) {
+
+ done_td->parent_ed_p->ed_status.
+ is_in_transferring = false;
+ done_td->parent_ed_p->ed_status.
+ in_transferring_td = 0;
+ done_td->is_transfer_done = true;
+ done_td->is_transferring = false;
+
+ spin_unlock(&otghost->lock);
+ otg_usbcore_giveback(done_td);
+ spin_lock(&otghost->lock);
+ release_trans_resource(otghost, done_td);
+
+ } else { /* NO_ACTION.... */
+ done_td->parent_ed_p->ed_status.
+ is_in_transferring = true;
+ done_td->parent_ed_p->ed_status.
+ in_transferring_td = (u32)done_td;
+ done_td->is_transfer_done = false;
+ done_td->is_transferring = true;
+ }
+ do_try_cnt++;
+ }
+ /* Complete to process the Channel Interrupt.
+ So. we now start to scheduler of S3CScheduler. */
+ do_schedule(otghost);
+}
+
+
diff --git a/drivers/usb/host/shost/shost_transferchecker_bulk.c b/drivers/usb/host/shost/shost_transferchecker_bulk.c
new file mode 100644
index 0000000..13c6b0c
--- /dev/null
+++ b/drivers/usb/host/shost/shost_transferchecker_bulk.c
@@ -0,0 +1,671 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : BulkTransferChecker.c
+ * [Description] : The Source file implements the external
+ * and internal functions of BulkTransferChecker.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/13
+ * [Revision History]
+ * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of BulkTransferChecker
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xfercompl_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xfercompl event.
+ * the procedure of this function is as following
+ * 1. clears all bits of the channel' HCINT
+ * 2. masks some bit of HCINTMSK
+ * 3. updates the result_td fields
+ * err_cnt/u8/standard_dev_req_info.
+ * 4. updates the result_td->parent_ed_p->ed_status.BulkDataTgl.
+ * 5. calculates the tranferred size on DATA_STAGE.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+static u8 process_xfercompl_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ u8 ret_val = 0;
+
+ result_td->err_cnt = 0;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ result_td->transferred_szie += calc_transferred_size(true,
+ result_td, hc_reg_data);
+
+ if (result_td->transferred_szie == result_td->buf_size) {
+ /* at IN Transfer, short transfer is accepted. */
+ result_td->error_code = USB_ERR_STATUS_COMPLETE;
+ ret_val = DE_ALLOCATE;
+
+ } else {
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in &&
+ hc_reg_data->hc_size.b.xfersize) {
+
+ if (result_td->transfer_flag & USB_TRANS_FLAG_NOT_SHORT)
+ result_td->error_code =
+ USB_ERR_STATUS_SHORTREAD;
+ else
+ result_td->error_code = USB_ERR_STATUS_COMPLETE;
+
+ ret_val = DE_ALLOCATE;
+
+ } else
+ ret_val = RE_SCHEDULE;
+ }
+
+ update_data_toggle(result_td, hc_reg_data->hc_size.b.pid);
+
+ if (hc_reg_data->hc_int.b.nyet) {
+ /* at OUT Transfer, we must re-transmit. */
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false) {
+
+ if (result_td->parent_ed_p->ed_desc.
+ dev_speed == HIGH_SPEED_OTG)
+
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+ else
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = false;
+ }
+ }
+
+ return ret_val;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ahb_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theAHB Errorl event.
+ * this function stop the channel to be executed
+ * TBD....
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+/* TODO: not used. removed */
+#if 0
+static u8 process_ahb_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_AHBERR;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_AHBErr);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ /* we just calculate the size of the transferred data on Data Stage
+ of Bulk Transfer. */
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hc_reg_data);
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ return DE_ALLOCATE;
+
+}
+#endif
+
+/******************************************************************************/
+/*!
+ * @name u8 process_stall_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theStall event.
+ * when Stall is occured at Bulk Transfer, we should reset the PID as DATA0
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+static u8 process_stall_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_STALL;
+
+ /* this channel is stalled, So we don't process another interrupts.*/
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+ result_td->transferred_szie += calc_transferred_size(false,
+ result_td, hc_reg_data);
+
+ update_data_toggle(result_td, DATA0);
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nak_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nak event.
+ * nak is occured at OUT/IN Transaction of Data/Status Stage,
+ * and is not occured at Setup Stage.
+ * If nak is occured at IN Transaction,
+ * this function processes this interrupt as following.
+ *
+ * 1. resets the result_td->err_cnt.
+ * 2. masks ack/nak/DaaTglErr bit of HCINTMSK.
+ * 3. clears the nak bit of HCINT
+ * 4. be careful, nak of IN Transaction don't require re-transmit.
+ * If nak is occured at OUT Transaction,
+ * this function processes this interrupt as following.
+
+ * 1. all procedures of IN Transaction are executed.
+ * 2. calculates the size of the transferred data.
+ * 3. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 4. update the Toggle at OUT Transaction,
+ * this function check whether the speed of USB Device
+ * is High-Speed or not.
+ * if USB Device is High-Speed, then
+ * this function sets the ping protocol.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE -if the direction of the Transfer is OUT
+ * NO_ACTION -if the direction of the Transfer is IN
+ */
+/******************************************************************************/
+static u8 process_nak_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK);
+
+ /* at OUT Transfer, we must re-transmit.*/
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false) {
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hc_reg_data);
+
+ if (result_td->parent_ed_p->ed_desc.dev_speed ==
+ HIGH_SPEED_OTG)
+ result_td->parent_ed_p->ed_status.is_ping_enable = true;
+ else
+ result_td->parent_ed_p->ed_status.is_ping_enable =
+ false;
+
+ update_data_toggle(result_td, hc_reg_data->hc_size.b.pid);
+
+ return RE_TRANSMIT;
+ }
+ return NO_ACTION;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ack_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the ack event
+ * ack of IN/OUT Transaction don't need any retransmit.
+ * this function just resets result_td->err_cnt
+ * and masks ack/nak/DataTgl of HCINTMSK.
+ * finally, this function clears ack bit of HCINT
+ * and ed_status.is_ping_enable.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+static u8 process_ack_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ return NO_ACTION;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nyet_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nyet event.
+ * nyet is only occured at OUT Transaction.
+ * If nyet is occured at OUT Transaction,
+ * this function processes this interrupt as following.
+ *
+ * 1. resets the result_td->err_cnt.
+ * 2. masks ack/nak/datatglerr bit of HCINTMSK.
+ * 3. clears the nyet bit of HCINT
+ * 4. calculates the size of the transferred data.
+ * 5. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 6. update the Data Toggle.
+ * 7. return RE_SCHEDULE to retransmit.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+static u8 process_nyet_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ if (result_td->parent_ed_p->ed_desc.is_ep_in) {
+ /* Error State.... */
+ return NO_ACTION;
+ }
+
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hc_reg_data);
+
+ if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG)
+ result_td->parent_ed_p->ed_status.is_ping_enable = true;
+ else
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ update_data_toggle(result_td, hc_reg_data->hc_size.b.pid);
+
+ return RE_TRANSMIT;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xacterr_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xacterr event.
+ * xacterr is occured at OUT/IN Transaction
+ * and we should retransmit the USB Transfer
+ * if the Error Counter is less than the RETRANSMIT_THRESHOLD.
+ * the reasons of xacterr is Timeout/CRC error/false EOP.
+ * the procedure to process xacterr is as following.
+ * 1. increses the result_td->err_cnt
+ * 2. check whether the result_td->err_cnt is equal to 3.
+ * 3. unmasks ack/nak/datatglerr bit of HCINTMSK.
+ * 4. clears the xacterr bit of HCINT
+ * 5. calculates the size of the transferred data.
+ * 6. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 7. update the Data Toggle.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if the error count is less than 3
+ * DE_ALLOCATE -if the error count is equal to 3
+ */
+/******************************************************************************/
+static u8 process_xacterr_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ u8 ret_val = 0;
+
+ if (result_td->err_cnt < RETRANSMIT_THRESHOLD) {
+
+ result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |=
+ (CH_STATUS_ACK + CH_STATUS_NAK+CH_STATUS_DataTglErr);
+
+ ret_val = RE_TRANSMIT;
+ result_td->err_cnt++ ;
+
+ } else {
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ ret_val = DE_ALLOCATE;
+ result_td->err_cnt = 0 ;
+ result_td->error_code = USB_ERR_STATUS_XACTERR;
+ }
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hc_reg_data);
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false) {
+ if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG)
+ result_td->parent_ed_p->ed_status.is_ping_enable = true;
+ else
+ result_td->parent_ed_p->ed_status.is_ping_enable =
+ false;
+ }
+
+ update_data_toggle(result_td, hc_reg_data->hc_size.b.pid);
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name void process_bblerr_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the Babble event
+ * babble error is occured when the buffer to receive data
+ * to be transmit is overflow.
+ * So, babble error can be just occured at IN Transaction.
+ * when Babble Error is occured, we should stop the USB Transfer,
+ * and return the fact to Application.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+static u8 process_bblerr_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+
+ if (!result_td->parent_ed_p->ed_desc.is_ep_in)
+ return NO_ACTION;
+
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_BBLERR;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ /* Mask ack Interrupt..*/
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hc_reg_data);
+
+ return DE_ALLOCATE;
+
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_datatgl_on_bulk( struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the datatglerr
+ * the datatglerr event is occured at IN Transfer,
+ * and the channel is not halted.
+ * this function just resets result_td->err_cnt
+ * and masks ack/nak/DataTgl of HCINTMSK.
+ * finally, this function clears datatglerr bit of HCINT.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+static u8 process_datatgl_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ result_td->err_cnt = 0;
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ return NO_ACTION;
+
+}
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_chhltd_on_bulk(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ * @brief this function processes Channel Halt event.
+ * firstly, this function checks the reason of the Channel Halt,
+ * and according to the reason,
+ * calls the sub-functions to process the result.
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if need to retransmit the result_td.
+ * RE_SCHEDULE -if need to reschedule the result_td.
+ * DE_ALLOCATE -if USB Transfer is completed.
+ */
+/******************************************************************************/
+static u8 process_chhltd_on_bulk(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ if (hc_reg_data->hc_int.b.xfercompl)
+ return process_xfercompl_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_reg_data->hc_int.b.stall)
+ return process_stall_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_reg_data->hc_int.b.bblerr)
+ return process_bblerr_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_reg_data->hc_int.b.xacterr)
+ return process_xacterr_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_reg_data->hc_int.b.nak)
+ return process_nak_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_reg_data->hc_int.b.nyet)
+ return process_nyet_on_bulk(result_td, hc_reg_data);
+
+ else {
+ /* occur error state...*/
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ALL);
+
+ /* Mask ack Interrupt..*/
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->err_cnt++;
+ if (result_td->err_cnt == 3) {
+ result_td->error_code = USB_ERR_STATUS_XACTERR;
+ result_td->err_cnt = 0;
+ return DE_ALLOCATE;
+ }
+
+ return RE_TRANSMIT;
+ }
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_bulk_transfer(struct td *result_td,
+ * struct hc_info *hc_reg_data)
+ *
+ *
+ * @brief this function processes the result of the Bulk Transfer.
+ * firstly, this function checks the result of the Bulk Transfer.
+ * and according to the result, calls the sub-functions
+ * to process the result.
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td whose channel is interruped.
+ * [IN] hc_reg_data
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if need to retransmit the result_td.
+ * RE_SCHEDULE -if need to reschedule the result_td.
+ * DE_ALLOCATE -if USB Transfer is completed.
+ * NO_ACTION -if we don't need any action,
+ */
+/******************************************************************************/
+static u8 process_bulk_transfer(struct td *result_td,
+ struct hc_info *hc_reg_data)
+{
+ hcintn_t hc_intr_info;
+ u8 ret = 0;
+
+ /* we just deal with the interrupts to be unmasked.*/
+ hc_intr_info.d32 = hc_reg_data->hc_int.d32 &
+ result_td->cur_stransfer.hc_reg.hc_int_msk.d32;
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in) {
+ if (hc_intr_info.b.chhltd)
+ ret = process_chhltd_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_intr_info.b.ack)
+ ret = process_ack_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_intr_info.b.nak)
+ ret = process_nak_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_intr_info.b.datatglerr)
+ ret = process_datatgl_on_bulk(result_td, hc_reg_data);
+
+ } else {
+
+ if (hc_intr_info.b.chhltd)
+ ret = process_chhltd_on_bulk(result_td, hc_reg_data);
+
+ else if (hc_intr_info.b.ack)
+ ret = process_ack_on_bulk(result_td, hc_reg_data);
+ }
+
+ return ret;
+}
+
diff --git a/drivers/usb/host/shost/shost_transferchecker_control.c b/drivers/usb/host/shost/shost_transferchecker_control.c
new file mode 100644
index 0000000..8d0cfbc
--- /dev/null
+++ b/drivers/usb/host/shost/shost_transferchecker_control.c
@@ -0,0 +1,777 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : ControlTransferChecker.c
+ * [Description] : The Source file implements the external
+ * and internal functions of ControlTransferChecker.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2009/02/10
+ * [Revision History]
+ * (1) 2008/06/13 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of ControlTransferChecker
+ * (2) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Completed to implement ControlTransferChecker.c v1.0
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xfercompl_on_control(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the xfercompl event
+ * the procedure of this function is as following
+ * 1. clears all bits of the channel' HCINT
+ * by using clear_ch_intr() of S3CIsr.
+ * 2. masks some bit of HCINTMSK
+ * 3. updates the result_td fields
+ * err_cnt/u8/standard_dev_req_info.
+ * 4. updates the result_td->parent_ed_p->ed_status.
+ * control_data_tgl.
+ * 5. calculates the tranferred size
+ * by calling calc_transferred_size() on DATA_STAGE.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+static u8 process_xfercompl_on_control(struct td *result_td, struct hc_info *hcinfo)
+{
+ u8 ret_val = 0;
+
+ result_td->err_cnt = 0;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ALL);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ switch (result_td->standard_dev_req_info.conrol_transfer_stage) {
+
+ case SETUP_STAGE:
+ if (result_td->standard_dev_req_info.is_data_stage)
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage = DATA_STAGE;
+ else
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage = STATUS_STAGE;
+
+ ret_val = RE_TRANSMIT;
+
+ break;
+
+ case DATA_STAGE:
+
+ result_td->transferred_szie +=
+ calc_transferred_size(true, result_td, hcinfo);
+
+ /* at IN Transfer, short transfer is accepted. */
+ if (result_td->transferred_szie == result_td->buf_size) {
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage = STATUS_STAGE;
+ result_td->error_code = USB_ERR_STATUS_COMPLETE;
+
+ } else {
+ if (result_td->parent_ed_p->ed_desc.is_ep_in &&
+ hcinfo->hc_size.b.xfersize) {
+
+ if (result_td->transfer_flag &
+ USB_TRANS_FLAG_NOT_SHORT) {
+
+ result_td->error_code =
+ USB_ERR_STATUS_SHORTREAD;
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage =
+ STATUS_STAGE;
+
+ } else {
+ result_td->error_code =
+ USB_ERR_STATUS_COMPLETE;
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage =
+ STATUS_STAGE;
+ }
+
+ } else {
+ /* the Data Stage is not completed.
+ So we need to continue Data Stage. */
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage = DATA_STAGE;
+ update_data_toggle(result_td,
+ hcinfo->hc_size.b.pid);
+ }
+ }
+
+ if (hcinfo->hc_int.b.nyet) {
+ /* at OUT Transfer, we must re-transmit. */
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false) {
+
+ if (result_td->parent_ed_p->ed_desc.
+ dev_speed == HIGH_SPEED_OTG)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+ else
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = false;
+ }
+ }
+ ret_val = RE_TRANSMIT;
+ break;
+
+ case STATUS_STAGE:
+ result_td->standard_dev_req_info.
+ conrol_transfer_stage = COMPLETE_STAGE;
+
+ if (hcinfo->hc_int.b.nyet) {
+ /* at OUT Transfer, we must re-transmit. */
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false) {
+
+ if (result_td->parent_ed_p->ed_desc.
+ dev_speed == HIGH_SPEED_OTG)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+ else
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = false;
+ }
+ }
+
+ ret_val = DE_ALLOCATE;
+ break;
+
+ default:
+ break;
+ }
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ahb_on_control(ruct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with theAHB Errorl event
+ * this function stop the channel to be executed
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+
+/* TODO: not used. remove */
+#if 0
+static u8 process_ahb_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_AHBERR;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_AHBErr);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ /* we just calculate the size of the transferred data
+ on Data Stage of Control Transfer. */
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE)
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ return DE_ALLOCATE;
+
+}
+#endif
+
+/******************************************************************************/
+/*!
+ * @name u8 process_stall_on_control(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with theStall event
+ * but USB2.0 Spec don't permit the Stall
+ * on Setup Stage of Control Transfer.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+static u8 process_stall_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_STALL;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ /* we just calculate the size of the transferred data
+ on Data Stage of Control Transfer. */
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE)
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nak_on_control(ruct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the nak event
+ * nak is occured at OUT/IN Transaction of Data/Status Stage,
+ * and is not occured at Setup Stage.
+ * If nak is occured at IN Transaction,
+ * this function processes this interrupt as following.
+ * 1. resets the result_td->err_cnt.
+ * 2. masks ack/nak/DaaTglErr bit of HCINTMSK.
+ * 3. clears the nak bit of HCINT
+ * 4. be careful, nak of IN Transaction don't require re-transmit.
+ *
+ * If nak is occured at OUT Transaction,
+ * this function processes this interrupt as following.
+ * 1. all procedures of IN Transaction are executed.
+ * 2. calculates the size of the transferred data.
+ * 3. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 4. update the Toggle
+ * at OUT Transaction, this function check whether the speed of
+ * USB Device is High-Speed or not.
+ * if USB Device is High-Speed, then
+ * this function sets the ping protocol.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+static u8 process_nak_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+
+ /* at OUT Transfer, we must re-transmit. */
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false) {
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+ }
+
+ if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) {
+
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE) {
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+ }
+
+ else if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == STATUS_STAGE) {
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == true)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+
+ } else
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = false;
+ }
+
+ return RE_SCHEDULE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ack_on_control(ruct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the ack event
+ * ack of IN/OUT Transaction don't need any retransmit.
+ * this function just resets result_td->err_cnt
+ * and masks ack/nak/DataTgl of HCINTMSK.
+ * finally, this function clears ack bit of HCINT.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+static u8 process_ack_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ return NO_ACTION;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nyet_on_control(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ * @brief this function deals with the nyet event
+ * nyet is occured at OUT Transaction of Data/Status Stage,
+ * and is not occured at Setup Stage.
+ * If nyet is occured at OUT Transaction,
+ * this function processes this interrupt as following.
+ * 1. resets the result_td->err_cnt.
+ * 2. masks ack/nak/datatglerr bit of HCINTMSK.
+ * 3. clears the nyet bit of HCINT
+ * 4. calculates the size of the transferred data.
+ * 5. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 6. update the Data Toggle.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+static u8 process_nyet_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) {
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE) {
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+ }
+
+ else if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == STATUS_STAGE) {
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == true)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+
+ } else
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = false;
+ }
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ return RE_SCHEDULE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xacterr_on_control(ruct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the xacterr event
+ * xacterr is occured at OUT/IN Transaction of Data/Status Stage,
+ * and is not occured at Setup Stage.
+ * if Timeout/CRC error/false EOP is occured, then xacterr is occured.
+ * the procedure to process xacterr is as following.
+ * 1. increses the result_td->err_cnt
+ * 2. check whether the result_td->err_cnt is equal to 3.
+ * 3. unmasks ack/nak/datatglerr bit of HCINTMSK.
+ * 4. clears the xacterr bit of HCINT
+ * 5. calculates the size of the transferred data.
+ * 6. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 7. update the Data Toggle.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if the Error Counter is less than RETRANSMIT_THRESHOLD
+ * DE_ALLOCATE -if the Error Counter is equal to RETRANSMIT_THRESHOLD
+ */
+/******************************************************************************/
+static u8 process_xacterr_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ u8 ret_val = 0;
+
+ if (result_td->err_cnt < RETRANSMIT_THRESHOLD) {
+
+ result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |=
+ (CH_STATUS_ACK + CH_STATUS_NAK + CH_STATUS_DataTglErr);
+
+ ret_val = RE_TRANSMIT;
+
+ unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+ result_td->err_cnt++ ;
+
+ } else {
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+ ret_val = DE_ALLOCATE;
+ result_td->err_cnt = 0 ;
+ result_td->error_code = USB_ERR_STATUS_XACTERR;
+ }
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE)
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) {
+
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE) {
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == false)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+ }
+
+ else if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == STATUS_STAGE) {
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in == true)
+ result_td->parent_ed_p->ed_status.
+ is_ping_enable = true;
+
+ } else
+ result_td->parent_ed_p->ed_status.is_ping_enable =
+ false;
+ }
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ return ret_val;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name void process_bblerr_on_control(truct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the Babble event
+ * babble error can be just occured at IN Transaction.
+ * So if the direction of transfer is
+ * OUT, this function return Error Code.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ * NO_ACTION -if the direction is OUT
+ */
+/******************************************************************************/
+static u8 process_bblerr_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+
+ if (!result_td->parent_ed_p->ed_desc.is_ep_in)
+ return NO_ACTION;
+
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_BBLERR;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ /* we just calculate the size of the transferred data
+ on Data Stage of Control Transfer. */
+ if (result_td->standard_dev_req_info.
+ conrol_transfer_stage == DATA_STAGE)
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_datatgl_on_control(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the datatglerr event
+ * the datatglerr event is occured at IN Transfer.
+ * this function just resets result_td->err_cnt
+ * and masks ack/nak/DataTgl of HCINTMSK.
+ * finally, this function clears datatglerr bit of HCINT.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+static u8 process_datatgl_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ return NO_ACTION;
+
+}
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_chhltd_on_control(truct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function processes Channel Halt event
+ * firstly, this function checks the reason of the Channel Halt,
+ * and according to the reason,
+ * calls the sub-functions to process the result.
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if need to retransmit the result_td.
+ * RE_SCHEDULE -if need to reschedule the result_td.
+ * DE_ALLOCATE -if USB Transfer is completed.
+ */
+/******************************************************************************/
+static u8 process_chhltd_on_control(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ if (hcinfo->hc_int.b.xfercompl)
+ return process_xfercompl_on_control(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.stall)
+ return process_stall_on_control(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.bblerr)
+ return process_bblerr_on_control(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.xacterr)
+ return process_xacterr_on_control(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.nak)
+ return process_nak_on_control(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.nyet)
+ return process_nyet_on_control(result_td, hcinfo);
+
+ else {
+
+ /* Occure Error State.....*/
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ALL);
+
+ /* Mask ack Interrupt.. */
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+ result_td->err_cnt++;
+
+ if (result_td->err_cnt == 3) {
+
+ result_td->error_code = USB_ERR_STATUS_XACTERR;
+ result_td->err_cnt = 0;
+ return DE_ALLOCATE;
+ }
+ return RE_TRANSMIT;
+ }
+ return USB_ERR_SUCCESS;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_control_transfer(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ * @brief this function processes the result of the Control Transfer.
+ * firstly, this function checks the result the Control Transfer.
+ * and according to the result, calls the sub-functions
+ * to process the result.
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] ubChNum
+ * -indicates the number of the channel to be interrupted.
+ * [IN] hcinfo
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if need to retransmit the result_td.
+ * RE_SCHEDULE -if need to reschedule the result_td.
+ * DE_ALLOCATE -if USB Transfer is completed.
+ */
+/******************************************************************************/
+static u8 process_control_transfer(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ hcintn_t hcintr_info;
+ u8 ret_val = 0;
+
+ /* we just deal with the interrupts to be unmasked. */
+ hcintr_info.d32 = hcinfo->hc_int.d32 &
+ result_td->cur_stransfer.hc_reg.hc_int_msk.d32;
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in) {
+ if (hcintr_info.b.chhltd)
+ ret_val = process_chhltd_on_control(result_td, hcinfo);
+
+ else if (hcintr_info.b.ack)
+ ret_val = process_ack_on_control(result_td, hcinfo);
+
+ else if (hcintr_info.b.nak)
+ ret_val = process_nak_on_control(result_td, hcinfo);
+
+ else if (hcintr_info.b.datatglerr)
+ ret_val = process_datatgl_on_control(result_td, hcinfo);
+
+ } else {
+
+ if (hcintr_info.b.chhltd)
+ ret_val = process_chhltd_on_control(result_td, hcinfo);
+
+ else if (hcintr_info.b.ack)
+ ret_val = process_ack_on_control(result_td, hcinfo);
+ }
+
+ return ret_val;
+}
+
diff --git a/drivers/usb/host/shost/shost_transferchecker_interrupt.c b/drivers/usb/host/shost/shost_transferchecker_interrupt.c
new file mode 100644
index 0000000..fa85721
--- /dev/null
+++ b/drivers/usb/host/shost/shost_transferchecker_interrupt.c
@@ -0,0 +1,617 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : IntTransferChecker.c
+ * [Description] : The Source file implements the external
+ * and internal functions of IntTransferChecker
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/19
+ * [Revision History]
+ * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements functions of IntTransferChecker
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ ****************************************************************************/
+
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xfercompl_on_intr( struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the xfercompl event
+ * the procedure of this function is as following
+ * 1. clears all bits of the channel' HCINT
+ * by using clear_ch_intr() of S3CIsr.
+ * 2. masks ack/nak(?)/datatglerr(?) bit of HCINTMSK
+ * 3. Resets the err_cnt of result_td.
+ * 4. updates the result_td->parent_ed_p->ed_status.
+ * IntDataTgl.
+ * 5. calculates the tranferred size by calling
+ * calc_transferred_size() on DATA_STAGE.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE -if USB Transfer is completed.
+ * RE_TRANSMIT -if need to retransmit the result_td.
+ */
+/******************************************************************************/
+static u8 process_xfercompl_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ u8 ret_val = 0;
+
+ result_td->err_cnt = 0;
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(true, result_td, hcinfo);
+
+ if (result_td->transferred_szie == result_td->buf_size) {
+ /* at IN Transfer, short transfer is accepted. */
+ result_td->error_code = USB_ERR_STATUS_COMPLETE;
+ ret_val = DE_ALLOCATE;
+
+ } else {
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in &&
+ hcinfo->hc_size.b.xfersize) {
+
+ if (result_td->transfer_flag & USB_TRANS_FLAG_NOT_SHORT)
+ result_td->error_code =
+ USB_ERR_STATUS_SHORTREAD;
+ else
+ result_td->error_code = USB_ERR_STATUS_COMPLETE;
+ ret_val = DE_ALLOCATE;
+
+ } else {
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+ ret_val = RE_TRANSMIT;
+ }
+ }
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ return ret_val;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ahb_on_intr(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with theAHB Errorl event
+ * this function stop the channel to be executed
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+/* TODO: not used. removed */
+#if 0
+static u8 process_ahb_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_AHBERR;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_AHBErr);
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+ result_td->parent_ed_p->ed_status.is_ping_enable = false;
+
+ return DE_ALLOCATE;
+
+}
+#endif
+
+/******************************************************************************/
+/*!
+ * @name u8 process_stall_on_intr(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the Stall event
+ * when Stall is occured at Int Transfer, we should reset the PID as DATA0
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+static u8 process_stall_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_STALL;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ update_data_toggle(result_td, DATA0);
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nak_on_intr(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the nak event
+ * nak is occured at OUT/IN Transaction of Interrupt Transfer.
+ * we can't use ping protocol on Interrupt Transfer.
+ * and Syonopsys OTG IP occures
+ * chhltd interrupt on nak of IN/OUT Transaction.
+ * So we should retransmit the transfer on IN Transfer.
+ * If nak is occured at IN Transaction,
+ * this function processes this interrupt as following.
+ * 1. resets the result_td->err_cnt.
+ * 2. masks ack/nak/DaaTglErr bit of HCINTMSK.
+ * 3. clears the nak bit of HCINT
+ * 4. calculates frame number to retransmit this Interrupt Transfer.
+ *
+ * If nak is occured at OUT Transaction,
+ * this function processes this interrupt as following.
+ * 1. all procedures of IN Transaction are executed.
+ * 2. calculates the size of the transferred data.
+ * 3. if the speed of USB Device is High-Speed, sets the ping protocol.
+ * 4. update the Toggle
+ * at OUT Transaction, this function check whether the speed of
+ * USB Device is High-Speed or not.
+ * if USB Device is High-Speed, then
+ * this function sets the ping protocol.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE -if the direction of the Transfer is OUT
+ * NO_ACTION -if the direction of the Transfer is IN
+ */
+/******************************************************************************/
+static u8 process_nak_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ update_frame_number(result_td);
+
+ return RE_SCHEDULE;
+
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ack_on_intr(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ *
+ * @brief this function deals with the ack event
+ * ack of IN/OUT Transaction don't need any retransmit.
+ * this function just resets result_td->err_cnt
+ * and masks ack/nak/DataTgl of HCINTMSK.
+ * finally, this function clears ack bit of HCINT
+ * and ed_status.is_ping_enable.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+static u8 process_ack_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+
+ return NO_ACTION;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xacterr_on_intr(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ * @brief this function deals with the xacterr event
+ * xacterr is occured at OUT/IN Transaction and we should
+ * retransmit the USB Transfer
+ * if the Error Counter is less than the RETRANSMIT_THRESHOLD.
+ * the reasons of xacterr is Timeout/CRC error/false EOP.
+ * the procedure to process xacterr is as following.
+ * 1. increses the result_td->err_cnt
+ * 2. check whether the result_td->err_cnt is equal to 3.
+ * 3. unmasks ack/nak/datatglerr bit of HCINTMSK.
+ * 4. clears the xacterr bit of HCINT
+ * 5. calculates the size of the transferred data.
+ * 6. update the Data Toggle.
+ * 7. update the frame number to start retransmitting
+ * the Interrupt Transfer.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE -if the error count is less than 3
+ * DE_ALLOCATE -if the error count is equal to 3
+ */
+/******************************************************************************/
+static u8 process_xacterr_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ u8 ret_val = 0;
+
+ if (result_td->err_cnt < RETRANSMIT_THRESHOLD) {
+ result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |=
+ (CH_STATUS_ACK + CH_STATUS_NAK + CH_STATUS_DataTglErr);
+ ret_val = RE_SCHEDULE;
+ result_td->err_cnt++ ;
+
+ } else {
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+ ret_val = DE_ALLOCATE;
+ result_td->err_cnt = 0 ;
+ }
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ if (ret_val == RE_SCHEDULE)
+ update_frame_number(result_td);
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name void process_bblerr_on_intr(struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ * @brief this function deals with the Babble event
+ * babble error is occured when the USB device continues to send packets
+ * althrough EOP is occured. So Babble error is only occured
+ * at IN Transfer.
+ * when Babble Error is occured, we should stop the USB Transfer,
+ * and return the fact to Application.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+static u8 process_bblerr_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ if (!result_td->parent_ed_p->ed_desc.is_ep_in)
+ return NO_ACTION;
+
+ result_td->err_cnt = 0;
+ result_td->error_code = USB_ERR_STATUS_BBLERR;
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL);
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_datatgl_on_intr( struct td *result_td,
+ * struct hc_info *hcinfo)
+ *
+ * @brief this function deals with the datatglerr event
+ * the datatglerr event is occured at IN Transfer,
+ * and the channel is not halted.
+ * this function just resets result_td->err_cnt
+ * and masks ack/nak/DataTgl of HCINTMSK.
+ * finally, this function clears datatglerr bit of HCINT.
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+static u8 process_datatgl_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ result_td->err_cnt = 0;
+
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_DataTglErr);
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+
+ result_td->transferred_szie +=
+ calc_transferred_size(false, result_td, hcinfo);
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ update_frame_number(result_td);
+
+ return RE_SCHEDULE;
+}
+
+static u8 process_frmovrrun_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_NAK);
+
+ update_data_toggle(result_td, hcinfo->hc_size.b.pid);
+
+ update_frame_number(result_td);
+
+ return RE_TRANSMIT;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_chhltd_on_intr(struct td *result_td,
+ * struct hc_info *HCRegData)
+ *
+ *
+ * @brief this function processes Channel Halt event
+ * firstly, this function checks the reason of the Channel Halt,
+ * and according to the reason,
+ * calls the sub-functions to process the result.
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td to be mapped with the uChNum.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if need to retransmit the result_td.
+ * RE_SCHEDULE -if need to reschedule the result_td.
+ * DE_ALLOCATE -if USB Transfer is completed.
+ */
+/******************************************************************************/
+static u8 process_chhltd_on_intr(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ if (result_td->parent_ed_p->ed_desc.is_ep_in) {
+ if (hcinfo->hc_int.b.xfercompl)
+ return process_xfercompl_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.stall)
+ return process_stall_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.bblerr)
+ return process_bblerr_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.nak)
+ return process_nak_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.datatglerr)
+ return process_datatgl_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.frmovrun)
+ return process_frmovrrun_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.xacterr)
+ return process_xacterr_on_intr(result_td, hcinfo);
+
+ else {
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ALL);
+
+ mask_channel_interrupt(result_td->cur_stransfer.
+ alloc_chnum, CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.
+ alloc_chnum, CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.
+ alloc_chnum, CH_STATUS_DataTglErr);
+ return RE_TRANSMIT;
+ }
+
+ } else {
+ if (hcinfo->hc_int.b.xfercompl)
+ return process_xfercompl_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.stall)
+ return process_stall_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.nak)
+ return process_nak_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.frmovrun)
+ return process_frmovrrun_on_intr(result_td, hcinfo);
+
+ else if (hcinfo->hc_int.b.xacterr)
+ return process_xacterr_on_intr(result_td, hcinfo);
+
+ else {
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum,
+ CH_STATUS_ALL);
+
+ mask_channel_interrupt(result_td->cur_stransfer.
+ alloc_chnum, CH_STATUS_ACK);
+ mask_channel_interrupt(result_td->cur_stransfer.
+ alloc_chnum, CH_STATUS_NAK);
+ mask_channel_interrupt(result_td->cur_stransfer.
+ alloc_chnum, CH_STATUS_DataTglErr);
+
+ result_td->err_cnt++;
+
+ if (result_td->err_cnt == 3) {
+ result_td->error_code = USB_ERR_STATUS_XACTERR;
+ result_td->err_cnt = 0;
+ return DE_ALLOCATE;
+ }
+
+ return RE_TRANSMIT;
+ }
+ }
+}
+
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_intr_transfer(struct td *result_td,
+ * struct hc_info *HCRegData)
+ *
+ * @brief this function processes the result of the Interrupt Transfer.
+ * firstly, this function checks the result of the Interrupt Transfer.
+ * and according to the result, calls the sub-functions
+ * to process the result.
+ *
+ *
+ * @param [IN] result_td
+ * -indicates the pointer of the struct td whose channel is interruped.
+ * [IN] HCRegData
+ * -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_TRANSMIT -if need to retransmit the result_td.
+ * RE_SCHEDULE -if need to reschedule the result_td.
+ * DE_ALLOCATE -if USB Transfer is completed.
+ * NO_ACTION -if we don't need any action,
+ */
+/******************************************************************************/
+static u8 process_intr_transfer(struct td *result_td,
+ struct hc_info *hcinfo)
+{
+ hcintn_t hc_intr_info;
+ u8 ret_val = 0;
+
+ hc_intr_info.d32 = hcinfo->hc_int.d32 &
+ result_td->cur_stransfer.hc_reg.hc_int_msk.d32;
+
+ if (result_td->parent_ed_p->ed_desc.is_ep_in) {
+
+ if (hc_intr_info.b.chhltd)
+ ret_val = process_chhltd_on_intr(result_td, hcinfo);
+
+ else if (hc_intr_info.b.ack)
+ ret_val = process_ack_on_intr(result_td, hcinfo);
+
+ } else {
+ if (hc_intr_info.b.chhltd)
+ ret_val = process_chhltd_on_intr(result_td, hcinfo);
+
+ else if (hc_intr_info.b.ack)
+ ret_val = process_ack_on_intr(result_td, hcinfo);
+ }
+
+ return ret_val;
+}
+
+
diff --git a/drivers/usb/host/xhci-exynos.c b/drivers/usb/host/xhci-exynos.c
new file mode 100644
index 0000000..7bd7ff2
--- /dev/null
+++ b/drivers/usb/host/xhci-exynos.c
@@ -0,0 +1,625 @@
+/* xhci-exynos.c - Driver for USB HOST on Samsung EXYNOS platform device
+ *
+ * Bus Glue for SAMSUNG EXYNOS USB HOST xHCI Controller
+ * xHCI host controller driver
+ *
+ * Copyright (C) 2008 Intel Corp.
+ * Copyright (C) 2011 Samsung Electronics Co.Ltd
+ *
+ * Author: Yulgon Kim <yulgon.kim@samsung.com>
+ *
+ * Based on "xhci-pci.c" by Sarah Sharp
+ * Modified for SAMSUNG EXYNOS XHCI by 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb/exynos_usb3_drd.h>
+#include <linux/platform_data/exynos_usb3_drd.h>
+
+#include "xhci.h"
+
+struct exynos_xhci_hcd {
+ struct device *dev;
+ struct usb_hcd *hcd;
+ struct clk *clk;
+ int irq;
+};
+
+struct xhci_hcd *exynos_xhci_dbg;
+
+static const char hcd_name[] = "xhci_hcd";
+
+static inline void __orr32(void __iomem *ptr, u32 val)
+{
+ writel(readl(ptr) | val, ptr);
+}
+
+static inline void __bic32(void __iomem *ptr, u32 val)
+{
+ writel(readl(ptr) & ~val, ptr);
+}
+
+static u32 exynos_xhci_change_mode(struct usb_hcd *hcd, bool host)
+{
+ u32 gctl;
+
+ gctl = readl(hcd->regs + EXYNOS_USB3_GCTL);
+ gctl &= ~(EXYNOS_USB3_GCTL_PrtCapDir_MASK |
+ EXYNOS_USB3_GCTL_FRMSCLDWN_MASK |
+ EXYNOS_USB3_GCTL_RAMClkSel_MASK);
+ gctl |= (EXYNOS_USB3_GCTL_FRMSCLDWN(0x1e85) | /* Power Down Scale */
+ EXYNOS_USB3_GCTL_RAMClkSel(0x2) | /* Ram Clock Select */
+ EXYNOS_USB3_GCTL_DisScramble);
+
+ if (host)
+ gctl |= EXYNOS_USB3_GCTL_PrtCapDir(0x1);/* 0x1 : Host */
+ else
+ gctl |= EXYNOS_USB3_GCTL_PrtCapDir(0x2);/* 0x2 : Device */
+
+ writel(gctl, hcd->regs + EXYNOS_USB3_GCTL);
+
+ printk(KERN_INFO "Change xHCI %s mode %x\n",
+ host ? "host" : "device", gctl);
+
+ return gctl;
+}
+
+static void exynos_xhci_phy_set(struct platform_device *pdev)
+{
+ struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data;
+ struct exynos_xhci_hcd *exynos_xhci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = exynos_xhci->hcd;
+ /* The reset values:
+ * GUSB2PHYCFG(0) = 0x00002400
+ * GUSB3PIPECTL(0) = 0x00260002
+ */
+
+ __orr32(hcd->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset);
+ __orr32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst);
+ __orr32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst);
+
+ /* PHY initialization */
+ if (pdata && pdata->phy_init)
+ pdata->phy_init(pdev, pdata->phy_type);
+
+ __bic32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst);
+ __bic32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst);
+ __bic32(hcd->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset);
+
+
+ __bic32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY |
+ EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM |
+ EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim_MASK);
+ __orr32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim(9));
+
+ __bic32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+
+ dev_dbg(exynos_xhci->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x",
+ readl(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0)),
+ readl(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0)));
+
+ /* Global core init */
+ writel(EXYNOS_USB3_GSBUSCFG0_INCR16BrstEna |
+ EXYNOS_USB3_GSBUSCFG0_INCR8BrstEna |
+ EXYNOS_USB3_GSBUSCFG0_INCR4BrstEna,
+ hcd->regs + EXYNOS_USB3_GSBUSCFG0);
+
+ writel(EXYNOS_USB3_GSBUSCFG1_BREQLIMIT(0x3),
+ hcd->regs + EXYNOS_USB3_GSBUSCFG1);
+
+ writel(0x0, hcd->regs + EXYNOS_USB3_GTXTHRCFG);
+ writel(0x0, hcd->regs + EXYNOS_USB3_GRXTHRCFG);
+}
+
+static void exynos_xhci_phy_unset(struct platform_device *pdev)
+{
+ struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data;
+ struct exynos_xhci_hcd *exynos_xhci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = exynos_xhci->hcd;
+
+ __orr32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0),
+ EXYNOS_USB3_GUSB2PHYCFGx_SusPHY |
+ EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM);
+
+ __orr32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0),
+ EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy);
+
+ dev_dbg(exynos_xhci->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x",
+ readl(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0)),
+ readl(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0)));
+
+ /* PHY shutdown */
+ if (pdata && pdata->phy_exit)
+ pdata->phy_exit(pdev, pdata->phy_type);
+}
+
+#ifdef CONFIG_PM
+static int exynos_xhci_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+ int retval = 0;
+
+ exynos_xhci = dev_get_drvdata(dev);
+ if (!exynos_xhci)
+ return -EINVAL;
+
+ hcd = exynos_xhci->hcd;
+ if (!hcd)
+ return -EINVAL;
+
+ xhci = hcd_to_xhci(hcd);
+
+ if (hcd->state != HC_STATE_SUSPENDED ||
+ xhci->shared_hcd->state != HC_STATE_SUSPENDED)
+ return -EINVAL;
+
+ if (pm_runtime_suspended(dev))
+ return 0;
+
+ retval = xhci_suspend(xhci);
+
+ exynos_xhci_change_mode(hcd, false);
+ exynos_xhci_phy_unset(pdev);
+ clk_disable(exynos_xhci->clk);
+
+ return retval;
+}
+
+static int exynos_xhci_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+ int retval = 0;
+
+ exynos_xhci = dev_get_drvdata(dev);
+ if (!exynos_xhci)
+ return -EINVAL;
+
+ hcd = exynos_xhci->hcd;
+ if (!hcd)
+ return -EINVAL;
+
+ pm_runtime_resume(dev);
+
+ clk_enable(exynos_xhci->clk);
+
+ exynos_xhci_change_mode(hcd, true);
+ exynos_xhci_phy_set(pdev);
+
+ xhci = hcd_to_xhci(hcd);
+ retval = xhci_resume(xhci, 0);
+
+ return retval;
+}
+#else
+#define exynos_xhci_suspend NULL
+#define exynos_xhci_resume NULL
+#endif
+
+#ifdef CONFIG_USB_SUSPEND
+static int exynos_xhci_runtime_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+ int retval = 0;
+
+ exynos_xhci = dev_get_drvdata(dev);
+ if (!exynos_xhci)
+ return -EINVAL;
+
+ hcd = exynos_xhci->hcd;
+ if (!hcd)
+ return -EINVAL;
+
+ xhci = hcd_to_xhci(hcd);
+
+ if (hcd->state != HC_STATE_SUSPENDED ||
+ xhci->shared_hcd->state != HC_STATE_SUSPENDED)
+ return -EINVAL;
+
+ retval = xhci_suspend(xhci);
+
+ exynos_xhci_change_mode(hcd, false);
+ exynos_xhci_phy_unset(pdev);
+ clk_disable(exynos_xhci->clk);
+#ifdef CONFIG_USB_EXYNOS_SWITCH
+ disable_irq(exynos_xhci->irq);
+#endif
+ return retval;
+}
+
+static int exynos_xhci_runtime_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+ int retval = 0;
+
+ exynos_xhci = dev_get_drvdata(dev);
+ if (!exynos_xhci)
+ return -EINVAL;
+
+ hcd = exynos_xhci->hcd;
+ if (!hcd)
+ return -EINVAL;
+
+#ifdef CONFIG_USB_EXYNOS_SWITCH
+ enable_irq(exynos_xhci->irq);
+#endif
+ if (dev->power.is_suspended)
+ return 0;
+
+ clk_enable(exynos_xhci->clk);
+
+ exynos_xhci_change_mode(hcd, true);
+ exynos_xhci_phy_set(pdev);
+
+ xhci = hcd_to_xhci(hcd);
+ retval = xhci_resume(xhci, 0);
+
+ return retval;
+}
+#else
+#define exynos_xhci_runtime_suspend NULL
+#define exynos_xhci_runtime_resume NULL
+#endif
+
+int exynos_xhci_bus_resume(struct usb_hcd *hcd)
+{
+ /* When suspend is failed, re-enable clocks & PHY */
+ pm_runtime_resume(hcd->self.controller);
+
+ return xhci_bus_resume(hcd);
+}
+
+static void exynos_xhci_quirks(struct device *dev, struct xhci_hcd *xhci)
+{
+ /* Don't use MSI interrupt */
+ xhci->quirks |= XHCI_BROKEN_MSI;
+}
+
+/* called during probe() after chip reset completes */
+static int exynos_xhci_setup(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci;
+ int retval;
+
+ retval = xhci_gen_setup(hcd, exynos_xhci_quirks);
+ if (retval)
+ return retval;
+
+ xhci = hcd_to_xhci(hcd);
+ if (!usb_hcd_is_primary_hcd(hcd))
+ return 0;
+
+ xhci->sbrn = HCD_USB3;
+ xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int) xhci->sbrn);
+
+ return retval;
+}
+
+
+static const struct hc_driver exynos_xhci_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "EXYNOS xHCI Host Controller",
+ .hcd_priv_size = sizeof(struct xhci_hcd *),
+
+ /*
+ * generic hardware linkage
+ */
+ .irq = xhci_irq,
+ .flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED,
+
+ /*
+ * basic lifecycle operations
+ */
+ .reset = exynos_xhci_setup,
+ .start = xhci_run,
+ .stop = xhci_stop,
+ .shutdown = xhci_shutdown,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = xhci_urb_enqueue,
+ .urb_dequeue = xhci_urb_dequeue,
+ .alloc_dev = xhci_alloc_dev,
+ .free_dev = xhci_free_dev,
+ .alloc_streams = xhci_alloc_streams,
+ .free_streams = xhci_free_streams,
+ .add_endpoint = xhci_add_endpoint,
+ .drop_endpoint = xhci_drop_endpoint,
+ .endpoint_reset = xhci_endpoint_reset,
+ .check_bandwidth = xhci_check_bandwidth,
+ .reset_bandwidth = xhci_reset_bandwidth,
+ .address_device = xhci_address_device,
+ .update_hub_device = xhci_update_hub_device,
+ .reset_device = xhci_discover_or_reset_device,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = xhci_get_frame,
+
+ /* Root hub support */
+ .hub_control = xhci_hub_control,
+ .hub_status_data = xhci_hub_status_data,
+ .bus_suspend = xhci_bus_suspend,
+ .bus_resume = exynos_xhci_bus_resume,
+};
+
+static int usb_hcd_exynos_probe(struct platform_device *pdev, const struct hc_driver *driver)
+{
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct exynos_xhci_plat *pdata;
+ struct usb_hcd *hcd;
+ struct resource *res;
+ int err;
+
+ if (usb_disabled())
+ return -ENODEV;
+
+ if (!driver)
+ return -EINVAL;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data defined\n");
+ return -EINVAL;
+ }
+
+ exynos_xhci = kzalloc(sizeof(struct exynos_xhci_hcd), GFP_KERNEL);
+ if (!exynos_xhci)
+ return -ENOMEM;
+
+ hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev));
+ if (!hcd) {
+ err = -ENOMEM;
+ goto fail_hcd;
+ }
+
+
+ exynos_xhci->clk = clk_get(&pdev->dev, "usbdrd30");
+ if (IS_ERR(exynos_xhci->clk)) {
+ dev_err(&pdev->dev, "Failed to get usbhost clock\n");
+ err = PTR_ERR(exynos_xhci->clk);
+ goto fail_clk;
+ }
+
+ err = clk_enable(exynos_xhci->clk);
+ if (err)
+ goto fail_clken;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get I/O memory\n");
+ err = -ENXIO;
+ goto fail_io;
+ }
+
+ /* EHCI, OHCI */
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = ioremap(res->start, resource_size(res));
+ if (!hcd->regs) {
+ dev_err(&pdev->dev, "Failed to remap I/O memory\n");
+ err = -ENOMEM;
+ goto fail_io;
+ }
+
+ exynos_xhci->irq = platform_get_irq(pdev, 0);
+ if (!exynos_xhci->irq) {
+ dev_err(&pdev->dev, "Failed to get IRQ\n");
+ err = -ENODEV;
+ goto fail;
+ }
+
+ exynos_xhci->dev = &pdev->dev;
+ exynos_xhci->hcd = hcd;
+ platform_set_drvdata(pdev, exynos_xhci);
+
+ exynos_xhci_change_mode(hcd, true);
+ exynos_xhci_phy_set(pdev);
+
+ err = usb_add_hcd(hcd, exynos_xhci->irq, IRQF_DISABLED | IRQF_SHARED);
+ if (err) {
+ dev_err(&pdev->dev, "Failed to add USB HCD\n");
+ goto fail;
+ }
+
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ return err;
+
+fail:
+ iounmap(hcd->regs);
+fail_io:
+ clk_disable(exynos_xhci->clk);
+fail_clken:
+ clk_put(exynos_xhci->clk);
+fail_clk:
+ usb_put_hcd(hcd);
+fail_hcd:
+ kfree(exynos_xhci);
+ return err;
+}
+
+void usb_hcd_exynos_remove(struct platform_device *pdev)
+{
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct usb_hcd *hcd;
+
+ exynos_xhci = dev_get_drvdata(&pdev->dev);
+ hcd = exynos_xhci->hcd;
+ if (!hcd)
+ return;
+
+ pm_runtime_disable(&pdev->dev);
+ /* Fake an interrupt request in order to give the driver a chance
+ * to test whether the controller hardware has been removed (e.g.,
+ * cardbus physical eject).
+ */
+ local_irq_disable();
+ usb_hcd_irq(0, hcd);
+ local_irq_enable();
+
+ usb_remove_hcd(hcd);
+
+ exynos_xhci_change_mode(hcd, false);
+ exynos_xhci_phy_unset(pdev);
+
+ if (hcd->driver->flags & HCD_MEMORY) {
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+ } else {
+ release_region(hcd->rsrc_start, hcd->rsrc_len);
+ }
+ usb_put_hcd(hcd);
+
+ kfree(exynos_xhci);
+ clk_disable(exynos_xhci->clk);
+ clk_put(exynos_xhci->clk);
+}
+
+static int __devinit exynos_xhci_probe(struct platform_device *pdev)
+{
+ struct exynos_xhci_hcd *exynos_xhci;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+ int err;
+
+ /* Register the USB 2.0 roothub.
+ * FIXME: USB core must know to register the USB 2.0 roothub first.
+ * This is sort of silly, because we could just set the HCD driver flags
+ * to say USB 2.0, but I'm not sure what the implications would be in
+ * the other parts of the HCD code.
+ */
+ err = usb_hcd_exynos_probe(pdev, &exynos_xhci_hc_driver);
+
+ if (err)
+ return err;
+
+ exynos_xhci = dev_get_drvdata(&pdev->dev);
+ hcd = exynos_xhci->hcd;
+ xhci = hcd_to_xhci(hcd);
+ xhci->shared_hcd = usb_create_shared_hcd(&exynos_xhci_hc_driver,
+ &pdev->dev, dev_name(&pdev->dev), hcd);
+ if (!hcd) {
+ dev_err(&pdev->dev, "Unable to create HCD\n");
+ err = -ENOMEM;
+ goto dealloc_usb2_hcd;
+ }
+
+ exynos_xhci_dbg = xhci;
+ /* Set the xHCI pointer before exynos_xhci_setup() (aka hcd_driver.reset)
+ * is called by usb_add_hcd().
+ */
+ *((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci;
+
+ err = usb_add_hcd(xhci->shared_hcd, exynos_xhci->irq,
+ IRQF_DISABLED | IRQF_SHARED);
+ if (err)
+ goto put_usb3_hcd;
+
+ /* Roothub already marked as USB 3.0 speed */
+ return err;
+
+put_usb3_hcd:
+ usb_put_hcd(xhci->shared_hcd);
+dealloc_usb2_hcd:
+ usb_remove_hcd(hcd);
+ usb_hcd_exynos_remove(pdev);
+
+ return err;
+}
+
+static int __devexit exynos_xhci_remove(struct platform_device *pdev)
+{
+ struct exynos_xhci_hcd *exynos_xhci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = exynos_xhci->hcd;
+ struct xhci_hcd *xhci;
+
+ xhci = hcd_to_xhci(hcd);
+ if (xhci->shared_hcd) {
+ usb_remove_hcd(xhci->shared_hcd);
+ usb_put_hcd(xhci->shared_hcd);
+ }
+ usb_remove_hcd(hcd);
+
+ usb_hcd_exynos_remove(pdev);
+ kfree(xhci);
+
+ return 0;
+}
+
+static void exynos_xhci_shutdown(struct platform_device *pdev)
+{
+ struct exynos_xhci_hcd *s5p_xhci = platform_get_drvdata(pdev);
+ struct usb_hcd *hcd = s5p_xhci->hcd;
+
+ if (hcd->driver->shutdown)
+ hcd->driver->shutdown(hcd);
+}
+
+static const struct dev_pm_ops exynos_xhci_pm_ops = {
+ .suspend = exynos_xhci_suspend,
+ .resume = exynos_xhci_resume,
+ .runtime_suspend = exynos_xhci_runtime_suspend,
+ .runtime_resume = exynos_xhci_runtime_resume,
+};
+
+static struct platform_driver exynos_xhci_driver = {
+ .probe = exynos_xhci_probe,
+ .remove = __devexit_p(exynos_xhci_remove),
+ .shutdown = exynos_xhci_shutdown,
+ .driver = {
+ .name = "exynos-xhci",
+ .owner = THIS_MODULE,
+ .pm = &exynos_xhci_pm_ops,
+ }
+};
+
+int xhci_register_exynos(void)
+{
+ return platform_driver_register(&exynos_xhci_driver);
+}
+
+void xhci_unregister_exynos(void)
+{
+ platform_driver_unregister(&exynos_xhci_driver);
+}
+
+MODULE_ALIAS("platform:exynos-xhci");
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index ffeee57..61a0cf3 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -319,7 +319,7 @@ static void xhci_free_stream_ctx(struct xhci_hcd *xhci,
struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE)
- pci_free_consistent(pdev,
+ dma_free_coherent(&pdev->dev,
sizeof(struct xhci_stream_ctx)*num_stream_ctxs,
stream_ctx, dma);
else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE)
@@ -347,9 +347,9 @@ static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci,
struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE)
- return pci_alloc_consistent(pdev,
+ return dma_alloc_coherent(&pdev->dev,
sizeof(struct xhci_stream_ctx)*num_stream_ctxs,
- dma);
+ dma, mem_flags);
else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE)
return dma_pool_alloc(xhci->small_streams_pool,
mem_flags, dma);
@@ -1344,10 +1344,9 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
if (!xhci->scratchpad)
goto fail_sp;
- xhci->scratchpad->sp_array =
- pci_alloc_consistent(to_pci_dev(dev),
+ xhci->scratchpad->sp_array = dma_alloc_coherent(dev,
num_sp * sizeof(u64),
- &xhci->scratchpad->sp_dma);
+ &xhci->scratchpad->sp_dma, flags);
if (!xhci->scratchpad->sp_array)
goto fail_sp2;
@@ -1364,8 +1363,8 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma);
for (i = 0; i < num_sp; i++) {
dma_addr_t dma;
- void *buf = pci_alloc_consistent(to_pci_dev(dev),
- xhci->page_size, &dma);
+ void *buf = dma_alloc_coherent(dev, xhci->page_size, &dma,
+ flags);
if (!buf)
goto fail_sp5;
@@ -1378,7 +1377,7 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
fail_sp5:
for (i = i - 1; i >= 0; i--) {
- pci_free_consistent(to_pci_dev(dev), xhci->page_size,
+ dma_free_coherent(dev, xhci->page_size,
xhci->scratchpad->sp_buffers[i],
xhci->scratchpad->sp_dma_buffers[i]);
}
@@ -1388,7 +1387,7 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags)
kfree(xhci->scratchpad->sp_buffers);
fail_sp3:
- pci_free_consistent(to_pci_dev(dev), num_sp * sizeof(u64),
+ dma_free_coherent(dev, num_sp * sizeof(u64),
xhci->scratchpad->sp_array,
xhci->scratchpad->sp_dma);
@@ -1412,13 +1411,13 @@ static void scratchpad_free(struct xhci_hcd *xhci)
num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2);
for (i = 0; i < num_sp; i++) {
- pci_free_consistent(pdev, xhci->page_size,
+ dma_free_coherent(&pdev->dev, xhci->page_size,
xhci->scratchpad->sp_buffers[i],
xhci->scratchpad->sp_dma_buffers[i]);
}
kfree(xhci->scratchpad->sp_dma_buffers);
kfree(xhci->scratchpad->sp_buffers);
- pci_free_consistent(pdev, num_sp * sizeof(u64),
+ dma_free_coherent(&pdev->dev, num_sp * sizeof(u64),
xhci->scratchpad->sp_array,
xhci->scratchpad->sp_dma);
kfree(xhci->scratchpad);
@@ -1500,7 +1499,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
}
size = sizeof(struct xhci_erst_entry)*(xhci->erst.num_entries);
if (xhci->erst.entries)
- pci_free_consistent(pdev, size,
+ dma_free_coherent(&pdev->dev, size,
xhci->erst.entries, xhci->erst.erst_dma_addr);
xhci->erst.entries = NULL;
xhci_dbg(xhci, "Freed ERST\n");
@@ -1540,7 +1539,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci_write_64(xhci, 0, &xhci->op_regs->dcbaa_ptr);
if (xhci->dcbaa)
- pci_free_consistent(pdev, sizeof(*xhci->dcbaa),
+ dma_free_coherent(&pdev->dev, sizeof(*xhci->dcbaa),
xhci->dcbaa, xhci->dcbaa->dma);
xhci->dcbaa = NULL;
@@ -1959,8 +1958,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
* Section 5.4.8 - doorbell array must be
* "physically contiguous and 64-byte (cache line) aligned".
*/
- xhci->dcbaa = pci_alloc_consistent(to_pci_dev(dev),
- sizeof(*xhci->dcbaa), &dma);
+ xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma,
+ GFP_KERNEL);
if (!xhci->dcbaa)
goto fail;
memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa));
@@ -1994,7 +1993,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
dma_pool_create("xHCI 1KB stream ctx arrays",
dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0);
/* Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE
- * will be allocated with pci_alloc_consistent()
+ * will be allocated with dma_alloc_coherent()
*/
if (!xhci->small_streams_pool || !xhci->medium_streams_pool)
@@ -2039,8 +2038,9 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
if (xhci_check_trb_in_td_math(xhci, flags) < 0)
goto fail;
- xhci->erst.entries = pci_alloc_consistent(to_pci_dev(dev),
- sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS, &dma);
+ xhci->erst.entries = dma_alloc_coherent(dev,
+ sizeof(struct xhci_erst_entry) * ERST_NUM_SEGS, &dma,
+ GFP_KERNEL);
if (!xhci->erst.entries)
goto fail;
xhci_dbg(xhci, "// Allocated event ring segment table at 0x%llx\n",
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 50e7156..dc78c63 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -51,61 +51,9 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev)
return 0;
}
-/* called during probe() after chip reset completes */
-static int xhci_pci_setup(struct usb_hcd *hcd)
+static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
{
- struct xhci_hcd *xhci;
- struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
- int retval;
- u32 temp;
-
- hcd->self.sg_tablesize = TRBS_PER_SEGMENT - 2;
-
- if (usb_hcd_is_primary_hcd(hcd)) {
- xhci = kzalloc(sizeof(struct xhci_hcd), GFP_KERNEL);
- if (!xhci)
- return -ENOMEM;
- *((struct xhci_hcd **) hcd->hcd_priv) = xhci;
- xhci->main_hcd = hcd;
- /* Mark the first roothub as being USB 2.0.
- * The xHCI driver will register the USB 3.0 roothub.
- */
- hcd->speed = HCD_USB2;
- hcd->self.root_hub->speed = USB_SPEED_HIGH;
- /*
- * USB 2.0 roothub under xHCI has an integrated TT,
- * (rate matching hub) as opposed to having an OHCI/UHCI
- * companion controller.
- */
- hcd->has_tt = 1;
- } else {
- /* xHCI private pointer was set in xhci_pci_probe for the second
- * registered roothub.
- */
- xhci = hcd_to_xhci(hcd);
- temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params);
- if (HCC_64BIT_ADDR(temp)) {
- xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n");
- dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64));
- } else {
- dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32));
- }
- return 0;
- }
-
- xhci->cap_regs = hcd->regs;
- xhci->op_regs = hcd->regs +
- HC_LENGTH(xhci_readl(xhci, &xhci->cap_regs->hc_capbase));
- xhci->run_regs = hcd->regs +
- (xhci_readl(xhci, &xhci->cap_regs->run_regs_off) & RTSOFF_MASK);
- /* Cache read-only capability registers */
- xhci->hcs_params1 = xhci_readl(xhci, &xhci->cap_regs->hcs_params1);
- xhci->hcs_params2 = xhci_readl(xhci, &xhci->cap_regs->hcs_params2);
- xhci->hcs_params3 = xhci_readl(xhci, &xhci->cap_regs->hcs_params3);
- xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hc_capbase);
- xhci->hci_version = HC_VERSION(xhci->hcc_params);
- xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hcc_params);
- xhci_print_registers(xhci);
+ struct pci_dev *pdev = to_pci_dev(dev);
/* Look for vendor-specific quirks */
if (pdev->vendor == PCI_VENDOR_ID_FRESCO_LOGIC &&
@@ -145,33 +93,22 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
xhci->quirks |= XHCI_RESET_ON_RESUME;
xhci_dbg(xhci, "QUIRK: Resetting on resume\n");
}
+}
- /* Make sure the HC is halted. */
- retval = xhci_halt(xhci);
- if (retval)
- goto error;
+/* called during probe() after chip reset completes */
+static int xhci_pci_setup(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci;
+ struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
+ int retval;
- xhci_dbg(xhci, "Resetting HCD\n");
- /* Reset the internal HC memory state and registers. */
- retval = xhci_reset(xhci);
+ retval = xhci_gen_setup(hcd, xhci_pci_quirks);
if (retval)
- goto error;
- xhci_dbg(xhci, "Reset complete\n");
-
- temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params);
- if (HCC_64BIT_ADDR(temp)) {
- xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n");
- dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64));
- } else {
- dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32));
- }
+ return retval;
- xhci_dbg(xhci, "Calling HCD init\n");
- /* Initialize HCD and host controller data structures. */
- retval = xhci_init(hcd);
- if (retval)
- goto error;
- xhci_dbg(xhci, "Called HCD init\n");
+ xhci = hcd_to_xhci(hcd);
+ if (!usb_hcd_is_primary_hcd(hcd))
+ return 0;
pci_read_config_byte(pdev, XHCI_SBRN_OFFSET, &xhci->sbrn);
xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int) xhci->sbrn);
@@ -181,7 +118,6 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
if (!retval)
return retval;
-error:
kfree(xhci);
return retval;
}
@@ -378,12 +314,12 @@ static struct pci_driver xhci_pci_driver = {
#endif
};
-int xhci_register_pci(void)
+int __init xhci_register_pci(void)
{
return pci_register_driver(&xhci_pci_driver);
}
-void xhci_unregister_pci(void)
+void __exit xhci_unregister_pci(void)
{
pci_unregister_driver(&xhci_pci_driver);
}
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index b4b0691..9615b80 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -1647,7 +1647,6 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td,
}
break;
case COMP_SHORT_TX:
- xhci_warn(xhci, "WARN: short transfer on control ep\n");
if (td->urb->transfer_flags & URB_SHORT_NOT_OK)
*status = -EREMOTEIO;
else
@@ -1993,7 +1992,7 @@ static int handle_tx_event(struct xhci_hcd *xhci,
xhci_dbg(xhci, "Stopped on No-op or Link TRB\n");
break;
case COMP_STALL:
- xhci_warn(xhci, "WARN: Stalled endpoint\n");
+ xhci_dbg(xhci, "Stalled endpoint\n");
ep->ep_state |= EP_HALTED;
status = -EPIPE;
break;
@@ -2003,11 +2002,11 @@ static int handle_tx_event(struct xhci_hcd *xhci,
break;
case COMP_SPLIT_ERR:
case COMP_TX_ERR:
- xhci_warn(xhci, "WARN: transfer error on endpoint\n");
+ xhci_dbg(xhci, "Transfer error on endpoint\n");
status = -EPROTO;
break;
case COMP_BABBLE:
- xhci_warn(xhci, "WARN: babble error on endpoint\n");
+ xhci_dbg(xhci, "Babble error on endpoint\n");
status = -EOVERFLOW;
break;
case COMP_DB_ERR:
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 221f14e..7605e46 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -175,28 +175,19 @@ int xhci_reset(struct xhci_hcd *xhci)
return handshake(xhci, &xhci->op_regs->status, STS_CNR, 0, 250 * 1000);
}
-/*
- * Free IRQs
- * free all IRQs request
- */
-static void xhci_free_irq(struct xhci_hcd *xhci)
+#ifdef CONFIG_PCI
+static int xhci_free_msi(struct xhci_hcd *xhci)
{
int i;
- struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
-
- /* return if using legacy interrupt */
- if (xhci_to_hcd(xhci)->irq >= 0)
- return;
- if (xhci->msix_entries) {
- for (i = 0; i < xhci->msix_count; i++)
- if (xhci->msix_entries[i].vector)
- free_irq(xhci->msix_entries[i].vector,
- xhci_to_hcd(xhci));
- } else if (pdev->irq >= 0)
- free_irq(pdev->irq, xhci_to_hcd(xhci));
+ if (!xhci->msix_entries)
+ return -EINVAL;
- return;
+ for (i = 0; i < xhci->msix_count; i++)
+ if (xhci->msix_entries[i].vector)
+ free_irq(xhci->msix_entries[i].vector,
+ xhci_to_hcd(xhci));
+ return 0;
}
/*
@@ -224,6 +215,28 @@ static int xhci_setup_msi(struct xhci_hcd *xhci)
}
/*
+ * Free IRQs
+ * free all IRQs request
+ */
+static void xhci_free_irq(struct xhci_hcd *xhci)
+{
+ struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
+ int ret;
+
+ /* return if using legacy interrupt */
+ if (xhci_to_hcd(xhci)->irq >= 0)
+ return;
+
+ ret = xhci_free_msi(xhci);
+ if (!ret)
+ return;
+ if (pdev->irq >= 0)
+ free_irq(pdev->irq, xhci_to_hcd(xhci));
+
+ return;
+}
+
+/*
* Set up MSI-X
*/
static int xhci_setup_msix(struct xhci_hcd *xhci)
@@ -302,6 +315,72 @@ static void xhci_cleanup_msix(struct xhci_hcd *xhci)
return;
}
+static void xhci_msix_sync_irqs(struct xhci_hcd *xhci)
+{
+ int i;
+
+ if (xhci->msix_entries) {
+ for (i = 0; i < xhci->msix_count; i++)
+ synchronize_irq(xhci->msix_entries[i].vector);
+ }
+}
+
+static int xhci_try_enable_msi(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
+ int ret;
+
+ /*
+ * Some Fresco Logic host controllers advertise MSI, but fail to
+ * generate interrupts. Don't even try to enable MSI.
+ */
+ if (xhci->quirks & XHCI_BROKEN_MSI)
+ return 0;
+
+ /* unregister the legacy interrupt */
+ if (hcd->irq)
+ free_irq(hcd->irq, hcd);
+ hcd->irq = -1;
+
+ ret = xhci_setup_msix(xhci);
+ if (ret)
+ /* fall back to msi*/
+ ret = xhci_setup_msi(xhci);
+
+ if (!ret)
+ /* hcd->irq is -1, we have MSI */
+ return 0;
+
+ /* fall back to legacy interrupt*/
+ ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED,
+ hcd->irq_descr, hcd);
+ if (ret) {
+ xhci_err(xhci, "request interrupt %d failed\n",
+ pdev->irq);
+ return ret;
+ }
+ hcd->irq = pdev->irq;
+ return 0;
+}
+
+#else
+
+static int xhci_try_enable_msi(struct usb_hcd *hcd)
+{
+ return 0;
+}
+
+static void xhci_cleanup_msix(struct xhci_hcd *xhci)
+{
+}
+
+static void xhci_msix_sync_irqs(struct xhci_hcd *xhci)
+{
+}
+
+#endif
+
/*
* Initialize memory for HCD and xHC (one-time init).
*
@@ -413,9 +492,8 @@ int xhci_run(struct usb_hcd *hcd)
{
u32 temp;
u64 temp_64;
- u32 ret;
+ int ret;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
- struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller);
/* Start the xHCI host controller running only after the USB 2.0 roothub
* is setup.
@@ -426,34 +504,10 @@ int xhci_run(struct usb_hcd *hcd)
return xhci_run_finished(xhci);
xhci_dbg(xhci, "xhci_run\n");
- /* unregister the legacy interrupt */
- if (hcd->irq)
- free_irq(hcd->irq, hcd);
- hcd->irq = -1;
-
- /* Some Fresco Logic host controllers advertise MSI, but fail to
- * generate interrupts. Don't even try to enable MSI.
- */
- if (xhci->quirks & XHCI_BROKEN_MSI)
- goto legacy_irq;
- ret = xhci_setup_msix(xhci);
+ ret = xhci_try_enable_msi(hcd);
if (ret)
- /* fall back to msi*/
- ret = xhci_setup_msi(xhci);
-
- if (ret) {
-legacy_irq:
- /* fall back to legacy interrupt*/
- ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED,
- hcd->irq_descr, hcd);
- if (ret) {
- xhci_err(xhci, "request interrupt %d failed\n",
- pdev->irq);
- return ret;
- }
- hcd->irq = pdev->irq;
- }
+ return ret;
#ifdef CONFIG_USB_XHCI_HCD_DEBUGGING
init_timer(&xhci->event_ring_timer);
@@ -697,7 +751,6 @@ int xhci_suspend(struct xhci_hcd *xhci)
int rc = 0;
struct usb_hcd *hcd = xhci_to_hcd(xhci);
u32 command;
- int i;
spin_lock_irq(&xhci->lock);
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags);
@@ -733,10 +786,7 @@ int xhci_suspend(struct xhci_hcd *xhci)
/* step 5: remove core well power */
/* synchronize irq when using MSI-X */
- if (xhci->msix_entries) {
- for (i = 0; i < xhci->msix_count; i++)
- synchronize_irq(xhci->msix_entries[i].vector);
- }
+ xhci_msix_sync_irqs(xhci);
return rc;
}
@@ -3064,17 +3114,110 @@ int xhci_get_frame(struct usb_hcd *hcd)
return xhci_readl(xhci, &xhci->run_regs->microframe_index) >> 3;
}
+int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks)
+{
+ struct xhci_hcd *xhci;
+ struct device *dev = hcd->self.controller;
+ int retval;
+ u32 temp;
+
+ hcd->self.sg_tablesize = TRBS_PER_SEGMENT - 2;
+
+ if (usb_hcd_is_primary_hcd(hcd)) {
+ xhci = kzalloc(sizeof(struct xhci_hcd), GFP_KERNEL);
+ if (!xhci)
+ return -ENOMEM;
+ *((struct xhci_hcd **) hcd->hcd_priv) = xhci;
+ xhci->main_hcd = hcd;
+ /* Mark the first roothub as being USB 2.0.
+ * The xHCI driver will register the USB 3.0 roothub.
+ */
+ hcd->speed = HCD_USB2;
+ hcd->self.root_hub->speed = USB_SPEED_HIGH;
+ /*
+ * USB 2.0 roothub under xHCI has an integrated TT,
+ * (rate matching hub) as opposed to having an OHCI/UHCI
+ * companion controller.
+ */
+ hcd->has_tt = 1;
+ } else {
+ /* xHCI private pointer was set in xhci_pci_probe for the second
+ * registered roothub.
+ */
+ xhci = hcd_to_xhci(hcd);
+ temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params);
+ if (HCC_64BIT_ADDR(temp)) {
+ xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n");
+ dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64));
+ } else {
+ dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32));
+ }
+ return 0;
+ }
+
+ xhci->cap_regs = hcd->regs;
+ xhci->op_regs = hcd->regs +
+ HC_LENGTH(xhci_readl(xhci, &xhci->cap_regs->hc_capbase));
+ xhci->run_regs = hcd->regs +
+ (xhci_readl(xhci, &xhci->cap_regs->run_regs_off) & RTSOFF_MASK);
+ /* Cache read-only capability registers */
+ xhci->hcs_params1 = xhci_readl(xhci, &xhci->cap_regs->hcs_params1);
+ xhci->hcs_params2 = xhci_readl(xhci, &xhci->cap_regs->hcs_params2);
+ xhci->hcs_params3 = xhci_readl(xhci, &xhci->cap_regs->hcs_params3);
+ xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hc_capbase);
+ xhci->hci_version = HC_VERSION(xhci->hcc_params);
+ xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hcc_params);
+ xhci_print_registers(xhci);
+
+ get_quirks(dev, xhci);
+
+ /* Make sure the HC is halted. */
+ retval = xhci_halt(xhci);
+ if (retval)
+ goto error;
+
+ xhci_dbg(xhci, "Resetting HCD\n");
+ /* Reset the internal HC memory state and registers. */
+ retval = xhci_reset(xhci);
+ if (retval)
+ goto error;
+ xhci_dbg(xhci, "Reset complete\n");
+
+ temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params);
+ if (HCC_64BIT_ADDR(temp)) {
+ xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n");
+ dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64));
+ } else {
+ dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32));
+ }
+
+ xhci_dbg(xhci, "Calling HCD init\n");
+ /* Initialize HCD and host controller data structures. */
+ retval = xhci_init(hcd);
+ if (retval)
+ goto error;
+ xhci_dbg(xhci, "Called HCD init\n");
+ return 0;
+error:
+ kfree(xhci);
+ return retval;
+}
+
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_LICENSE("GPL");
static int __init xhci_hcd_init(void)
{
-#ifdef CONFIG_PCI
- int retval = 0;
-
+ int retval;
+#ifdef CONFIG_USB_XHCI_EXYNOS
+ retval = xhci_register_exynos();
+ if (retval < 0) {
+ printk(KERN_DEBUG "Problem registering Exynos driver.");
+ return retval;
+ }
+#else
retval = xhci_register_pci();
-
if (retval < 0) {
printk(KERN_DEBUG "Problem registering PCI driver.");
return retval;
@@ -3104,7 +3247,9 @@ module_init(xhci_hcd_init);
static void __exit xhci_hcd_cleanup(void)
{
-#ifdef CONFIG_PCI
+#ifdef CONFIG_USB_XHCI_EXYNOS
+ xhci_unregister_exynos();
+#else
xhci_unregister_pci();
#endif
}
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 49ce76c..3bd0d41 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1477,9 +1477,16 @@ void xhci_free_command(struct xhci_hcd *xhci,
/* xHCI PCI glue */
int xhci_register_pci(void);
void xhci_unregister_pci(void);
+#elif CONFIG_USB_XHCI_EXYNOS
+int xhci_register_exynos(void);
+void xhci_unregister_exynos(void);
+#else
+static inline int xhci_register_pci(void) { return 0; }
+static inline void xhci_unregister_pci(void) {}
#endif
/* xHCI host controller glue */
+typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *);
void xhci_quiesce(struct xhci_hcd *xhci);
int xhci_halt(struct xhci_hcd *xhci);
int xhci_reset(struct xhci_hcd *xhci);
@@ -1487,6 +1494,7 @@ int xhci_init(struct usb_hcd *hcd);
int xhci_run(struct usb_hcd *hcd);
void xhci_stop(struct usb_hcd *hcd);
void xhci_shutdown(struct usb_hcd *hcd);
+int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
#ifdef CONFIG_PM
int xhci_suspend(struct xhci_hcd *xhci);
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");
diff --git a/drivers/usb/notify/Kconfig b/drivers/usb/notify/Kconfig
new file mode 100644
index 0000000..fef8c88
--- /dev/null
+++ b/drivers/usb/notify/Kconfig
@@ -0,0 +1,11 @@
+#
+# USB Host notify configuration
+#
+
+config USB_HOST_NOTIFY
+ boolean "USB Host notify Driver"
+ depends on USB
+ help
+ Android framework needs uevents for usb host operation.
+ Host notify Driver serves uevent format
+ that is used by usb host or otg. \ No newline at end of file
diff --git a/drivers/usb/notify/Makefile b/drivers/usb/notify/Makefile
new file mode 100644
index 0000000..5fd4512
--- /dev/null
+++ b/drivers/usb/notify/Makefile
@@ -0,0 +1,4 @@
+
+# host notify driver
+obj-y += host_notify_class.o
+obj-y += host_notifier.o
diff --git a/drivers/usb/notify/host_notifier.c b/drivers/usb/notify/host_notifier.c
new file mode 100644
index 0000000..613e84b
--- /dev/null
+++ b/drivers/usb/notify/host_notifier.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co. Ltd.
+ * Hyuk Kang <hyuk78.kang@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/kthread.h>
+#include <linux/wakelock.h>
+#include <linux/host_notify.h>
+
+struct host_notifier_info {
+ struct host_notifier_platform_data *pdata;
+ struct task_struct *th;
+ struct wake_lock wlock;
+ struct delayed_work current_dwork;
+ wait_queue_head_t delay_wait;
+ int thread_remove;
+ int currentlimit_irq;
+};
+
+static struct host_notifier_info ninfo;
+
+static int currentlimit_thread(void *data)
+{
+ struct host_notifier_info *ninfo = data;
+ struct host_notify_dev *ndev = &ninfo->pdata->ndev;
+ int gpio = ninfo->pdata->gpio;
+ int prev = ndev->booster;
+ int ret = 0;
+
+ pr_info("host_notifier usbhostd: start %d\n", prev);
+
+ while (!kthread_should_stop()) {
+ wait_event_interruptible_timeout(ninfo->delay_wait,
+ ninfo->thread_remove, 1 * HZ);
+
+ ret = gpio_get_value(gpio);
+ if (prev != ret) {
+ pr_info("host_notifier usbhostd: gpio %d = %s\n",
+ gpio, ret ? "HIGH" : "LOW");
+ ndev->booster = ret ?
+ NOTIFY_POWER_ON : NOTIFY_POWER_OFF;
+ prev = ret;
+
+ if (!ret && ndev->mode == NOTIFY_HOST_MODE) {
+ host_state_notify(ndev,
+ NOTIFY_HOST_OVERCURRENT);
+ pr_err("host_notifier usbhostd: overcurrent\n");
+ break;
+ }
+ }
+ }
+
+ ninfo->thread_remove = 1;
+
+ pr_info("host_notifier usbhostd: exit %d\n", ret);
+ return 0;
+}
+
+static int start_usbhostd_thread(void)
+{
+ if (!ninfo.th) {
+ pr_info("host_notifier: start usbhostd thread\n");
+
+ init_waitqueue_head(&ninfo.delay_wait);
+ ninfo.thread_remove = 0;
+ ninfo.th = kthread_run(currentlimit_thread,
+ &ninfo, "usbhostd");
+
+ if (IS_ERR(ninfo.th)) {
+ pr_err("host_notifier: Unable to start usbhostd\n");
+ ninfo.th = NULL;
+ ninfo.thread_remove = 1;
+ return -1;
+ }
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_ADD);
+ wake_lock(&ninfo.wlock);
+
+ } else
+ pr_info("host_notifier: usbhostd already started!\n");
+
+ return 0;
+}
+
+static int stop_usbhostd_thread(void)
+{
+ if (ninfo.th) {
+ pr_info("host_notifier: stop thread\n");
+
+ if (!ninfo.thread_remove)
+ kthread_stop(ninfo.th);
+
+ ninfo.th = NULL;
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_REMOVE);
+ wake_unlock(&ninfo.wlock);
+ } else
+ pr_info("host_notifier: no thread\n");
+
+ return 0;
+}
+
+static int start_usbhostd_notify(void)
+{
+ pr_info("host_notifier: start usbhostd notify\n");
+
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_ADD);
+ wake_lock(&ninfo.wlock);
+
+ return 0;
+}
+
+static int stop_usbhostd_notify(void)
+{
+ pr_info("host_notifier: stop usbhostd notify\n");
+
+ host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_REMOVE);
+ wake_unlock(&ninfo.wlock);
+
+ return 0;
+}
+
+static void host_notifier_booster(int enable)
+{
+ pr_info("host_notifier: booster %s\n", enable ? "ON" : "OFF");
+
+ ninfo.pdata->booster(enable);
+
+ if (ninfo.pdata->thread_enable) {
+ if (enable)
+ start_usbhostd_thread();
+ else
+ stop_usbhostd_thread();
+ }
+}
+
+static irqreturn_t currentlimit_irq_thread(int irq, void *data)
+{
+ struct host_notifier_info *hostinfo = data;
+ struct host_notify_dev *ndev = &hostinfo->pdata->ndev;
+ int gpio = hostinfo->pdata->gpio;
+ int prev = ndev->booster;
+ int ret = 0;
+
+ ret = gpio_get_value(gpio);
+ pr_info("currentlimit_irq_thread gpio : %d, value : %d\n", gpio, ret);
+
+ if (prev != ret) {
+ pr_info("host_notifier currentlimit_irq_thread: gpio %d = %s\n",
+ gpio, ret ? "HIGH" : "LOW");
+ ndev->booster = ret ?
+ NOTIFY_POWER_ON : NOTIFY_POWER_OFF;
+ prev = ret;
+
+ if (!ret && ndev->mode == NOTIFY_HOST_MODE) {
+ host_state_notify(ndev,
+ NOTIFY_HOST_OVERCURRENT);
+ pr_err("host_notifier currentlimit_irq_thread: overcurrent\n");
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static int currentlimit_irq_init(struct host_notifier_info *hostinfo)
+{
+ int ret = 0;
+
+ hostinfo->currentlimit_irq = gpio_to_irq(hostinfo->pdata->gpio);
+
+ ret = request_threaded_irq(hostinfo->currentlimit_irq, NULL,
+ currentlimit_irq_thread,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "overcurrent_detect", hostinfo);
+ if (ret)
+ pr_info("host_notifier: %s return : %d\n", __func__, ret);
+
+ return ret;
+}
+
+static void currentlimit_irq_work(struct work_struct *work)
+{
+ int retval;
+ struct host_notifier_info *hostinfo = container_of(work,
+ struct host_notifier_info, current_dwork.work);
+
+ retval = currentlimit_irq_init(hostinfo);
+
+ if (retval)
+ pr_err("host_notifier: %s retval : %d\n", __func__, retval);
+ return;
+}
+
+static int host_notifier_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ if (pdev && pdev->dev.platform_data)
+ ninfo.pdata = pdev->dev.platform_data;
+ else {
+ pr_err("host_notifier: platform_data is null.\n");
+ return -ENODEV;
+ }
+
+ dev_info(&pdev->dev, "notifier_prove\n");
+
+ if (ninfo.pdata->thread_enable) {
+ ret = gpio_request(ninfo.pdata->gpio, "host_notifier");
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request %d\n",
+ ninfo.pdata->gpio);
+ return -EPERM;
+ }
+ gpio_direction_input(ninfo.pdata->gpio);
+ dev_info(&pdev->dev, "gpio = %d\n", ninfo.pdata->gpio);
+
+ ninfo.pdata->ndev.set_booster = host_notifier_booster;
+ ninfo.pdata->usbhostd_start = start_usbhostd_thread;
+ ninfo.pdata->usbhostd_stop = stop_usbhostd_thread;
+ } else if (ninfo.pdata->irq_enable) {
+ INIT_DELAYED_WORK(&ninfo.current_dwork, currentlimit_irq_work);
+ schedule_delayed_work(&ninfo.current_dwork,
+ msecs_to_jiffies(10000));
+ ninfo.pdata->ndev.set_booster = host_notifier_booster;
+ ninfo.pdata->usbhostd_start = start_usbhostd_notify;
+ ninfo.pdata->usbhostd_stop = stop_usbhostd_notify;
+ } else {
+ ninfo.pdata->ndev.set_booster = host_notifier_booster;
+ ninfo.pdata->usbhostd_start = start_usbhostd_notify;
+ ninfo.pdata->usbhostd_stop = stop_usbhostd_notify;
+ }
+
+ ret = host_notify_dev_register(&ninfo.pdata->ndev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to host_notify_dev_register\n");
+ return ret;
+ }
+ wake_lock_init(&ninfo.wlock, WAKE_LOCK_SUSPEND, "hostd");
+
+ return 0;
+}
+
+static int host_notifier_remove(struct platform_device *pdev)
+{
+ /* gpio_free(ninfo.pdata->gpio); */
+ host_notify_dev_unregister(&ninfo.pdata->ndev);
+ wake_lock_destroy(&ninfo.wlock);
+ return 0;
+}
+
+static struct platform_driver host_notifier_driver = {
+ .probe = host_notifier_probe,
+ .remove = host_notifier_remove,
+ .driver = {
+ .name = "host_notifier",
+ .owner = THIS_MODULE,
+ },
+};
+
+
+static int __init host_notifier_init(void)
+{
+ return platform_driver_register(&host_notifier_driver);
+}
+
+static void __init host_notifier_exit(void)
+{
+ platform_driver_unregister(&host_notifier_driver);
+}
+
+module_init(host_notifier_init);
+module_exit(host_notifier_exit);
+
+MODULE_AUTHOR("Hyuk Kang <hyuk78.kang@samsung.com>");
+MODULE_DESCRIPTION("USB Host notifier");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/notify/host_notify_class.c b/drivers/usb/notify/host_notify_class.c
new file mode 100644
index 0000000..9250568
--- /dev/null
+++ b/drivers/usb/notify/host_notify_class.c
@@ -0,0 +1,262 @@
+/*
+ * drivers/usb/notify/host_notify_class.c
+ *
+ * Copyright (C) 2011 Samsung, Inc.
+ * Author: Dongrak Shin <dongrak.shin@samsung.com>
+ *
+*/
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/err.h>
+#include <linux/host_notify.h>
+
+struct notify_data {
+ struct class *host_notify_class;
+ atomic_t device_count;
+};
+
+static struct notify_data host_notify;
+
+static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+ char *mode;
+
+ switch (ndev->mode) {
+ case NOTIFY_HOST_MODE:
+ mode = "HOST";
+ break;
+ case NOTIFY_PERIPHERAL_MODE:
+ mode = "PERIPHERAL";
+ break;
+ case NOTIFY_TEST_MODE:
+ mode = "TEST";
+ break;
+ case NOTIFY_NONE_MODE:
+ default:
+ mode = "NONE";
+ break;
+ }
+
+ printk(KERN_INFO "host_notify: read mode %s\n", mode);
+ return sprintf(buf, "%s\n", mode);
+}
+
+static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+
+ char *mode;
+ size_t ret = -ENOMEM;
+
+ mode = kzalloc(size + 1, GFP_KERNEL);
+ if (!mode)
+ goto error;
+
+ sscanf(buf, "%s", mode);
+
+ if (ndev->set_mode) {
+ printk(KERN_INFO "host_notify: set mode %s\n", mode);
+ if (!strcmp(mode, "HOST"))
+ ndev->set_mode(NOTIFY_SET_ON);
+ else if (!strcmp(mode, "NONE"))
+ ndev->set_mode(NOTIFY_SET_OFF);
+ }
+ ret = size;
+ kfree(mode);
+ error:
+ return ret;
+}
+
+static ssize_t booster_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+ char *booster;
+
+ switch (ndev->booster) {
+ case NOTIFY_POWER_ON:
+ booster = "ON";
+ break;
+ case NOTIFY_POWER_OFF:
+ default:
+ booster = "OFF";
+ break;
+ }
+
+ printk(KERN_INFO "host_notify: read booster %s\n", booster);
+ return sprintf(buf, "%s\n", booster);
+}
+
+static ssize_t booster_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+
+ char *booster;
+ size_t ret = -ENOMEM;
+
+ booster = kzalloc(size + 1, GFP_KERNEL);
+ if (!booster)
+ goto error;
+
+ sscanf(buf, "%s", booster);
+
+ if (ndev->set_booster) {
+ printk(KERN_INFO "host_notify: set booster %s\n", booster);
+ if (!strcmp(booster, "ON")) {
+ ndev->mode = NOTIFY_TEST_MODE;
+ ndev->set_booster(NOTIFY_SET_ON);
+ } else if (!strcmp(booster, "OFF")) {
+ ndev->mode = NOTIFY_NONE_MODE;
+ ndev->set_booster(NOTIFY_SET_OFF);
+ }
+ }
+ ret = size;
+ kfree(booster);
+ error:
+ return ret;
+}
+
+static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR | S_IWGRP, mode_show, mode_store);
+static DEVICE_ATTR(booster, S_IRUGO | S_IWUSR | S_IWGRP,
+ booster_show, booster_store);
+
+static struct attribute *host_notify_attrs[] = {
+ &dev_attr_mode.attr,
+ &dev_attr_booster.attr,
+ NULL,
+};
+
+static struct attribute_group host_notify_attr_grp = {
+ .attrs = host_notify_attrs,
+};
+
+void host_state_notify(struct host_notify_dev *ndev, int state)
+{
+ printk(KERN_INFO
+ "host_notify: ndev name=%s: from state=%d -> to state=%d\n",
+ ndev->name, ndev->state, state);
+ if (ndev->state != state) {
+ ndev->state = state;
+ kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE);
+ }
+}
+EXPORT_SYMBOL_GPL(host_state_notify);
+
+static int host_notify_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct host_notify_dev *ndev = (struct host_notify_dev *)
+ dev_get_drvdata(dev);
+ char *state;
+
+ if (!ndev) {
+ /* this happens when the device is first created */
+ return 0;
+ }
+ switch (ndev->state) {
+ case NOTIFY_HOST_ADD:
+ state = "ADD";
+ break;
+ case NOTIFY_HOST_REMOVE:
+ state = "REMOVE";
+ break;
+ case NOTIFY_HOST_OVERCURRENT:
+ state = "OVERCURRENT";
+ break;
+ case NOTIFY_HOST_LOWBATT:
+ state = "LOWBATT";
+ break;
+ case NOTIFY_HOST_UNKNOWN:
+ state = "UNKNOWN";
+ break;
+ case NOTIFY_HOST_NONE:
+ default:
+ return 0;
+ }
+ if (add_uevent_var(env, "DEVNAME=%s", ndev->dev->kobj.name))
+ return -ENOMEM;
+ if (add_uevent_var(env, "STATE=%s", state))
+ return -ENOMEM;
+ return 0;
+}
+
+static int create_notify_class(void)
+{
+ if (!host_notify.host_notify_class) {
+ host_notify.host_notify_class
+ = class_create(THIS_MODULE, "host_notify");
+ if (IS_ERR(host_notify.host_notify_class))
+ return PTR_ERR(host_notify.host_notify_class);
+ atomic_set(&host_notify.device_count, 0);
+ host_notify.host_notify_class->dev_uevent = host_notify_uevent;
+ }
+
+ return 0;
+}
+
+int host_notify_dev_register(struct host_notify_dev *ndev)
+{
+ int ret;
+
+ if (!host_notify.host_notify_class) {
+ ret = create_notify_class();
+ if (ret < 0)
+ return ret;
+ }
+
+ ndev->index = atomic_inc_return(&host_notify.device_count);
+ ndev->dev = device_create(host_notify.host_notify_class, NULL,
+ MKDEV(0, ndev->index), NULL, ndev->name);
+ if (IS_ERR(ndev->dev))
+ return PTR_ERR(ndev->dev);
+
+ ret = sysfs_create_group(&ndev->dev->kobj, &host_notify_attr_grp);
+ if (ret < 0) {
+ device_destroy(host_notify.host_notify_class,
+ MKDEV(0, ndev->index));
+ return ret;
+ }
+
+ dev_set_drvdata(ndev->dev, ndev);
+ ndev->state = 0;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(host_notify_dev_register);
+
+void host_notify_dev_unregister(struct host_notify_dev *ndev)
+{
+ ndev->state = NOTIFY_HOST_NONE;
+ sysfs_remove_group(&ndev->dev->kobj, &host_notify_attr_grp);
+ device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index));
+ dev_set_drvdata(ndev->dev, NULL);
+}
+EXPORT_SYMBOL_GPL(host_notify_dev_unregister);
+
+static int __init notify_class_init(void)
+{
+ return create_notify_class();
+}
+
+static void __exit notify_class_exit(void)
+{
+ class_destroy(host_notify.host_notify_class);
+}
+
+module_init(notify_class_init);
+module_exit(notify_class_exit);
+
+MODULE_AUTHOR("Dongrak Shin <dongrak.shin@samsung.com>");
+MODULE_DESCRIPTION("Usb host notify driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig
index c66481a..cd77719 100644
--- a/drivers/usb/otg/Kconfig
+++ b/drivers/usb/otg/Kconfig
@@ -12,6 +12,14 @@ config USB_OTG_UTILS
Select this to make sure the build includes objects from
the OTG infrastructure directory.
+config USB_OTG_WAKELOCK
+ bool "Hold a wakelock when USB connected"
+ depends on WAKELOCK
+ select USB_OTG_UTILS
+ help
+ Select this to automatically hold a wakelock when USB is
+ connected, preventing suspend.
+
if USB || USB_GADGET
#
diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile
index 566655c..d2c0a7b 100644
--- a/drivers/usb/otg/Makefile
+++ b/drivers/usb/otg/Makefile
@@ -7,6 +7,8 @@ ccflags-$(CONFIG_USB_GADGET_DEBUG) += -DDEBUG
# infrastructure
obj-$(CONFIG_USB_OTG_UTILS) += otg.o
+obj-$(CONFIG_USB_OTG_WAKELOCK) += otg-wakelock.o
+obj-$(CONFIG_USB_OTG_UTILS) += otg_id.o
# transceiver drivers
obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o
diff --git a/drivers/usb/otg/otg-wakelock.c b/drivers/usb/otg/otg-wakelock.c
new file mode 100644
index 0000000..2f11472
--- /dev/null
+++ b/drivers/usb/otg/otg-wakelock.c
@@ -0,0 +1,169 @@
+/*
+ * otg-wakelock.c
+ *
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/device.h>
+#include <linux/notifier.h>
+#include <linux/wakelock.h>
+#include <linux/spinlock.h>
+#include <linux/usb/otg.h>
+
+#define TEMPORARY_HOLD_TIME 2000
+
+static bool enabled = true;
+static struct otg_transceiver *otgwl_xceiv;
+static struct notifier_block otgwl_nb;
+
+/*
+ * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the
+ * held field is updated to match.
+ */
+
+static DEFINE_SPINLOCK(otgwl_spinlock);
+
+/*
+ * Only one lock, but since these 3 fields are associated with each other...
+ */
+
+struct otgwl_lock {
+ char name[40];
+ struct wake_lock wakelock;
+ bool held;
+};
+
+/*
+ * VBUS present lock. Also used as a timed lock on charger
+ * connect/disconnect and USB host disconnect, to allow the system
+ * to react to the change in power.
+ */
+
+static struct otgwl_lock vbus_lock;
+
+static void otgwl_hold(struct otgwl_lock *lock)
+{
+ if (!lock->held) {
+ wake_lock(&lock->wakelock);
+ lock->held = true;
+ }
+}
+
+static void otgwl_temporary_hold(struct otgwl_lock *lock)
+{
+ wake_lock_timeout(&lock->wakelock,
+ msecs_to_jiffies(TEMPORARY_HOLD_TIME));
+ lock->held = false;
+}
+
+static void otgwl_drop(struct otgwl_lock *lock)
+{
+ if (lock->held) {
+ wake_unlock(&lock->wakelock);
+ lock->held = false;
+ }
+}
+
+static void otgwl_handle_event(unsigned long event)
+{
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&otgwl_spinlock, irqflags);
+
+ if (!enabled) {
+ otgwl_drop(&vbus_lock);
+ spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
+ return;
+ }
+
+ switch (event) {
+ case USB_EVENT_VBUS:
+ case USB_EVENT_ENUMERATED:
+ otgwl_hold(&vbus_lock);
+ break;
+
+ case USB_EVENT_NONE:
+ case USB_EVENT_ID:
+ case USB_EVENT_CHARGER:
+ otgwl_temporary_hold(&vbus_lock);
+ break;
+
+ default:
+ break;
+ }
+
+ spin_unlock_irqrestore(&otgwl_spinlock, irqflags);
+}
+
+static int otgwl_otg_notifications(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ otgwl_handle_event(event);
+ return NOTIFY_OK;
+}
+
+static int set_enabled(const char *val, const struct kernel_param *kp)
+{
+ int rv = param_set_bool(val, kp);
+
+ if (rv)
+ return rv;
+
+ if (otgwl_xceiv)
+ otgwl_handle_event(otgwl_xceiv->last_event);
+
+ return 0;
+}
+
+static struct kernel_param_ops enabled_param_ops = {
+ .set = set_enabled,
+ .get = param_get_bool,
+};
+
+module_param_cb(enabled, &enabled_param_ops, &enabled, 0644);
+MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present");
+
+static int __init otg_wakelock_init(void)
+{
+ int ret;
+
+ otgwl_xceiv = otg_get_transceiver();
+
+ if (!otgwl_xceiv) {
+ pr_err("%s: No OTG transceiver found\n", __func__);
+ return -ENODEV;
+ }
+
+ snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s",
+ dev_name(otgwl_xceiv->dev));
+ wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND,
+ vbus_lock.name);
+
+ otgwl_nb.notifier_call = otgwl_otg_notifications;
+ ret = otg_register_notifier(otgwl_xceiv, &otgwl_nb);
+
+ if (ret) {
+ pr_err("%s: otg_register_notifier on transceiver %s"
+ " failed\n", __func__,
+ dev_name(otgwl_xceiv->dev));
+ otgwl_xceiv = NULL;
+ wake_lock_destroy(&vbus_lock.wakelock);
+ return ret;
+ }
+
+ otgwl_handle_event(otgwl_xceiv->last_event);
+ return ret;
+}
+
+late_initcall(otg_wakelock_init);
diff --git a/drivers/usb/otg/otg_id.c b/drivers/usb/otg/otg_id.c
new file mode 100644
index 0000000..8037edb
--- /dev/null
+++ b/drivers/usb/otg/otg_id.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2011 Google, Inc.
+ *
+ * Author:
+ * Colin Cross <ccross@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/mutex.h>
+#include <linux/notifier.h>
+#include <linux/usb/otg_id.h>
+
+static DEFINE_MUTEX(otg_id_lock);
+static struct plist_head otg_id_plist =
+ PLIST_HEAD_INIT(otg_id_plist);
+static struct otg_id_notifier_block *otg_id_active;
+static bool otg_id_cancelling;
+static bool otg_id_inited;
+static int otg_id_suspended;
+static bool otg_id_pending;
+
+static void otg_id_cancel(void)
+{
+ if (otg_id_active) {
+ otg_id_cancelling = true;
+ mutex_unlock(&otg_id_lock);
+
+ otg_id_active->cancel(otg_id_active);
+
+ mutex_lock(&otg_id_lock);
+ otg_id_cancelling = false;
+ }
+}
+
+static void __otg_id_notify(void)
+{
+ int ret;
+ struct otg_id_notifier_block *otg_id_nb;
+ bool proxy_wait = false;
+ if (plist_head_empty(&otg_id_plist))
+ return;
+
+ plist_for_each_entry(otg_id_nb, &otg_id_plist, p) {
+ if (proxy_wait) {
+ if (otg_id_nb->proxy_wait)
+ ret = otg_id_nb->proxy_wait(otg_id_nb);
+ } else {
+ ret = otg_id_nb->detect(otg_id_nb);
+ }
+ if (ret == OTG_ID_HANDLED) {
+ otg_id_active = otg_id_nb;
+ return;
+ }
+ if (ret == OTG_ID_PROXY_WAIT)
+ proxy_wait = true;
+
+ }
+
+ WARN(1, "otg id event not handled");
+ otg_id_active = NULL;
+}
+
+int otg_id_init(void)
+{
+ mutex_lock(&otg_id_lock);
+
+ otg_id_inited = true;
+ __otg_id_notify();
+
+ mutex_unlock(&otg_id_lock);
+ return 0;
+}
+late_initcall(otg_id_init);
+
+/**
+ * otg_id_register_notifier
+ * @otg_id_nb: notifier block containing priority and callback function
+ *
+ * Register a notifier that will be called on any USB cable state change.
+ * The priority determines the order the callback will be called in, a higher
+ * number will be called first. A callback function needs to determine the
+ * type of USB cable that is connected. If it can determine the type, it
+ * should notify the appropriate drivers (for example, call an otg notifier
+ * with USB_EVENT_VBUS), and return OTG_ID_HANDLED. Once a callback has
+ * returned OTG_ID_HANDLED, it is responsible for calling otg_id_notify() when
+ * the detected USB cable is disconnected.
+ */
+int otg_id_register_notifier(struct otg_id_notifier_block *otg_id_nb)
+{
+ plist_node_init(&otg_id_nb->p, otg_id_nb->priority);
+
+ mutex_lock(&otg_id_lock);
+ plist_add(&otg_id_nb->p, &otg_id_plist);
+
+ if (otg_id_inited) {
+ otg_id_cancel();
+ __otg_id_notify();
+ }
+
+ mutex_unlock(&otg_id_lock);
+
+ return 0;
+}
+
+void otg_id_unregister_notifier(struct otg_id_notifier_block *otg_id_nb)
+{
+ mutex_lock(&otg_id_lock);
+
+ plist_del(&otg_id_nb->p, &otg_id_plist);
+
+ if (otg_id_inited && (otg_id_active == otg_id_nb)) {
+ otg_id_cancel();
+ __otg_id_notify();
+ }
+
+ mutex_unlock(&otg_id_lock);
+}
+
+/**
+ * otg_id_notify
+ *
+ * Notify listeners on any USB cable state change.
+ *
+ * A driver may only call otg_id_notify if it returned OTG_ID_HANDLED the last
+ * time it's notifier was called, and it's cancel function has not been called.
+ */
+void otg_id_notify(void)
+{
+ mutex_lock(&otg_id_lock);
+
+ if (otg_id_cancelling)
+ goto out;
+
+ if (otg_id_suspended != 0) {
+ otg_id_pending = true;
+ goto out;
+ }
+
+ __otg_id_notify();
+out:
+ mutex_unlock(&otg_id_lock);
+}
+
+/**
+ * otg_id_suspend
+ *
+ * Mark the otg_id subsystem as going into suspend. From here on out,
+ * any notifications will be deferred until the last otg_id client resumes.
+ * If there is a pending notification when calling this function, it will
+ * return a negative errno and expects that the caller will abort suspend.
+ * Returs 0 on success.
+ */
+int otg_id_suspend(void)
+{
+ int ret = 0;
+
+ mutex_lock(&otg_id_lock);
+
+ /*
+ * if there's a pending notification, tell the caller to abort suspend
+ */
+ if (otg_id_suspended != 0 && otg_id_pending) {
+ pr_info("otg_id: pending notification, should abort suspend\n");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ otg_id_suspended++;
+out:
+ mutex_unlock(&otg_id_lock);
+ return ret;
+}
+
+/**
+ * otg_id_resume
+ *
+ * Inform the otg_id subsystem that a client is resuming. If this is the
+ * last client to be resumed and there's a pending notification,
+ * otg_id_notify() is called.
+ */
+void otg_id_resume(void)
+{
+ mutex_lock(&otg_id_lock);
+ if (WARN(!otg_id_suspended, "unbalanced otg_id_resume\n"))
+ goto out;
+ if (--otg_id_suspended == 0) {
+ if (otg_id_pending) {
+ pr_info("otg_id: had pending notification\n");
+ otg_id_pending = false;
+ __otg_id_notify();
+ }
+ }
+out:
+ mutex_unlock(&otg_id_lock);
+}
diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c
index b9bb247..3508152 100644
--- a/drivers/usb/serial/qcserial.c
+++ b/drivers/usb/serial/qcserial.c
@@ -86,10 +86,14 @@ static const struct usb_device_id id_table[] = {
{USB_DEVICE(0x05c6, 0x9204)}, /* Gobi 2000 QDL device */
{USB_DEVICE(0x05c6, 0x9205)}, /* Gobi 2000 Modem device */
{USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */
+ {USB_DEVICE(0x05c6, 0x9048)}, /* MDM9x15 device */
+ {USB_DEVICE(0x05c6, 0x904C)}, /* MDM9x15 device */
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, id_table);
+#define EFS_SYNC_IFC_NUM 2
+
static struct usb_driver qcdriver = {
.name = "qcserial",
.probe = usb_serial_probe,
@@ -201,6 +205,14 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
}
break;
+ case 9:
+ if (ifnum != EFS_SYNC_IFC_NUM) {
+ kfree(data);
+ break;
+ }
+
+ retval = 0;
+ break;
default:
dev_err(&serial->dev->dev,
"unknown number of interfaces: %d\n", nintf);
diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h
index c47b6ec..de8d490 100644
--- a/drivers/usb/serial/usb-wwan.h
+++ b/drivers/usb/serial/usb-wwan.h
@@ -31,10 +31,10 @@ extern int usb_wwan_resume(struct usb_serial *serial);
/* per port private data */
-#define N_IN_URB 4
-#define N_OUT_URB 4
-#define IN_BUFLEN 4096
-#define OUT_BUFLEN 4096
+#define N_IN_URB 5
+#define N_OUT_URB 5
+#define IN_BUFLEN 65536
+#define OUT_BUFLEN 65536
struct usb_wwan_intf_private {
spinlock_t susp_lock;
diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c
index e4fad5e..2a9d21d 100644
--- a/drivers/usb/serial/usb_wwan.c
+++ b/drivers/usb/serial/usb_wwan.c
@@ -406,6 +406,11 @@ int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
portdata = usb_get_serial_port_data(port);
intfdata = serial->private;
+ /* explicitly set the driver mode to raw */
+ tty->raw = 1;
+ tty->real_raw = 1;
+
+ set_bit(TTY_NO_WRITE_SPLIT, &tty->flags);
dbg("%s", __func__);
/* Start reading from the IN endpoint */
@@ -548,7 +553,7 @@ int usb_wwan_startup(struct usb_serial *serial)
init_usb_anchor(&portdata->delayed);
for (j = 0; j < N_IN_URB; j++) {
- buffer = (u8 *) __get_free_page(GFP_KERNEL);
+ buffer = kmalloc(IN_BUFLEN, GFP_KERNEL);
if (!buffer)
goto bail_out_error;
portdata->in_buffer[j] = buffer;
diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c
index c325e69..9d6cf49 100644
--- a/drivers/usb/storage/usb.c
+++ b/drivers/usb/storage/usb.c
@@ -907,6 +907,9 @@ int usb_stor_probe1(struct us_data **pus,
/*
* Allow 16-byte CDBs and thus > 2TB
*/
+#ifdef CONFIG_USB_HOST_NOTIFY
+ host->by_usb = 1;
+#endif
host->max_cmd_len = 16;
host->sg_tablesize = usb_stor_sg_tablesize(intf);
*pus = us = host_to_us(host);