aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/Kconfig4
-rw-r--r--drivers/usb/Makefile4
-rw-r--r--drivers/usb/core/driver.c7
-rw-r--r--drivers/usb/core/hcd.c4
-rw-r--r--drivers/usb/dwc/Kconfig84
-rw-r--r--drivers/usb/dwc/Makefile17
-rw-r--r--drivers/usb/dwc/apmppc.c372
-rw-r--r--drivers/usb/dwc/cil.c893
-rw-r--r--drivers/usb/dwc/cil.h1179
-rw-r--r--drivers/usb/dwc/cil_intr.c616
-rw-r--r--drivers/usb/dwc/driver.h76
-rw-r--r--drivers/usb/dwc/hcd.c2473
-rw-r--r--drivers/usb/dwc/hcd.h416
-rw-r--r--drivers/usb/dwc/hcd_intr.c1477
-rw-r--r--drivers/usb/dwc/hcd_queue.c696
-rw-r--r--drivers/usb/dwc/param.c180
-rw-r--r--drivers/usb/dwc/pcd.c1791
-rw-r--r--drivers/usb/dwc/pcd.h139
-rw-r--r--drivers/usb/dwc/pcd_intr.c2312
-rw-r--r--drivers/usb/dwc/regs.h1326
-rw-r--r--drivers/usb/gadget/Kconfig66
-rwxr-xr-x[-rw-r--r--]drivers/usb/gadget/Makefile3
-rw-r--r--drivers/usb/gadget/android.c1349
-rw-r--r--drivers/usb/gadget/composite.c62
-rw-r--r--drivers/usb/gadget/epautoconf.c54
-rw-r--r--drivers/usb/gadget/f_accessory.c1176
-rw-r--r--drivers/usb/gadget/f_acm.c21
-rw-r--r--drivers/usb/gadget/f_adb.c614
-rw-r--r--drivers/usb/gadget/f_audio_source.c821
-rw-r--r--drivers/usb/gadget/f_mass_storage.c4
-rw-r--r--drivers/usb/gadget/f_mtp.c1267
-rw-r--r--drivers/usb/gadget/f_rndis.c46
-rw-r--r--drivers/usb/gadget/gadget_chips.h8
-rw-r--r--drivers/usb/gadget/rndis.c11
-rw-r--r--drivers/usb/gadget/s3c_udc.h164
-rw-r--r--drivers/usb/gadget/s3c_udc_otg.c1544
-rw-r--r--drivers/usb/gadget/s3c_udc_otg_xfer_dma.c1387
-rw-r--r--drivers/usb/gadget/storage_common.c6
-rw-r--r--drivers/usb/gadget/u_ether.c48
-rw-r--r--drivers/usb/gadget/u_ether.h9
-rw-r--r--drivers/usb/gadget/u_serial.c6
-rw-r--r--drivers/usb/host/Kconfig16
-rw-r--r--drivers/usb/host/Makefile1
-rw-r--r--drivers/usb/host/ehci-q.c18
-rw-r--r--drivers/usb/host/ehci.h17
-rw-r--r--drivers/usb/host/s3c-otg/Makefile18
-rw-r--r--drivers/usb/host/s3c-otg/debug-mem.c55
-rw-r--r--drivers/usb/host/s3c-otg/debug-mem.h9
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-common-common.h49
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-common-const.h167
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h811
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h115
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h302
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h88
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c386
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.h75
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c708
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h152
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h420
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h217
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h191
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-isr.c294
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-isr.h72
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-oci.c815
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-oci.h86
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-roothub.c605
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-roothub.h65
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c369
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-scheduler-readyq.c264
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c424
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h100
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c810
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c140
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c128
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h144
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c720
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h88
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h66
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c238
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h79
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c698
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h99
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c574
-rw-r--r--drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h97
-rwxr-xr-xdrivers/usb/notify/Kconfig11
-rwxr-xr-xdrivers/usb/notify/Makefile4
-rwxr-xr-xdrivers/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
91 files changed, 34125 insertions, 58 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 48f1781..e9660f1 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -120,6 +120,8 @@ source "drivers/usb/musb/Kconfig"
source "drivers/usb/renesas_usbhs/Kconfig"
+source "drivers/usb/dwc/Kconfig"
+
source "drivers/usb/class/Kconfig"
source "drivers/usb/storage/Kconfig"
@@ -168,4 +170,6 @@ source "drivers/usb/gadget/Kconfig"
source "drivers/usb/otg/Kconfig"
+source "drivers/usb/notify/Kconfig"
+
endif # USB_SUPPORT
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 30ddf8d..5adcecf 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_USB) += core/
obj-$(CONFIG_USB_MON) += mon/
obj-$(CONFIG_PCI) += host/
+obj-$(CONFIG_USB_S3C_OTG_HOST) += host/
obj-$(CONFIG_USB_EHCI_HCD) += host/
obj-$(CONFIG_USB_ISP116X_HCD) += host/
obj-$(CONFIG_USB_OHCI_HCD) += host/
@@ -50,4 +51,7 @@ obj-$(CONFIG_USB_SPEEDTOUCH) += atm/
obj-$(CONFIG_USB_MUSB_HDRC) += musb/
obj-$(CONFIG_USB_RENESAS_USBHS) += renesas_usbhs/
obj-$(CONFIG_USB_OTG_UTILS) += otg/
+obj-$(CONFIG_USB_DWC_OTG) += dwc/
obj-$(CONFIG_USB_GADGET) += gadget/
+
+obj-$(CONFIG_USB_HOST_NOTIFY) += notify/
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index 75b4bc0..962f2b5 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1617,6 +1617,13 @@ static int autosuspend_check(struct usb_device *udev)
/* Fail if autosuspend is disabled, or any interfaces are in use, or
* any interface drivers require remote wakeup but it isn't available.
*/
+
+#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG
+ /* temporarily disabled autosuspend for otg host */
+ if (udev->bus->busnum == 2)
+ return -EOPNOTSUPP;
+#endif
+
w = 0;
if (udev->actconfig) {
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 45e0908..67a9d82 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -156,7 +156,11 @@ static const u8 usb2_rh_dev_descriptor [18] = {
0x09, /* __u8 bDeviceClass; HUB_CLASSCODE */
0x00, /* __u8 bDeviceSubClass; */
+#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG
+ 0x01, /* __u8 bDeviceProtocol; [ usb 2.0 single TT ] */
+#else
0x00, /* __u8 bDeviceProtocol; [ usb 2.0 no TT ] */
+#endif
0x40, /* __u8 bMaxPacketSize0; 64 Bytes */
0x6b, 0x1d, /* __le16 idVendor; Linux Foundation */
diff --git a/drivers/usb/dwc/Kconfig b/drivers/usb/dwc/Kconfig
new file mode 100644
index 0000000..4439810
--- /dev/null
+++ b/drivers/usb/dwc/Kconfig
@@ -0,0 +1,84 @@
+#
+# USB Dual Role (OTG-ready) Controller Drivers
+# for silicon based on Synopsys DesignWare IP
+#
+
+comment "Enable Host or Gadget support for DesignWare OTG controller"
+ depends on !USB && USB_GADGET=n
+
+config USB_DWC_OTG
+ tristate "Synopsys DWC OTG Controller"
+ depends on USB || USB_GADGET
+ select NOP_USB_XCEIV
+ select USB_OTG_UTILS
+ default USB_GADGET
+ help
+ This driver provides USB Device Controller support for the
+ Synopsys DesignWare USB OTG Core used on the AppliedMicro PowerPC SoC.
+
+config DWC_DEBUG
+ bool "Enable DWC Debugging"
+ depends on USB_DWC_OTG
+ default n
+ help
+ Enable DWC driver debugging
+
+choice
+ prompt "DWC Mode Selection"
+ depends on USB_DWC_OTG
+ default DWC_HOST_ONLY
+ help
+ Select the DWC Core in OTG, Host only, or Device only mode.
+
+config DWC_HOST_ONLY
+ bool "DWC Host Only Mode"
+
+config DWC_OTG_MODE
+ bool "DWC OTG Mode"
+ select USB_GADGET_SELECTED
+
+config DWC_DEVICE_ONLY
+ bool "DWC Device Only Mode"
+ select USB_GADGET_SELECTED
+
+endchoice
+
+# enable peripheral support (including with OTG)
+choice
+ prompt "DWC DMA/SlaveMode Selection"
+ depends on USB_DWC_OTG
+ default DWC_DMA_MODE
+ help
+ Select the DWC DMA or Slave Mode.
+ DMA mode uses the DWC core internal DMA engines.
+ Slave mode uses the processor PIO to tranfer data.
+ In Slave mode, processor's DMA channels can be used if available.
+
+config DWC_SLAVE
+ bool "DWC Slave Mode"
+
+config DWC_DMA_MODE
+ bool "DWC DMA Mode"
+
+endchoice
+
+config DWC_OTG_REG_LE
+ bool "DWC Little Endian Register"
+ depends on USB_DWC_OTG
+ default y
+ help
+ OTG core register access is Little-Endian.
+
+config DWC_OTG_FIFO_LE
+ bool "DWC FIFO Little Endian"
+ depends on USB_DWC_OTG
+ default y
+ help
+ OTG core FIFO access is Little-Endian.
+
+config DWC_LIMITED_XFER_SIZE
+ bool "DWC Endpoint Limited Xfer Size"
+ depends on USB_GADGET_DWC_HDRC
+ default n
+ help
+ Bit fields in the Device EP Transfer Size Register is 11 bits.
diff --git a/drivers/usb/dwc/Makefile b/drivers/usb/dwc/Makefile
new file mode 100644
index 0000000..c79d6e6
--- /dev/null
+++ b/drivers/usb/dwc/Makefile
@@ -0,0 +1,17 @@
+#
+# OTG infrastructure and transceiver drivers
+#
+obj-$(CONFIG_USB_DWC_OTG) += dwc.o
+
+dwc-objs := cil.o cil_intr.o param.o
+
+dwc-objs += apmppc.o
+
+ifneq ($(CONFIG_DWC_DEVICE_ONLY),y)
+dwc-objs += hcd.o hcd_intr.o \
+ hcd_queue.o
+endif
+
+ifneq ($(CONFIG_DWC_HOST_ONLY),y)
+dwc-objs += pcd.o pcd_intr.o
+endif
diff --git a/drivers/usb/dwc/apmppc.c b/drivers/usb/dwc/apmppc.c
new file mode 100644
index 0000000..81881d0
--- /dev/null
+++ b/drivers/usb/dwc/apmppc.c
@@ -0,0 +1,372 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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 dwc_otg module provides the initialization and cleanup entry
+ * points for the dwcotg driver. This module will be dynamically installed
+ * after Linux is booted using the insmod command. When the module is
+ * installed, the dwc_otg_driver_init function is called. When the module is
+ * removed (using rmmod), the dwc_otg_driver_cleanup function is called.
+ *
+ * This module also defines a data structure for the dwc_otg driver, which is
+ * used in conjunction with the standard device structure. These
+ * structures allow the OTG driver to comply with the standard Linux driver
+ * model in which devices and drivers are registered with a bus driver. This
+ * has the benefit that Linux can expose attributes of the driver and device
+ * in its special sysfs file system. Users can then read or write files in
+ * this file system to perform diagnostics on the driver components or the
+ * device.
+ */
+
+#include <linux/of_platform.h>
+#include <mach/map.h>
+#include <linux/platform_device.h>
+#include "driver.h"
+#include <linux/delay.h>
+
+#define DWC_DRIVER_VERSION "1.05"
+#define DWC_DRIVER_DESC "HS OTG USB Controller driver"
+static const char dwc_driver_name[] = "dwc_otg";
+
+static irqreturn_t dwc_otg_common_irq(int _irq, void *dev)
+{
+ struct dwc_otg_device *dwc_dev = dev;
+ int retval;
+ struct dwc_hcd *dwc_hcd;
+
+ dwc_hcd = dwc_dev->hcd;
+ spin_lock(&dwc_hcd->lock);
+ retval = dwc_otg_handle_common_intr(dwc_dev->core_if);
+ spin_unlock(&dwc_hcd->lock);
+ return IRQ_RETVAL(retval);
+}
+
+static irqreturn_t dwc_otg_externalchgpump_irq(int _irq, void *dev)
+{
+ struct dwc_otg_device *dwc_dev = dev;
+
+ if (dwc_otg_is_host_mode(dwc_dev->core_if)) {
+ struct dwc_hcd *dwc_hcd;
+ u32 hprt0 = 0;
+
+ dwc_hcd = dwc_dev->hcd;
+ spin_lock(&dwc_hcd->lock);
+ dwc_hcd->flags.b.port_over_current_change = 1;
+
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(dwc_dev->core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock(&dwc_hcd->lock);
+ } else {
+ /* Device mode - This int is n/a for device mode */
+ dev_dbg(dev, "DeviceMode: OTG OverCurrent Detected\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int __devexit dwc_otg_driver_remove(struct platform_device *ofdev)
+{
+ struct device *dev = &ofdev->dev;
+ struct dwc_otg_device *dwc_dev = dev_get_drvdata(dev);
+
+ /* Memory allocation for dwc_otg_device may have failed. */
+ if (!dwc_dev)
+ return 0;
+
+ /* Free the IRQ */
+ free_irq(dwc_dev->irq, dwc_dev);
+ /* Free external charge pump irq */
+ free_irq(dwc_dev->hcd->cp_irq, dwc_dev);
+
+ if (dwc_dev->hcd)
+ dwc_otg_hcd_remove(dev);
+
+ if (dwc_dev->pcd)
+ dwc_otg_pcd_remove(dev);
+
+ if (dwc_dev->core_if)
+ dwc_otg_cil_remove(dwc_dev->core_if);
+
+ /* Return the memory. */
+ if (dwc_dev->base)
+ iounmap(dwc_dev->base);
+
+ if (dwc_dev->phys_addr)
+ release_mem_region(dwc_dev->phys_addr, dwc_dev->base_len);
+
+ otg_put_transceiver(dwc_dev->core_if->xceiv);
+ dwc_dev->core_if->xceiv = NULL;
+
+ kfree(dwc_dev);
+
+ /* Clear the drvdata pointer. */
+ dev_set_drvdata(dev, NULL);
+ return 0;
+}
+
+static int dwc_otg_driver_probe(struct platform_device *ofdev)
+{
+ int retval,reg_val;
+ struct dwc_otg_device *dwc_dev;
+ struct device *dev = &ofdev->dev;
+ struct resource res;
+ ulong gusbcfg_addr;
+ u32 usbcfg = 0;
+
+ dwc_dev = kzalloc(sizeof(*dwc_dev), GFP_KERNEL);
+ if (!dwc_dev) {
+ dev_err(dev, "kmalloc of dwc_otg_device failed\n");
+ retval = -ENOMEM;
+ goto fail_dwc_dev;
+ }
+
+ /* Retrieve the memory and IRQ resources. */
+ dwc_dev->irq = ofdev->resource[1].start;
+ if (dwc_dev->irq == NO_IRQ) {
+ dev_err(dev, "no device irq\n");
+ retval = -ENODEV;
+ goto fail_of_irq;
+ }
+
+ res = ofdev->resource[0];
+/* if (of_address_to_resource(ofdev->dev.of_node, 0, &res)) {
+ dev_err(dev, "%s: Can't get USB-OTG register address\n",
+ __func__);
+ retval = -ENOMEM;
+ goto fail_of_irq;
+ }*/
+
+ dwc_dev->phys_addr = res.start;
+ dwc_dev->base_len = res.end - res.start + 1;
+ if (!request_mem_region(dwc_dev->phys_addr,
+ dwc_dev->base_len, dwc_driver_name)) {
+ dev_err(dev, "request_mem_region failed\n");
+ retval = -EBUSY;
+ goto fail_of_irq;
+ }
+
+ /* Map the DWC_otg Core memory into virtual address space. */
+ dwc_dev->base = ioremap(dwc_dev->phys_addr, dwc_dev->base_len);
+ if (!dwc_dev->base) {
+ dev_err(dev, "ioremap() failed\n");
+ retval = -ENOMEM;
+ goto fail_ioremap;
+ }
+ dev_dbg(dev, "mapped base=0x%08x\n", (__force u32)dwc_dev->base);
+
+ /**
+ * Attempt to ensure this device is really a Synopsys 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 = readl(dwc_dev->base+0x40);
+ if ((reg_val & 0xFFFFF000) != 0x4F542000) {
+ dev_err(dev,"Bad value for SNPSID: 0x%x\n", reg_val);
+ retval = -EINVAL;
+ goto fail_invalid_device;
+ }
+
+ /*
+ * Initialize driver data to point to the global DWC_otg
+ * Device structure.
+ */
+ dev_set_drvdata(dev, dwc_dev);
+
+ dwc_dev->core_if =
+ dwc_otg_cil_init(dwc_dev->base, &dwc_otg_module_params);
+ if (!dwc_dev->core_if) {
+ dev_err(dev, "CIL initialization failed!\n");
+ retval = -ENOMEM;
+ goto fail_cil_init;
+ }
+
+ /*
+ * Validate parameter values after dwc_otg_cil_init.
+ */
+ if (check_parameters(dwc_dev->core_if)) {
+ retval = -EINVAL;
+ goto fail_check_param;
+ }
+
+ usb_nop_xceiv_register();
+ dwc_dev->core_if->xceiv = otg_get_transceiver();
+ if (!dwc_dev->core_if->xceiv) {
+ retval = -ENODEV;
+ goto fail_xceiv;
+ }
+ dwc_set_feature(dwc_dev->core_if);
+
+ /* Initialize the DWC_otg core. */
+ dwc_otg_core_init(dwc_dev->core_if);
+
+ /*
+ * Disable the global interrupt until all the interrupt
+ * handlers are installed.
+ */
+ spin_lock(&dwc_dev->hcd->lock);
+ dwc_otg_disable_global_interrupts(dwc_dev->core_if);
+ spin_unlock(&dwc_dev->hcd->lock);
+
+ /*
+ * Install the interrupt handler for the common interrupts before
+ * enabling common interrupts in core_init below.
+ */
+ retval = request_irq(dwc_dev->irq, dwc_otg_common_irq,
+ IRQF_SHARED, "dwc_otg", dwc_dev);
+ if (retval) {
+ dev_err(dev, "request of irq%d failed retval: %d\n",
+ dwc_dev->irq, retval);
+ retval = -EBUSY;
+ goto fail_req_irq;
+ } else {
+ dwc_dev->common_irq_installed = 1;
+ }
+
+ if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) {
+ /* Initialize the PCD */
+ retval = dwc_otg_pcd_init(dev);
+ if (retval) {
+ dev_err(dev, "dwc_otg_pcd_init failed\n");
+ dwc_dev->pcd = NULL;
+ goto fail_req_irq;
+ }
+ }
+
+ gusbcfg_addr = (ulong) (dwc_dev->core_if->core_global_regs)
+ + DWC_GUSBCFG;
+ if (!dwc_has_feature(dwc_dev->core_if, DWC_DEVICE_ONLY)) {
+ /* Initialize the HCD and force_host_mode */
+ usbcfg = dwc_reg_read(gusbcfg_addr, 0);
+ usbcfg |= DWC_USBCFG_FRC_HST_MODE;
+ dwc_reg_write(gusbcfg_addr, 0, usbcfg);
+
+ retval = dwc_otg_hcd_init(dev, dwc_dev);
+ if (retval) {
+ dev_err(dev, "dwc_otg_hcd_init failed\n");
+ dwc_dev->hcd = NULL;
+ goto fail_hcd;
+ }
+ /* configure chargepump interrupt */
+ dwc_dev->hcd->cp_irq = 0;//irq_of_parse_and_map(ofdev->dev.of_node, 3);
+ if (dwc_dev->hcd->cp_irq) {
+ retval = request_irq(dwc_dev->hcd->cp_irq,
+ dwc_otg_externalchgpump_irq,
+ IRQF_SHARED,
+ "dwc_otg_ext_chg_pump", dwc_dev);
+ if (retval) {
+ dev_err(dev,
+ "request of irq failed retval: %d\n",
+ retval);
+ retval = -EBUSY;
+ goto fail_hcd;
+ } else {
+ dev_dbg(dev, "%s: ExtChgPump Detection "
+ "IRQ registered\n", dwc_driver_name);
+ }
+ }
+ }
+ /*
+ * Enable the global interrupt after all the interrupt
+ * handlers are installed.
+ */
+ dwc_otg_enable_global_interrupts(dwc_dev->core_if);
+
+ usbcfg = dwc_reg_read(gusbcfg_addr, 0);
+ usbcfg &= ~DWC_USBCFG_FRC_HST_MODE;
+ dwc_reg_write(gusbcfg_addr, 0, usbcfg);
+
+ printk("Done\n");msleep(100);
+
+ return 0;
+fail_hcd:
+ free_irq(dwc_dev->irq, dwc_dev);
+ if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) {
+ if (dwc_dev->pcd)
+ dwc_otg_pcd_remove(dev);
+ }
+fail_req_irq:
+ otg_put_transceiver(dwc_dev->core_if->xceiv);
+fail_xceiv:
+ usb_nop_xceiv_unregister();
+fail_check_param:
+ dwc_otg_cil_remove(dwc_dev->core_if);
+fail_cil_init:
+ dev_set_drvdata(dev, NULL);
+fail_invalid_device:
+ iounmap(dwc_dev->base);
+fail_ioremap:
+ release_mem_region(dwc_dev->phys_addr, dwc_dev->base_len);
+fail_of_irq:
+ kfree(dwc_dev);
+fail_dwc_dev:
+ return retval;
+}
+
+/* static const struct of_device_id dwc_otg_match[] = {
+ {.compatible = "amcc,dwc-otg",},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, dwc_otg_match); */
+
+struct platform_driver dwc_otg_driver = {
+ .probe = dwc_otg_driver_probe,
+ .remove = __devexit_p(dwc_otg_driver_remove),
+ .driver = {
+ .name = "dwc_otg",
+ .owner = THIS_MODULE,
+// .of_match_table = dwc_otg_match,
+ },
+};
+
+/*static int dwc_otg_driver_init(void)
+{
+
+ return platform_driver_register(&dwc_otg_driver);
+}
+
+module_init(dwc_otg_driver_init);
+
+static void __exit dwc_otg_driver_cleanup(void)
+{
+ platform_driver_unregister(&dwc_otg_driver);
+}
+
+module_exit(dwc_otg_driver_cleanup);*/
+
+MODULE_DESCRIPTION(DWC_DRIVER_DESC);
+MODULE_AUTHOR("Mark Miesfeld <mmiesfeld@apm.com");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/dwc/cil.c b/drivers/usb/dwc/cil.c
new file mode 100644
index 0000000..8d7a9e8
--- /dev/null
+++ b/drivers/usb/dwc/cil.c
@@ -0,0 +1,893 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#include <linux/delay.h>
+
+#include "cil.h"
+
+void dwc_otg_enable_global_interrupts(struct core_if *core_if)
+{
+ u32 ahbcfg = 0;
+
+ ahbcfg |= DWC_AHBCFG_GLBL_INT_MASK;
+ dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, 0,
+ ahbcfg);
+}
+
+void dwc_otg_disable_global_interrupts(struct core_if *core_if)
+{
+ u32 ahbcfg = 0;
+
+ ahbcfg |= DWC_AHBCFG_GLBL_INT_MASK;
+ dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG,
+ ahbcfg, 0);
+}
+
+/**
+ * Tests if the current hardware is using a full speed phy.
+ */
+static inline int full_speed_phy(struct core_if *core_if)
+{
+ if ((DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 &&
+ DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 1 &&
+ core_if->core_params->ulpi_fs_ls) ||
+ core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)
+ return 1;
+ return 0;
+}
+
+/**
+ * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY
+ * type.
+ */
+void init_fslspclksel(struct core_if *core_if)
+{
+ u32 val;
+ u32 hcfg = 0;
+
+ if (full_speed_phy(core_if))
+ val = DWC_HCFG_48_MHZ;
+ else
+ /* High speed PHY running at full speed or high speed */
+ val = DWC_HCFG_30_60_MHZ;
+
+ hcfg = dwc_reg_read(core_if->host_if->host_global_regs, DWC_HCFG);
+ hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, val);
+ dwc_reg_write(core_if->host_if->host_global_regs, DWC_HCFG, hcfg);
+}
+
+/**
+ * Initializes the DevSpd field of the DCFG register depending on the PHY type
+ * and the enumeration speed of the device.
+ */
+static void init_devspd(struct core_if *core_if)
+{
+ u32 val;
+ u32 dcfg;
+
+ if (full_speed_phy(core_if))
+ val = 0x3;
+ else if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL)
+ /* High speed PHY running at full speed */
+ val = 0x1;
+ else
+ /* High speed PHY running at high speed */
+ val = 0x0;
+
+ dcfg = dwc_reg_read(core_if->dev_if->dev_global_regs, DWC_DCFG);
+ dcfg = DWC_DCFG_DEV_SPEED_WR(dcfg, val);
+ dwc_reg_write(core_if->dev_if->dev_global_regs, DWC_DCFG, dcfg);
+}
+
+/**
+ * This function calculates the number of IN EPS using GHWCFG1 and GHWCFG2
+ * registers values
+ */
+static u32 calc_num_in_eps(struct core_if *core_if)
+{
+ u32 num_in_eps = 0;
+ u32 num_eps = DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2);
+ u32 hwcfg1 = core_if->hwcfg1 >> 2;
+ u32 num_tx_fifos = DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4);
+ u32 i;
+
+ for (i = 0; i < num_eps; ++i) {
+ if (!(hwcfg1 & 0x1))
+ num_in_eps++;
+ hwcfg1 >>= 2;
+ }
+
+ if (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4))
+ num_in_eps = num_in_eps > num_tx_fifos ?
+ num_tx_fifos : num_in_eps;
+
+ return num_in_eps;
+}
+
+/**
+ * This function calculates the number of OUT EPS using GHWCFG1 and GHWCFG2
+ * registers values
+ */
+static u32 calc_num_out_eps(struct core_if *core_if)
+{
+ u32 num_out_eps = 0;
+ u32 num_eps = DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2);
+ u32 hwcfg1 = core_if->hwcfg1 >> 2;
+ u32 i;
+
+ for (i = 0; i < num_eps; ++i) {
+ if (!(hwcfg1 & 0x2))
+ num_out_eps++;
+ hwcfg1 >>= 2;
+ }
+ return num_out_eps;
+}
+
+/**
+ * Do core a soft reset of the core. Be careful with this because it
+ * resets all the internal state machines of the core.
+ */
+extern void otg_host_phy_init(void);
+static void dwc_otg_core_reset(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 greset = 0;
+ int count = 0;
+
+ otg_host_phy_init();
+
+ /* Wait for AHB master IDLE state. */
+ do {
+ udelay(10);
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 100000) {
+ pr_warning("%s() HANG! AHB Idle GRSTCTL=%0x\n",
+ __func__, greset);
+ return;
+ }
+ } while (!(greset & DWC_RSTCTL_AHB_IDLE));
+
+ /* Core Soft Reset */
+ count = 0;
+ greset |= DWC_RSTCTL_SFT_RST;
+ dwc_reg_write(global_regs, DWC_GRSTCTL, greset);
+
+ do {
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 10000) {
+ pr_warning("%s() HANG! Soft Reset "
+ "GRSTCTL=%0x\n", __func__, greset);
+ break;
+ }
+ udelay(1);
+ } while (greset & DWC_RSTCTL_SFT_RST);
+
+ /* Wait for 3 PHY Clocks */
+ msleep(100);
+}
+
+/**
+ * This function initializes the commmon interrupts, used in both
+ * device and host modes.
+ */
+void dwc_otg_enable_common_interrupts(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /* Clear any pending OTG Interrupts */
+ dwc_reg_write(global_regs, DWC_GOTGINT, 0xFFFFFFFF);
+
+ /* Clear any pending interrupts */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the interrupts in the GINTMSK. */
+ intr_mask |= DWC_INTMSK_MODE_MISMTC;
+ intr_mask |= DWC_INTMSK_OTG;
+ intr_mask |= DWC_INTMSK_CON_ID_STS_CHG;
+ intr_mask |= DWC_INTMSK_WKP;
+ intr_mask |= DWC_INTMSK_SES_DISCON_DET;
+ intr_mask |= DWC_INTMSK_USB_SUSP;
+ intr_mask |= DWC_INTMSK_NEW_SES_DET;
+ if (!core_if->dma_enable)
+ intr_mask |= DWC_INTMSK_RXFIFO_NOT_EMPT;
+ dwc_reg_write(global_regs, DWC_GINTMSK, intr_mask);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers and prepares the
+ * core for device mode or host mode operation.
+ */
+void dwc_otg_core_init(struct core_if *core_if)
+{
+ u32 i;
+ ulong global_reg = core_if->core_global_regs;
+ struct device_if *dev_if = core_if->dev_if;
+ u32 ahbcfg = 0;
+ u32 i2cctl = 0;
+ u32 gusbcfg;
+
+ /* Common Initialization */
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+
+ /* Program the ULPI External VBUS bit if needed */
+ gusbcfg |= DWC_USBCFG_ULPI_EXT_VBUS_DRV;
+
+ /* Set external TS Dline pulsing */
+ if (core_if->core_params->ts_dline == 1)
+ gusbcfg |= DWC_USBCFG_TERM_SEL_DL_PULSE;
+ else
+ gusbcfg = gusbcfg & (~((u32) DWC_USBCFG_TERM_SEL_DL_PULSE));
+
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Reset the Controller */
+ dwc_otg_core_reset(core_if);
+
+ /* Initialize parameters from Hardware configuration registers. */
+ dev_if->num_in_eps = calc_num_in_eps(core_if);
+ dev_if->num_out_eps = calc_num_out_eps(core_if);
+
+ for (i = 0; i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ i++) {
+ dev_if->perio_tx_fifo_size[i] =
+ dwc_reg_read(global_reg, DWC_DPTX_FSIZ_DIPTXF(i)) >> 16;
+ }
+ for (i = 0; i < DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); i++) {
+ dev_if->tx_fifo_size[i] =
+ dwc_reg_read(global_reg, DWC_DPTX_FSIZ_DIPTXF(i)) >> 16;
+ }
+
+ core_if->total_fifo_size = DWC_HWCFG3_DFIFO_DEPTH_RD(core_if->hwcfg3);
+ core_if->rx_fifo_size = dwc_reg_read(global_reg, DWC_GRXFSIZ);
+ core_if->nperio_tx_fifo_size =
+ dwc_reg_read(global_reg, DWC_GRXFSIZ) >> 16;
+ /*
+ * This programming sequence needs to happen in FS mode before any
+ * other programming occurs
+ */
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL &&
+ core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) {
+ /*
+ * core_init() is now called on every switch so only call the
+ * following for the first time through.
+ */
+ if (!core_if->phy_init_done) {
+ core_if->phy_init_done = 1;
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg |= DWC_USBCFG_ULPI_UTMI_SEL;
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Reset after a PHY select */
+ dwc_otg_core_reset(core_if);
+ }
+
+ /*
+ * Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS.
+ * Also do this on HNP Dev/Host mode switches (done in dev_init
+ * and host_init).
+ */
+ if (dwc_otg_is_host_mode(core_if))
+ init_fslspclksel(core_if);
+ else
+ init_devspd(core_if);
+
+ if (core_if->core_params->i2c_enable) {
+ /* Program GUSBCFG.OtgUtmifsSel to I2C */
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg |= DWC_USBCFG_OTGUTMIFSSEL;
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Program GI2CCTL.I2CEn */
+ i2cctl = dwc_reg_read(global_reg, DWC_GI2CCTL);
+ i2cctl |= DWC_I2CCTL_I2CDEVADDR(1);
+ i2cctl &= ~DWC_I2CCTL_I2CEN;
+ dwc_reg_write(global_reg, DWC_GI2CCTL, i2cctl);
+ i2cctl |= DWC_I2CCTL_I2CEN;
+ dwc_reg_write(global_reg, DWC_GI2CCTL, i2cctl);
+ }
+ } else if (!core_if->phy_init_done) {
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ core_if->phy_init_done = 1;
+ if (core_if->core_params->phy_type)
+ gusbcfg |= DWC_USBCFG_ULPI_UTMI_SEL;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_ULPI_UTMI_SEL);
+
+ if (gusbcfg & DWC_USBCFG_ULPI_UTMI_SEL) {
+ /* ULPI interface */
+ gusbcfg |= DWC_USBCFG_PHYIF;
+ if (core_if->core_params->phy_ulpi_ddr)
+ gusbcfg |= DWC_USBCFG_DDRSEL;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_DDRSEL);
+ } else {
+ /* UTMI+ interface */
+ if (core_if->core_params->phy_utmi_width == 16)
+ gusbcfg |= DWC_USBCFG_PHYIF;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_PHYIF);
+ }
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Reset after setting the PHY parameters */
+ dwc_otg_core_reset(core_if);
+ }
+
+ if (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 &&
+ DWC_HWCFG2_FS_PHY_TYPE_RD(core_if->hwcfg2) == 1 &&
+ core_if->core_params->ulpi_fs_ls) {
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg |= DWC_USBCFG_ULPI_FSLS;
+ gusbcfg |= DWC_USBCFG_ULPI_CLK_SUS_M;
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+ } else {
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ gusbcfg &= ~((u32) DWC_USBCFG_ULPI_FSLS);
+ gusbcfg &= ~((u32) DWC_USBCFG_ULPI_CLK_SUS_M);
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+ }
+
+ /* Program the GAHBCFG Register. */
+ switch (DWC_HWCFG2_ARCH_RD(core_if->hwcfg2)) {
+ case DWC_SLAVE_ONLY_ARCH:
+ ahbcfg &= ~DWC_AHBCFG_NPFIFO_EMPTY; /* HALF empty */
+ ahbcfg &= ~DWC_AHBCFG_FIFO_EMPTY; /* HALF empty */
+ core_if->dma_enable = 0;
+ break;
+ case DWC_EXT_DMA_ARCH:
+ ahbcfg = (ahbcfg & ~DWC_AHBCFG_BURST_LEN(0xf)) |
+ DWC_AHBCFG_BURST_LEN(core_if->core_params->dma_burst_size);
+ core_if->dma_enable = (core_if->core_params->dma_enable != 0);
+ break;
+ case DWC_INT_DMA_ARCH:
+ ahbcfg = (ahbcfg & ~DWC_AHBCFG_BURST_LEN(0xf)) |
+ DWC_AHBCFG_BURST_LEN(DWC_GAHBCFG_INT_DMA_BURST_INCR);
+ core_if->dma_enable = (core_if->core_params->dma_enable != 0);
+ break;
+ }
+
+ if (core_if->dma_enable)
+ ahbcfg |= DWC_AHBCFG_DMA_ENA;
+ else
+ ahbcfg &= ~DWC_AHBCFG_DMA_ENA;
+ dwc_reg_write(global_reg, DWC_GAHBCFG, ahbcfg);
+ core_if->en_multiple_tx_fifo =
+ DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4);
+
+ /* Program the GUSBCFG register. */
+ gusbcfg = dwc_reg_read(global_reg, DWC_GUSBCFG);
+ switch (DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2)) {
+ case DWC_MODE_HNP_SRP_CAPABLE:
+ if (core_if->core_params->otg_cap ==
+ DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_HNP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_SRP_ONLY_CAPABLE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_NO_HNP_SRP_CAPABLE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_SRP_CAPABLE_DEVICE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_NO_SRP_CAPABLE_DEVICE:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_SRP_CAPABLE_HOST:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ if (core_if->core_params->otg_cap !=
+ DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE)
+ gusbcfg |= DWC_USBCFG_SRP_CAP;
+ else
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ case DWC_MODE_NO_SRP_CAPABLE_HOST:
+ gusbcfg &= ~((u32) DWC_USBCFG_HNP_CAP);
+ gusbcfg &= ~((u32) DWC_USBCFG_SRP_CAP);
+ break;
+ }
+ dwc_reg_write(global_reg, DWC_GUSBCFG, gusbcfg);
+
+ /* Enable common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /*
+ * Do device or host intialization based on mode during PCD
+ * and HCD initialization
+ */
+ if (dwc_otg_is_host_mode(core_if)) {
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ } else {
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ if (dwc_has_feature(core_if, DWC_DEVICE_ONLY))
+ dwc_otg_core_dev_init(core_if);
+ }
+}
+
+/**
+ * This function enables the Device mode interrupts.
+ *
+ * Note that the bits in the Device IN endpoint mask register are laid out
+ * exactly the same as the Device IN endpoint interrupt register.
+ */
+static void dwc_otg_enable_device_interrupts(struct core_if *core_if)
+{
+ u32 intr_mask = 0;
+ u32 msk = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ /* Disable all interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTMSK, 0);
+
+ /* Clear any pending interrupts */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /* Enable interrupts */
+ intr_mask |= DWC_INTMSK_USB_RST;
+ intr_mask |= DWC_INTMSK_ENUM_DONE;
+ intr_mask |= DWC_INTMSK_IN_ENDP;
+ intr_mask |= DWC_INTMSK_OUT_ENDP;
+ intr_mask |= DWC_INTMSK_EARLY_SUSP;
+ if (!core_if->en_multiple_tx_fifo)
+ intr_mask |= DWC_INTMSK_ENDP_MIS_MTCH;
+
+ /* Periodic EP */
+ intr_mask |= DWC_INTMSK_ISYNC_OUTPKT_DRP;
+ intr_mask |= DWC_INTMSK_END_OF_PFRM;
+ intr_mask |= DWC_INTMSK_INCMP_IN_ATX;
+ intr_mask |= DWC_INTMSK_INCMP_OUT_PTX;
+
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+
+ msk = DWC_DIEPMSK_TXFIFO_UNDERN_RW(msk, 1);
+ dwc_reg_modify(core_if->dev_if->dev_global_regs, DWC_DIEPMSK,
+ msk, msk);
+}
+
+/**
+ * Configures the device data fifo sizes when dynamic sizing is enabled.
+ */
+static void config_dev_dynamic_fifos(struct core_if *core_if)
+{
+ u32 i;
+ ulong regs = core_if->core_global_regs;
+ struct core_params *params = core_if->core_params;
+ u32 txsize = 0;
+ u32 nptxsize = 0;
+ u32 ptxsize = 0;
+
+ /* Rx FIFO */
+ dwc_reg_write(regs, DWC_GRXFSIZ, params->dev_rx_fifo_size);
+
+ /* Set Periodic and Non-periodic Tx FIFO Mask bits to all 0 */
+ core_if->p_tx_msk = 0;
+ core_if->tx_msk = 0;
+
+ if (core_if->en_multiple_tx_fifo == 0) {
+ /* Non-periodic Tx FIFO */
+ nptxsize = DWC_RX_FIFO_DEPTH_WR(nptxsize,
+ params->
+ dev_nperio_tx_fifo_size);
+ nptxsize =
+ DWC_RX_FIFO_START_ADDR_WR(nptxsize,
+ params->dev_rx_fifo_size);
+ dwc_reg_write(regs, DWC_GNPTXFSIZ, nptxsize);
+
+ ptxsize = DWC_RX_FIFO_START_ADDR_WR(ptxsize,
+ (DWC_RX_FIFO_START_ADDR_RD
+ (nptxsize) +
+ DWC_RX_FIFO_DEPTH_RD
+ (nptxsize)));
+ for (i = 0;
+ i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ i++) {
+ ptxsize =
+ DWC_RX_FIFO_DEPTH_WR(ptxsize,
+ params->
+ dev_perio_tx_fifo_size[i]);
+ dwc_reg_write(regs, DWC_DPTX_FSIZ_DIPTXF(i), ptxsize);
+ ptxsize = DWC_RX_FIFO_START_ADDR_WR(ptxsize,
+ (DWC_RX_FIFO_START_ADDR_RD
+ (ptxsize) +
+ DWC_RX_FIFO_DEPTH_RD
+ (ptxsize)));
+ }
+ } else {
+ nptxsize = DWC_RX_FIFO_DEPTH_WR(nptxsize,
+ params->
+ dev_nperio_tx_fifo_size);
+ nptxsize =
+ DWC_RX_FIFO_START_ADDR_WR(nptxsize,
+ params->dev_rx_fifo_size);
+ dwc_reg_write(regs, DWC_GNPTXFSIZ, nptxsize);
+
+ txsize = DWC_RX_FIFO_START_ADDR_WR(txsize,
+ (DWC_RX_FIFO_START_ADDR_RD
+ (nptxsize) +
+ DWC_RX_FIFO_DEPTH_RD
+ (nptxsize)));
+ for (i = 1;
+ i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ i++) {
+ txsize =
+ DWC_RX_FIFO_DEPTH_WR(txsize,
+ params->dev_tx_fifo_size[i]);
+ dwc_reg_write(regs, DWC_DPTX_FSIZ_DIPTXF(i - 1),
+ txsize);
+ txsize = DWC_RX_FIFO_START_ADDR_WR(txsize,
+ (DWC_RX_FIFO_START_ADDR_RD
+ (txsize) +
+ DWC_RX_FIFO_DEPTH_RD
+ (txsize)));
+ }
+ }
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * device mode.
+ */
+void dwc_otg_core_dev_init(struct core_if *c_if)
+{
+ u32 i;
+ struct device_if *d_if = c_if->dev_if;
+ struct core_params *params = c_if->core_params;
+ u32 dcfg = 0;
+ u32 resetctl = 0;
+ u32 dthrctl = 0;
+
+ /* Restart the Phy Clock */
+ dwc_reg_write(c_if->pcgcctl, 0, 0);
+
+ /* Device configuration register */
+ init_devspd(c_if);
+ dcfg = dwc_reg_read(d_if->dev_global_regs, DWC_DCFG);
+ dcfg = DWC_DCFG_P_FRM_INTRVL_WR(dcfg, DWC_DCFG_FRAME_INTERVAL_80);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DCFG, dcfg);
+
+ /* If needed configure data FIFO sizes */
+ if (DWC_HWCFG2_DYN_FIFO_RD(c_if->hwcfg2) && params->enable_dynamic_fifo)
+ config_dev_dynamic_fifos(c_if);
+
+ /* Flush the FIFOs */
+ dwc_otg_flush_tx_fifo(c_if, DWC_GRSTCTL_TXFNUM_ALL);
+ dwc_otg_flush_rx_fifo(c_if);
+
+ /* Flush the Learning Queue. */
+ resetctl |= DWC_RSTCTL_TKN_QUE_FLUSH;
+ dwc_reg_write(c_if->core_global_regs, DWC_GRSTCTL, resetctl);
+
+ /* Clear all pending Device Interrupts */
+ dwc_reg_write(d_if->dev_global_regs, DWC_DIEPMSK, 0);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DOEPMSK, 0);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DAINT, 0xFFFFFFFF);
+ dwc_reg_write(d_if->dev_global_regs, DWC_DAINTMSK, 0);
+
+ for (i = 0; i <= d_if->num_in_eps; i++) {
+ u32 depctl = 0;
+
+ depctl = dwc_reg_read(d_if->in_ep_regs[i], DWC_DIEPCTL);
+ if (DWC_DEPCTL_EPENA_RD(depctl)) {
+ depctl = 0;
+ depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1);
+ depctl = DWC_DEPCTL_SET_NAK_RW(depctl, 1);
+ } else {
+ depctl = 0;
+ }
+
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPCTL, depctl);
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPTSIZ, 0);
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPDMA, 0);
+ dwc_reg_write(d_if->in_ep_regs[i], DWC_DIEPINT, 0xFF);
+ }
+
+ for (i = 0; i <= d_if->num_out_eps; i++) {
+ u32 depctl = 0;
+ depctl = dwc_reg_read(d_if->out_ep_regs[i], DWC_DOEPCTL);
+ if (DWC_DEPCTL_EPENA_RD(depctl)) {
+ depctl = 0;
+ depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1);
+ depctl = DWC_DEPCTL_SET_NAK_RW(depctl, 1);
+ } else {
+ depctl = 0;
+ }
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPCTL, depctl);
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPTSIZ, 0);
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPDMA, 0);
+ dwc_reg_write(d_if->out_ep_regs[i], DWC_DOEPINT, 0xFF);
+ }
+
+ if (c_if->en_multiple_tx_fifo && c_if->dma_enable) {
+ d_if->non_iso_tx_thr_en = c_if->core_params->thr_ctl & 0x1;
+ d_if->iso_tx_thr_en = (c_if->core_params->thr_ctl >> 1) & 0x1;
+ d_if->rx_thr_en = (c_if->core_params->thr_ctl >> 2) & 0x1;
+ d_if->rx_thr_length = c_if->core_params->rx_thr_length;
+ d_if->tx_thr_length = c_if->core_params->tx_thr_length;
+
+ dthrctl = 0;
+ dthrctl = DWC_DTHCTRL_NON_ISO_THR_ENA_RW
+ (dthrctl, d_if->non_iso_tx_thr_en);
+ dthrctl = DWC_DTHCTRL_ISO_THR_EN_RW
+ (dthrctl, d_if->iso_tx_thr_en);
+ dthrctl = DWC_DTHCTRL_TX_THR_LEN_RW
+ (dthrctl, d_if->tx_thr_length);
+ dthrctl = DWC_DTHCTRL_RX_THR_EN_RW(dthrctl, d_if->rx_thr_en);
+ dthrctl = DWC_DTHCTRL_RX_THR_LEN_RW
+ (dthrctl, d_if->rx_thr_length);
+ dwc_reg_write(d_if->dev_global_regs,
+ DWC_DTKNQR3_DTHRCTL, dthrctl);
+
+ }
+
+ dwc_otg_enable_device_interrupts(c_if);
+}
+
+/**
+ * This function reads a packet from the Rx FIFO into the destination buffer.
+ * To read SETUP data use dwc_otg_read_setup_packet.
+ */
+void dwc_otg_read_packet(struct core_if *core_if, u8 * dest, u16 _bytes)
+{
+ u32 i;
+ int word_count = (_bytes + 3) / 4;
+ u32 fifo = core_if->data_fifo[0];
+ u32 *data_buff = (u32 *) dest;
+
+ /*
+ * This requires reading data from the FIFO into a u32 temp buffer,
+ * then moving it into the data buffer.
+ */
+ for (i = 0; i < word_count; i++, data_buff++)
+ *data_buff = dwc_read_fifo32(fifo);
+}
+
+/**
+ * Flush a Tx FIFO.
+ */
+void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int num)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 greset = 0;
+ int count = 0;
+
+ greset |= DWC_RSTCTL_TX_FIFO_FLUSH;
+ greset = DWC_RSTCTL_TX_FIFO_NUM(greset, num);
+ dwc_reg_write(global_regs, DWC_GRSTCTL, greset);
+
+ do {
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 10000) {
+ pr_warning("%s() HANG! GRSTCTL=%0x "
+ "GNPTXSTS=0x%08x\n", __func__, greset,
+ dwc_reg_read(global_regs, DWC_GNPTXSTS));
+ break;
+ }
+ udelay(1);
+ } while (greset & DWC_RSTCTL_TX_FIFO_FLUSH);
+
+ /* Wait for 3 PHY Clocks */
+ udelay(1);
+}
+
+/**
+ * Flush Rx FIFO.
+ */
+void dwc_otg_flush_rx_fifo(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 greset = 0;
+ int count = 0;
+
+ greset |= DWC_RSTCTL_RX_FIFO_FLUSH;
+ dwc_reg_write(global_regs, DWC_GRSTCTL, greset);
+
+ do {
+ greset = dwc_reg_read(global_regs, DWC_GRSTCTL);
+ if (++count > 10000) {
+ pr_warning("%s() HANG! GRSTCTL=%0x\n",
+ __func__, greset);
+ break;
+ }
+ udelay(1);
+ } while (greset & DWC_RSTCTL_RX_FIFO_FLUSH);
+
+ /* Wait for 3 PHY Clocks */
+ udelay(1);
+}
+
+/**
+ * Register HCD callbacks.
+ * The callbacks are used to start and stop the HCD for interrupt processing.
+ */
+void dwc_otg_cil_register_hcd_callbacks(struct core_if *c_if,
+ struct cil_callbacks *cb,
+ void *p)
+{
+ c_if->hcd_cb = cb;
+ cb->p = p;
+}
+
+/**
+ * Register PCD callbacks.
+ * The callbacks are used to start and stop the PCD for interrupt processing.
+ */
+void dwc_otg_cil_register_pcd_callbacks(struct core_if *c_if,
+ struct cil_callbacks *cb,
+ void *p)
+{
+ c_if->pcd_cb = cb;
+ cb->p = p;
+}
+
+/**
+ * This function is called to initialize the DWC_otg CSR data structures.
+ *
+ * The register addresses in the device and host structures are initialized from
+ * the base address supplied by the caller. The calling function must make the
+ * OS calls to get the base address of the DWC_otg controller registers.
+ *
+ * The params argument holds the parameters that specify how the core should be
+ * configured.
+ */
+struct core_if *dwc_otg_cil_init(const __iomem u32 * base,
+ struct core_params *params)
+{
+ struct core_if *core_if;
+ struct device_if *dev_if;
+ struct dwc_host_if *host_if;
+ u8 *reg_base = (__force u8 *)base;
+ u32 offset;
+ u32 i;
+
+ core_if = kzalloc(sizeof(*core_if), GFP_KERNEL);
+ if (!core_if)
+ return NULL;
+
+ core_if->core_params = params;
+ core_if->core_global_regs = (ulong)reg_base;
+
+ /* Allocate the Device Mode structures. */
+ dev_if = kmalloc(sizeof(*dev_if), GFP_KERNEL);
+ if (!dev_if) {
+ kfree(core_if);
+ return NULL;
+ }
+
+ dev_if->dev_global_regs = (ulong)(reg_base + DWC_DEV_GLOBAL_REG_OFFSET);
+
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ offset = i * DWC_EP_REG_OFFSET;
+
+ dev_if->in_ep_regs[i] = (ulong)(reg_base +
+ DWC_DEV_IN_EP_REG_OFFSET +
+ offset);
+
+ dev_if->out_ep_regs[i] = (ulong)(reg_base +
+ DWC_DEV_OUT_EP_REG_OFFSET +
+ offset);
+ }
+
+ dev_if->speed = 0; /* unknown */
+ core_if->dev_if = dev_if;
+
+ /* Allocate the Host Mode structures. */
+ host_if = kmalloc(sizeof(*host_if), GFP_KERNEL);
+ if (!host_if) {
+ kfree(dev_if);
+ kfree(core_if);
+ return NULL;
+ }
+
+ host_if->host_global_regs = (ulong)(reg_base +
+ DWC_OTG_HOST_GLOBAL_REG_OFFSET);
+
+ host_if->hprt0 = (ulong)(reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET);
+
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ offset = i * DWC_OTG_CHAN_REGS_OFFSET;
+
+ host_if->hc_regs[i] = (ulong)(reg_base +
+ DWC_OTG_HOST_CHAN_REGS_OFFSET +
+ offset);
+ }
+
+ host_if->num_host_channels = MAX_EPS_CHANNELS;
+ core_if->host_if = host_if;
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ core_if->data_fifo[i] =
+ (ulong)(reg_base + DWC_OTG_DATA_FIFO_OFFSET +
+ (i * DWC_OTG_DATA_FIFO_SIZE));
+ }
+ core_if->pcgcctl = (ulong)(reg_base + DWC_OTG_PCGCCTL_OFFSET);
+
+ /*
+ * Store the contents of the hardware configuration registers here for
+ * easy access later.
+ */
+ core_if->hwcfg1 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG1);
+ core_if->hwcfg2 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG2);
+ core_if->hwcfg3 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG3);
+ core_if->hwcfg4 =
+ dwc_reg_read(core_if->core_global_regs, DWC_GHWCFG4);
+
+ /* Set the SRP sucess bit for FS-I2c */
+ core_if->srp_success = 0;
+ core_if->srp_timer_started = 0;
+ return core_if;
+}
+
+/**
+ * This function frees the structures allocated by dwc_otg_cil_init().
+ */
+void dwc_otg_cil_remove(struct core_if *core_if)
+{
+ /* Disable all interrupts */
+ dwc_reg_modify(core_if->core_global_regs, DWC_GAHBCFG, 1, 0);
+ dwc_reg_write(core_if->core_global_regs, DWC_GINTMSK, 0);
+
+ if (core_if) {
+ kfree(core_if->dev_if);
+ kfree(core_if->host_if);
+ }
+ kfree(core_if);
+}
diff --git a/drivers/usb/dwc/cil.h b/drivers/usb/dwc/cil.h
new file mode 100644
index 0000000..80c4361
--- /dev/null
+++ b/drivers/usb/dwc/cil.h
@@ -0,0 +1,1179 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#if !defined(__DWC_CIL_H__)
+#define __DWC_CIL_H__
+#include <linux/io.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/interrupt.h>
+#include <linux/dmapool.h>
+#include <linux/spinlock.h>
+#include <linux/usb/otg.h>
+
+#include "regs.h"
+
+#ifdef CONFIG_DWC_DEBUG
+#define DEBUG
+#endif
+
+#ifdef CONFIG_ARM
+#define in_le32(x) readl(x)
+#define out_le32(x,y) writel(y,x)
+#endif
+
+/**
+ * Reads the content of a register.
+ */
+static inline u32 dwc_reg_read(ulong reg , u32 offset)
+{
+
+#ifdef CONFIG_DWC_OTG_REG_LE
+ return in_le32((unsigned __iomem *)(reg + offset));
+#else
+ return in_be32((unsigned __iomem *)(reg + offset));
+#endif
+}
+
+static inline void dwc_reg_write(ulong reg, u32 offset, const u32 value)
+{
+#ifdef CONFIG_DWC_OTG_REG_LE
+ out_le32((unsigned __iomem *)(reg + offset), value);
+#else
+ out_be32((unsigned __iomem *)(reg + offset), value);
+#endif
+};
+/**
+ * This function modifies bit values in a register. Using the
+ * algorithm: (reg_contents & ~clear_mask) | set_mask.
+ */
+static inline void dwc_reg_modify(ulong reg, u32 offset, const u32 _clear_mask,
+ const u32 _set_mask)
+{
+#ifdef CONFIG_DWC_OTG_REG_LE
+ out_le32((unsigned __iomem *)(reg + offset),
+ (in_le32((unsigned __iomem *)(reg + offset))
+ & ~_clear_mask) | _set_mask);
+#else
+ out_be32((unsigned __iomem *)(reg + offset),
+ (in_be32(((unsigned __iomem *))(reg + offset))
+ & ~_clear_mask) | _set_mask);
+#endif
+};
+
+static inline void dwc_write_fifo32(ulong reg, const u32 _value)
+{
+#ifdef CONFIG_DWC_OTG_FIFO_LE
+ out_le32((unsigned __iomem *)reg, _value);
+#else
+ out_be32((unsigned __iomem *)reg, _value);
+#endif
+};
+
+static inline u32 dwc_read_fifo32(ulong _reg)
+{
+#ifdef CONFIG_DWC_OTG_FIFO_LE
+ return in_le32((unsigned __iomem *) _reg);
+#else
+ return in_be32((unsigned __iomem *) _reg);
+#endif
+};
+
+/*
+ * Debugging support vanishes in non-debug builds.
+ */
+/* Display CIL Debug messages */
+#define dwc_dbg_cil (0x2)
+
+/* Display CIL Verbose debug messages */
+#define dwc_dbg_cilv (0x20)
+
+/* Display PCD (Device) debug messages */
+#define dwc_dbg_pcd (0x4)
+
+/* Display PCD (Device) Verbose debug messages */
+#define dwc_dbg_pcdv (0x40)
+
+/* Display Host debug messages */
+#define dwc_dbg_hcd (0x8)
+
+/* Display Verbose Host debug messages */
+#define dwc_dbg_hcdv (0x80)
+
+/* Display enqueued URBs in host mode. */
+#define dwc_dbg_hcd_urb (0x800)
+
+/* Display "special purpose" debug messages */
+#define dwc_dbg_sp (0x400)
+
+/* Display all debug messages */
+#define dwc_dbg_any (0xFF)
+
+/* All debug messages off */
+#define dwc_dbg_off 0
+
+/* Prefix string for DWC_DEBUG print macros. */
+#define usb_dwc "dwc_otg: "
+
+/*
+ * This file contains the interface to the Core Interface Layer.
+ */
+
+/*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger transfers
+ * into multiple transfers of 1024 bytes sized messages.
+ * I happens often, that transfers of 4096 bytes are
+ * required (zero-gadget, file_storage-gadget).
+ *
+ * MAX_XFER_LEN is set to 1024 right now, but could be 2047,
+ * since the xfer-size field in the 405EZ USB device controller
+ * implementation has 11 bits. Using 1024 seems to work for now.
+ */
+#define MAX_XFER_LEN 1024
+
+/*
+ * The dwc_ep structure represents the state of a single endpoint when acting in
+ * device mode. It contains the data items needed for an endpoint to be
+ * activated and transfer packets.
+ */
+struct dwc_ep {
+ /* EP number used for register address lookup */
+ u8 num;
+ /* EP direction 0 = OUT */
+ unsigned is_in:1;
+ /* EP active. */
+ unsigned active:1;
+
+ /*
+ * Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use
+ * non-periodic Tx FIFO If dedicated Tx FIFOs are enabled for all
+ * IN Eps - Tx FIFO # FOR IN EPs
+ */
+ unsigned tx_fifo_num:4;
+ /* EP type: 0 - Control, 1 - ISOC, 2 - BULK, 3 - INTR */
+ unsigned type:2;
+#define DWC_OTG_EP_TYPE_CONTROL 0
+#define DWC_OTG_EP_TYPE_ISOC 1
+#define DWC_OTG_EP_TYPE_BULK 2
+#define DWC_OTG_EP_TYPE_INTR 3
+
+ /* DATA start PID for INTR and BULK EP */
+ unsigned data_pid_start:1;
+ /* Frame (even/odd) for ISOC EP */
+ unsigned even_odd_frame:1;
+ /* Max Packet bytes */
+ unsigned maxpacket:11;
+
+ ulong dma_addr;
+
+ /*
+ * Pointer to the beginning of the transfer buffer -- do not modify
+ * during transfer.
+ */
+ u8 *start_xfer_buff;
+ /* pointer to the transfer buffer */
+ u8 *xfer_buff;
+ /* Number of bytes to transfer */
+ unsigned xfer_len:19;
+ /* Number of bytes transferred. */
+ unsigned xfer_count:19;
+ /* Sent ZLP */
+ unsigned sent_zlp:1;
+ /* Total len for control transfer */
+ unsigned total_len:19;
+
+ /* stall clear flag */
+ unsigned stall_clear_flag:1;
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger transfers
+ * into multiple transfers of 1024 bytes sized messages.
+ * I happens often, that transfers of 4096 bytes are
+ * required (zero-gadget, file_storage-gadget).
+ *
+ * "bytes_pending" will hold the amount of bytes that are
+ * still pending to be send in further messages to complete
+ * the bigger transfer.
+ */
+ u32 bytes_pending;
+};
+
+/*
+ * States of EP0.
+ */
+enum ep0_state {
+ EP0_DISCONNECT = 0, /* no host */
+ EP0_IDLE = 1,
+ EP0_IN_DATA_PHASE = 2,
+ EP0_OUT_DATA_PHASE = 3,
+ EP0_STATUS = 4,
+ EP0_STALL = 5,
+};
+
+/* Fordward declaration.*/
+struct dwc_pcd;
+
+/*
+ * This structure describes an EP, there is an array of EPs in the PCD
+ * structure.
+ */
+struct pcd_ep {
+ /* USB EP data */
+ struct usb_ep ep;
+ /* USB EP Descriptor */
+ const struct usb_endpoint_descriptor *desc;
+
+ /* queue of dwc_otg_pcd_requests. */
+ struct list_head queue;
+ unsigned stopped:1;
+ unsigned disabling:1;
+ unsigned dma:1;
+ unsigned queue_sof:1;
+ unsigned wedged:1;
+
+ /* DWC_otg ep data. */
+ struct dwc_ep dwc_ep;
+
+ /* Pointer to PCD */
+ struct dwc_pcd *pcd;
+};
+
+/*
+ * DWC_otg PCD Structure.
+ * This structure encapsulates the data for the dwc_otg PCD.
+ */
+struct dwc_pcd {
+ /* USB gadget */
+ struct usb_gadget gadget;
+ /* USB gadget driver pointer */
+ struct usb_gadget_driver *driver;
+ /* The DWC otg device pointer. */
+ struct dwc_otg_device *otg_dev;
+
+ /* State of EP0 */
+ enum ep0_state ep0state;
+ /* EP0 Request is pending */
+ unsigned ep0_pending:1;
+ /* Indicates when SET CONFIGURATION Request is in process */
+ unsigned request_config:1;
+ /* The state of the Remote Wakeup Enable. */
+ unsigned remote_wakeup_enable:1;
+ /* The state of the B-Device HNP Enable. */
+ unsigned b_hnp_enable:1;
+ /* The state of A-Device HNP Support. */
+ unsigned a_hnp_support:1;
+ /* The state of the A-Device Alt HNP support. */
+ unsigned a_alt_hnp_support:1;
+ /* Count of pending Requests */
+ unsigned request_pending;
+
+ /*
+ * SETUP packet for EP0. This structure is allocated as a DMA buffer on
+ * PCD initialization with enough space for up to 3 setup packets.
+ */
+ union {
+ struct usb_ctrlrequest req;
+ u32 d32[2];
+ } *setup_pkt;
+
+ struct dma_pool *dwc_pool;
+ dma_addr_t setup_pkt_dma_handle;
+
+ /* 2-byte dma buffer used to return status from GET_STATUS */
+ u16 *status_buf;
+ dma_addr_t status_buf_dma_handle;
+
+ /* Array of EPs. */
+ struct pcd_ep ep0;
+ /* Array of IN EPs. */
+ struct pcd_ep in_ep[MAX_EPS_CHANNELS - 1];
+ /* Array of OUT EPs. */
+ struct pcd_ep out_ep[MAX_EPS_CHANNELS - 1];
+ spinlock_t lock;
+ /*
+ * Timer for SRP. If it expires before SRP is successful clear the
+ * SRP.
+ */
+ struct timer_list srp_timer;
+
+ /*
+ * Tasklet to defer starting of TEST mode transmissions until Status
+ * Phase has been completed.
+ */
+ struct tasklet_struct test_mode_tasklet;
+
+ /* Tasklet to delay starting of xfer in DMA mode */
+ struct tasklet_struct *start_xfer_tasklet;
+
+ /* The test mode to enter when the tasklet is executed. */
+ unsigned test_mode;
+};
+
+/*
+ * This structure holds the state of the HCD, including the non-periodic and
+ * periodic schedules.
+ */
+struct dwc_hcd {
+ spinlock_t lock;
+
+ /* DWC OTG Core Interface Layer */
+ struct core_if *core_if;
+
+ /* Internal DWC HCD Flags */
+ union dwc_otg_hcd_internal_flags {
+ u32 d32;
+ struct {
+ unsigned port_connect_status_change:1;
+ unsigned port_connect_status:1;
+ unsigned port_reset_change:1;
+ unsigned port_enable_change:1;
+ unsigned port_suspend_change:1;
+ unsigned port_over_current_change:1;
+ unsigned reserved:27;
+ } b;
+ } flags;
+
+ /*
+ * Inactive items in the non-periodic schedule. This is a list of
+ * Queue Heads. Transfers associated with these Queue Heads are not
+ * currently assigned to a host channel.
+ */
+ struct list_head non_periodic_sched_inactive;
+
+ /*
+ * Deferred items in the non-periodic schedule. This is a list of
+ * Queue Heads. Transfers associated with these Queue Heads are not
+ * currently assigned to a host channel.
+ * When we get an NAK, the QH goes here.
+ */
+ struct list_head non_periodic_sched_deferred;
+
+ /*
+ * Active items in the non-periodic schedule. This is a list of
+ * Queue Heads. Transfers associated with these Queue Heads are
+ * currently assigned to a host channel.
+ */
+ struct list_head non_periodic_sched_active;
+
+ /*
+ * Pointer to the next Queue Head to process in the active
+ * non-periodic schedule.
+ */
+ struct list_head *non_periodic_qh_ptr;
+
+ /*
+ * Inactive items in the periodic schedule. This is a list of QHs for
+ * periodic transfers that are _not_ scheduled for the next frame.
+ * Each QH in the list has an interval counter that determines when it
+ * needs to be scheduled for execution. This scheduling mechanism
+ * allows only a simple calculation for periodic bandwidth used (i.e.
+ * must assume that all periodic transfers may need to execute in the
+ * same frame). However, it greatly simplifies scheduling and should
+ * be sufficient for the vast majority of OTG hosts, which need to
+ * connect to a small number of peripherals at one time.
+ *
+ * Items move from this list to periodic_sched_ready when the QH
+ * interval counter is 0 at SOF.
+ */
+ struct list_head periodic_sched_inactive;
+
+ /*
+ * List of periodic QHs that are ready for execution in the next
+ * frame, but have not yet been assigned to host channels.
+ *
+ * Items move from this list to periodic_sched_assigned as host
+ * channels become available during the current frame.
+ */
+ struct list_head periodic_sched_ready;
+
+ /*
+ * List of periodic QHs to be executed in the next frame that are
+ * assigned to host channels.
+ *
+ * Items move from this list to periodic_sched_queued as the
+ * transactions for the QH are queued to the DWC_otg controller.
+ */
+ struct list_head periodic_sched_assigned;
+
+ /*
+ * List of periodic QHs that have been queued for execution.
+ *
+ * Items move from this list to either periodic_sched_inactive or
+ * periodic_sched_ready when the channel associated with the transfer
+ * is released. If the interval for the QH is 1, the item moves to
+ * periodic_sched_ready because it must be rescheduled for the next
+ * frame. Otherwise, the item moves to periodic_sched_inactive.
+ */
+ struct list_head periodic_sched_queued;
+
+ /*
+ * Total bandwidth claimed so far for periodic transfers. This value
+ * is in microseconds per (micro)frame. The assumption is that all
+ * periodic transfers may occur in the same (micro)frame.
+ */
+ u16 periodic_usecs;
+
+ /*
+ * Total bandwidth claimed so far for all periodic transfers
+ * in a frame.
+ * This will include a mixture of HS and FS transfers.
+ * Units are microseconds per (micro)frame.
+ * We have a budget per frame and have to schedule
+ * transactions accordingly.
+ * Watch out for the fact that things are actually scheduled for the
+ * "next frame".
+ */
+ u16 frame_usecs[8];
+
+ /*
+ * Frame number read from the core at SOF. The value ranges from 0 to
+ * DWC_HFNUM_MAX_FRNUM.
+ */
+ u16 frame_number;
+
+ /*
+ * Free host channels in the controller. This is a list of
+ * struct dwc_hc items.
+ */
+ struct list_head free_hc_list;
+
+ /*
+ * Number of available host channels.
+ */
+ u32 available_host_channels;
+
+ /*
+ * Array of pointers to the host channel descriptors. Allows accessing
+ * a host channel descriptor given the host channel number. This is
+ * useful in interrupt handlers.
+ */
+ struct dwc_hc *hc_ptr_array[MAX_EPS_CHANNELS];
+
+ /*
+ * Buffer to use for any data received during the status phase of a
+ * control transfer. Normally no data is transferred during the status
+ * phase. This buffer is used as a bit bucket.
+ */
+ u8 *status_buf;
+
+ /*
+ * DMA address for status_buf.
+ */
+ dma_addr_t status_buf_dma;
+#define DWC_OTG_HCD_STATUS_BUF_SIZE 64
+
+ /*
+ * Structure to allow starting the HCD in a non-interrupt context
+ * during an OTG role change.
+ */
+ struct work_struct start_work;
+ struct usb_hcd *_p;
+
+ /*
+ * Connection timer. An OTG host must display a message if the device
+ * does not connect. Started when the VBus power is turned on via
+ * sysfs attribute "buspower".
+ */
+ struct timer_list conn_timer;
+
+ /* workqueue for port wakeup */
+ struct work_struct usb_port_reset;
+
+ /* Addition HCD interrupt */
+ int cp_irq; /* charge pump interrupt */
+ int cp_irq_installed;
+};
+
+/*
+ * Reasons for halting a host channel.
+ */
+enum dwc_halt_status {
+ DWC_OTG_HC_XFER_NO_HALT_STATUS,
+ DWC_OTG_HC_XFER_COMPLETE,
+ DWC_OTG_HC_XFER_URB_COMPLETE,
+ DWC_OTG_HC_XFER_ACK,
+ DWC_OTG_HC_XFER_NAK,
+ DWC_OTG_HC_XFER_NYET,
+ DWC_OTG_HC_XFER_STALL,
+ DWC_OTG_HC_XFER_XACT_ERR,
+ DWC_OTG_HC_XFER_FRAME_OVERRUN,
+ DWC_OTG_HC_XFER_BABBLE_ERR,
+ DWC_OTG_HC_XFER_DATA_TOGGLE_ERR,
+ DWC_OTG_HC_XFER_AHB_ERR,
+ DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE,
+ DWC_OTG_HC_XFER_URB_DEQUEUE
+};
+
+/*
+ * Host channel descriptor. This structure represents the state of a single
+ * host channel when acting in host mode. It contains the data items needed to
+ * transfer packets to an endpoint via a host channel.
+ */
+struct dwc_hc {
+ /* Host channel number used for register address lookup */
+ u8 hc_num;
+
+ /* Device to access */
+ unsigned dev_addr:7;
+
+ /* EP to access */
+ unsigned ep_num:4;
+
+ /* EP direction. 0: OUT, 1: IN */
+ unsigned ep_is_in:1;
+
+ /*
+ * EP speed.
+ * One of the following values:
+ * - DWC_OTG_EP_SPEED_LOW
+ * - DWC_OTG_EP_SPEED_FULL
+ * - DWC_OTG_EP_SPEED_HIGH
+ */
+ unsigned speed:2;
+#define DWC_OTG_EP_SPEED_LOW 0
+#define DWC_OTG_EP_SPEED_FULL 1
+#define DWC_OTG_EP_SPEED_HIGH 2
+
+ /*
+ * Endpoint type.
+ * One of the following values:
+ * - DWC_OTG_EP_TYPE_CONTROL: 0
+ * - DWC_OTG_EP_TYPE_ISOC: 1
+ * - DWC_OTG_EP_TYPE_BULK: 2
+ * - DWC_OTG_EP_TYPE_INTR: 3
+ */
+ unsigned ep_type:2;
+
+ /* Max packet size in bytes */
+ unsigned max_packet:11;
+
+ /*
+ * PID for initial transaction.
+ * 0: DATA0,
+ * 1: DATA2,
+ * 2: DATA1,
+ * 3: MDATA (non-Control EP),
+ * SETUP (Control EP)
+ */
+ unsigned data_pid_start:2;
+#define DWC_OTG_HC_PID_DATA0 0
+#define DWC_OTG_HC_PID_DATA2 1
+#define DWC_OTG_HC_PID_DATA1 2
+#define DWC_OTG_HC_PID_MDATA 3
+#define DWC_OTG_HC_PID_SETUP 3
+
+ /* Number of periodic transactions per (micro)frame */
+ unsigned multi_count:2;
+
+ /* Pointer to the current transfer buffer position. */
+ u8 *xfer_buff;
+ /* Total number of bytes to transfer. */
+ u32 xfer_len;
+ /* Number of bytes transferred so far. */
+ u32 xfer_count;
+ /* Packet count at start of transfer. */
+ u16 start_pkt_count;
+
+ /*
+ * Flag to indicate whether the transfer has been started. Set to 1 if
+ * it has been started, 0 otherwise.
+ */
+ u8 xfer_started;
+
+ /*
+ * Set to 1 to indicate that a PING request should be issued on this
+ * channel. If 0, process normally.
+ */
+ u8 do_ping;
+
+ /*
+ * Set to 1 to indicate that the error count for this transaction is
+ * non-zero. Set to 0 if the error count is 0.
+ */
+ u8 error_state;
+
+ /*
+ * Set to 1 to indicate that this channel should be halted the next
+ * time a request is queued for the channel. This is necessary in
+ * slave mode if no request queue space is available when an attempt
+ * is made to halt the channel.
+ */
+ u8 halt_on_queue;
+
+ /*
+ * Set to 1 if the host channel has been halted, but the core is not
+ * finished flushing queued requests. Otherwise 0.
+ */
+ u8 halt_pending;
+
+ /* Reason for halting the host channel. */
+ enum dwc_halt_status halt_status;
+
+ /* Split settings for the host channel */
+ u8 do_split; /* Enable split for the channel */
+ u8 complete_split; /* Enable complete split */
+ u8 hub_addr; /* Address of high speed hub */
+ u8 port_addr; /* Port of the low/full speed device */
+
+ /*
+ * Split transaction position. One of the following values:
+ * - DWC_HCSPLIT_XACTPOS_MID
+ * - DWC_HCSPLIT_XACTPOS_BEGIN
+ * - DWC_HCSPLIT_XACTPOS_END
+ * - DWC_HCSPLIT_XACTPOS_ALL */
+ u8 xact_pos;
+
+ /* Set when the host channel does a short read. */
+ u8 short_read;
+
+ /*
+ * Number of requests issued for this channel since it was assigned to
+ * the current transfer (not counting PINGs).
+ */
+ u8 requests;
+
+ /* Queue Head for the transfer being processed by this channel. */
+ struct dwc_qh *qh;
+
+ /* Entry in list of host channels. */
+ struct list_head hc_list_entry;
+};
+
+/*
+ * The following parameters may be specified when starting the module. These
+ * parameters define how the DWC_otg controller should be configured. Parameter
+ * values are passed to the CIL initialization function dwc_otg_cil_init.
+ */
+struct core_params {
+ /*
+ * Specifies the OTG capabilities. The driver will automatically
+ * detect the value for this parameter if none is specified.
+ * 0 - HNP and SRP capable (default)
+ * 1 - SRP Only capable
+ * 2 - No HNP/SRP capable
+ */
+ int otg_cap;
+#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0
+#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1
+#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2
+
+#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE
+
+ /*
+ * Specifies whether to use slave or DMA mode for accessing the data
+ * FIFOs. The driver will automatically detect the value for this
+ * parameter if none is specified.
+ * 0 - Slave
+ * 1 - DMA (default, if available)
+ */
+ int dma_enable;
+#ifdef CONFIG_DWC_SLAVE
+#define dwc_param_dma_enable_default 0
+#else
+#define dwc_param_dma_enable_default 1
+#endif
+
+ /*
+ * The DMA Burst size (applicable only for External DMA Mode).
+ * 1, 4, 8 16, 32, 64, 128, 256 (default 32)
+ */
+ int dma_burst_size; /* Translate this to GAHBCFG values */
+#define dwc_param_dma_burst_size_default 32
+
+ /*
+ * Specifies the maximum speed of operation in host and device mode.
+ * The actual speed depends on the speed of the attached device and
+ * the value of phy_type. The actual speed depends on the speed of the
+ * attached device.
+ * 0 - High Speed (default)
+ * 1 - Full Speed
+ */
+ int speed;
+#define dwc_param_speed_default 0
+#define DWC_SPEED_PARAM_HIGH 0
+#define DWC_SPEED_PARAM_FULL 1
+
+ /*
+ * Specifies whether low power mode is supported when attached to a Full
+ * Speed or Low Speed device in host mode.
+ * 0 - Don't support low power mode (default)
+ * 1 - Support low power mode
+ */
+ int host_support_fs_ls_low_power;
+#define dwc_param_host_support_fs_ls_low_power_default 0
+
+ /*
+ * Specifies the PHY clock rate in low power mode when connected to a
+ * Low Speed device in host mode. This parameter is applicable only if
+ * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS
+ * then defaults to 6 MHZ otherwise 48 MHZ.
+ *
+ * 0 - 48 MHz
+ * 1 - 6 MHz
+ */
+ int host_ls_low_power_phy_clk;
+#define dwc_param_host_ls_low_power_phy_clk_default 0
+#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0
+#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1
+
+ /*
+ * 0 - Use cC FIFO size parameters
+ * 1 - Allow dynamic FIFO sizing (default)
+ */
+ int enable_dynamic_fifo;
+#define dwc_param_enable_dynamic_fifo_default 1
+
+ /*
+ * Number of 4-byte words in the Rx FIFO in device mode when dynamic
+ * FIFO sizing is enabled. 16 to 32768 (default 1064)
+ */
+ int dev_rx_fifo_size;
+#define dwc_param_dev_rx_fifo_size_default 1064
+
+ /*
+ * Number of 4-byte words in the non-periodic Tx FIFO in device mode
+ * when dynamic FIFO sizing is enabled. 16 to 32768 (default 1024)
+ */
+ int dev_nperio_tx_fifo_size;
+#define dwc_param_dev_nperio_tx_fifo_size_default 1024
+
+ /*
+ * Number of 4-byte words in each of the periodic Tx FIFOs in device
+ * mode when dynamic FIFO sizing is enabled. 4 to 768 (default 256)
+ */
+ u32 dev_perio_tx_fifo_size[MAX_PERIO_FIFOS];
+#define dwc_param_dev_perio_tx_fifo_size_default 256
+
+ /*
+ * Number of 4-byte words in the Rx FIFO in host mode when dynamic
+ * FIFO sizing is enabled. 16 to 32768 (default 1024)
+ */
+ int host_rx_fifo_size;
+#define dwc_param_host_rx_fifo_size_default 1024
+
+ /*
+ * Number of 4-byte words in the non-periodic Tx FIFO in host mode
+ * when Dynamic FIFO sizing is enabled in the core. 16 to 32768
+ * (default 1024)
+ */
+ int host_nperio_tx_fifo_size;
+#define dwc_param_host_nperio_tx_fifo_size_default 1024
+
+ /*
+ Number of 4-byte words in the host periodic Tx FIFO when dynamic
+ * FIFO sizing is enabled. 16 to 32768 (default 1024)
+ */
+ int host_perio_tx_fifo_size;
+#define dwc_param_host_perio_tx_fifo_size_default 1024
+
+ /*
+ * The maximum transfer size supported in bytes. 2047 to 65,535
+ * (default 65,535)
+ */
+ int max_transfer_size;
+#define dwc_param_max_transfer_size_default 65535
+
+ /*
+ * The maximum number of packets in a transfer. 15 to 511 (default 511)
+ */
+ int max_packet_count;
+#define dwc_param_max_packet_count_default 511
+
+ /*
+ * The number of host channel registers to use.
+ * 1 to 16 (default 12)
+ * Note: The FPGA configuration supports a maximum of 12 host channels.
+ */
+ int host_channels;
+#define dwc_param_host_channels_default 12
+
+ /*
+ * The number of endpoints in addition to EP0 available for device
+ * mode operations.
+ * 1 to 15 (default 6 IN and OUT)
+ * Note: The FPGA configuration supports a maximum of 6 IN and OUT
+ * endpoints in addition to EP0.
+ */
+ int dev_endpoints;
+#define dwc_param_dev_endpoints_default 6
+
+ /*
+ * Specifies the type of PHY interface to use. By default, the driver
+ * will automatically detect the phy_type.
+ *
+ * 0 - Full Speed PHY
+ * 1 - UTMI+ (default)
+ * 2 - ULPI
+ */
+ int phy_type;
+#define DWC_PHY_TYPE_PARAM_FS 0
+#define DWC_PHY_TYPE_PARAM_UTMI 1
+#define DWC_PHY_TYPE_PARAM_ULPI 2
+#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI
+
+ /*
+ * Specifies the UTMI+ Data Width. This parameter is applicable for a
+ * PHY_TYPE of UTMI+ or ULPI. (For a ULPI PHY_TYPE, this parameter
+ * indicates the data width between the MAC and the ULPI Wrapper.) Also,
+ * this parameter is applicable only if the OTG_HSPHY_WIDTH cC parameter
+ * was set to "8 and 16 bits", meaning that the core has been configured
+ * to work at either data path width.
+ *
+ * 8 or 16 bits (default 16)
+ */
+ int phy_utmi_width;
+#define dwc_param_phy_utmi_width_default 16
+
+ /*
+ * Specifies whether the ULPI operates at double or single
+ * data rate. This parameter is only applicable if PHY_TYPE is
+ * ULPI.
+ *
+ * 0 - single data rate ULPI interface with 8 bit wide data
+ * bus (default)
+ * 1 - double data rate ULPI interface with 4 bit wide data
+ * bus
+ */
+ int phy_ulpi_ddr;
+#define dwc_param_phy_ulpi_ddr_default 0
+
+ /*
+ * Specifies whether to use the internal or external supply to
+ * drive the vbus with a ULPI phy.
+ */
+ int phy_ulpi_ext_vbus;
+#define DWC_PHY_ULPI_INTERNAL_VBUS 0
+#define DWC_PHY_ULPI_EXTERNAL_VBUS 1
+#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS
+
+ /*
+ * Specifies whether to use the I2Cinterface for full speed PHY. This
+ * parameter is only applicable if PHY_TYPE is FS.
+ * 0 - No (default)
+ * 1 - Yes
+ */
+ int i2c_enable;
+#define dwc_param_i2c_enable_default 0
+
+ int ulpi_fs_ls;
+#define dwc_param_ulpi_fs_ls_default 0
+
+ int ts_dline;
+#define dwc_param_ts_dline_default 0
+
+ /*
+ * Specifies whether dedicated transmit FIFOs are enabled for non
+ * periodic IN endpoints in device mode
+ * 0 - No
+ * 1 - Yes
+ */
+ int en_multiple_tx_fifo;
+#define dwc_param_en_multiple_tx_fifo_default 1
+
+ /*
+ * Number of 4-byte words in each of the Tx FIFOs in device
+ * mode when dynamic FIFO sizing is enabled. 4 to 768 (default 256)
+ */
+ u32 dev_tx_fifo_size[MAX_TX_FIFOS];
+#define dwc_param_dev_tx_fifo_size_default 256
+
+ /*
+ * Thresholding enable flag
+ * bit 0 - enable non-ISO Tx thresholding
+ * bit 1 - enable ISO Tx thresholding
+ * bit 2 - enable Rx thresholding
+ */
+ u32 thr_ctl;
+#define dwc_param_thr_ctl_default 0
+
+ /* Thresholding length for Tx FIFOs in 32 bit DWORDs */
+ u32 tx_thr_length;
+#define dwc_param_tx_thr_length_default 64
+
+ /* Thresholding length for Rx FIFOs in 32 bit DWORDs */
+ u32 rx_thr_length;
+#define dwc_param_rx_thr_length_default 64
+
+};
+
+/*
+ * The core_if structure contains information needed to manage the
+ * DWC_otg controller acting in either host or device mode. It represents the
+ * programming view of the controller as a whole.
+ */
+struct core_if {
+ /* Parameters that define how the core should be configured. */
+ struct core_params *core_params;
+
+ /* Core Global registers starting at offset 000h. */
+ ulong core_global_regs;
+
+ /* Device-specific information */
+ struct device_if *dev_if;
+ /* Host-specific information */
+ struct dwc_host_if *host_if;
+
+ /*
+ * Set to 1 if the core PHY interface bits in USBCFG have been
+ * initialized.
+ */
+ u8 phy_init_done;
+
+ /*
+ * SRP Success flag, set by srp success interrupt in FS I2C mode
+ */
+ u8 srp_success;
+ u8 srp_timer_started;
+
+ /* Common configuration information */
+ /* Power and Clock Gating Control Register */
+ ulong pcgcctl;
+#define DWC_OTG_PCGCCTL_OFFSET 0xE00
+
+ /* Push/pop addresses for endpoints or host channels. */
+ ulong data_fifo[MAX_EPS_CHANNELS];
+#define DWC_OTG_DATA_FIFO_OFFSET 0x1000
+#define DWC_OTG_DATA_FIFO_SIZE 0x1000
+
+ /* Total RAM for FIFOs (Bytes) */
+ u16 total_fifo_size;
+ /* Size of Rx FIFO (Bytes) */
+ u16 rx_fifo_size;
+ /* Size of Non-periodic Tx FIFO (Bytes) */
+ u16 nperio_tx_fifo_size;
+
+ /* 1 if DMA is enabled, 0 otherwise. */
+ u8 dma_enable;
+
+ /* 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */
+ u8 en_multiple_tx_fifo;
+
+ /*
+ * Set to 1 if multiple packets of a high-bandwidth transfer is in
+ * process of being queued
+ */
+ u8 queuing_high_bandwidth;
+
+ /* Hardware Configuration -- stored here for convenience. */
+ ulong hwcfg1;
+ ulong hwcfg2;
+ ulong hwcfg3;
+ ulong hwcfg4;
+
+ /* HCD callbacks */
+ /* include/linux/usb/otg.h */
+
+ /* HCD callbacks */
+ struct cil_callbacks *hcd_cb;
+ /* PCD callbacks */
+ struct cil_callbacks *pcd_cb;
+
+ /* Device mode Periodic Tx FIFO Mask */
+ u32 p_tx_msk;
+ /* Device mode Periodic Tx FIFO Mask */
+ u32 tx_msk;
+
+ /* Features of various DWC implementation */
+ u32 features;
+
+ /* Added to support PLB DMA : phys-virt mapping */
+ resource_size_t phys_addr;
+
+ struct delayed_work usb_port_wakeup;
+ struct work_struct usb_port_otg;
+ struct otg_transceiver *xceiv;
+};
+
+/*
+ * The following functions support initialization of the CIL driver component
+ * and the DWC_otg controller.
+ */
+extern void dwc_otg_core_init(struct core_if *core_if);
+extern void init_fslspclksel(struct core_if *core_if);
+extern void dwc_otg_core_dev_init(struct core_if *core_if);
+extern void dwc_otg_enable_global_interrupts(struct core_if *core_if);
+extern void dwc_otg_disable_global_interrupts(struct core_if *core_if);
+extern void dwc_otg_enable_common_interrupts(struct core_if *core_if);
+
+/**
+ * This function Reads HPRT0 in preparation to modify. It keeps the WC bits 0
+ * so that if they are read as 1, they won't clear when you write it back
+ */
+static inline u32 dwc_otg_read_hprt0(struct core_if *core_if)
+{
+ u32 hprt0 = 0;
+ hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_CONN_DET_RW(hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0, 0);
+ hprt0 = DWC_HPRT0_PRT_OVRCURR_ACT_RW(hprt0, 0);
+ return hprt0;
+}
+
+/*
+ * The following functions support managing the DWC_otg controller in either
+ * device or host mode.
+ */
+extern void dwc_otg_read_packet(struct core_if *core_if, u8 * dest, u16 bytes);
+extern void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int _num);
+extern void dwc_otg_flush_rx_fifo(struct core_if *core_if);
+
+#define NP_TXFIFO_EMPTY -1
+#define MAX_NP_TXREQUEST_Q_SLOTS 8
+
+/**
+ * This function returns the Core Interrupt register.
+ */
+static inline u32 dwc_otg_read_core_intr(struct core_if *core_if)
+{
+ u32 global_regs = (u32) core_if->core_global_regs;
+ return dwc_reg_read(global_regs, DWC_GINTSTS) &
+ dwc_reg_read(global_regs, DWC_GINTMSK);
+}
+
+/**
+ * This function returns the mode of the operation, host or device.
+ */
+static inline u32 dwc_otg_mode(struct core_if *core_if)
+{
+ u32 global_regs = (u32) core_if->core_global_regs;
+ return dwc_reg_read(global_regs, DWC_GINTSTS) & 0x1;
+}
+
+static inline u8 dwc_otg_is_device_mode(struct core_if *core_if)
+{
+ return dwc_otg_mode(core_if) != DWC_HOST_MODE;
+}
+static inline u8 dwc_otg_is_host_mode(struct core_if *core_if)
+{
+ return dwc_otg_mode(core_if) == DWC_HOST_MODE;
+}
+
+extern int dwc_otg_handle_common_intr(struct core_if *core_if);
+
+/*
+ * DWC_otg CIL callback structure. This structure allows the HCD and PCD to
+ * register functions used for starting and stopping the PCD and HCD for role
+ * change on for a DRD.
+ */
+struct cil_callbacks {
+ /* Start function for role change */
+ int (*start) (void *_p);
+ /* Stop Function for role change */
+ int (*stop) (void *_p);
+ /* Disconnect Function for role change */
+ int (*disconnect) (void *_p);
+ /* Resume/Remote wakeup Function */
+ int (*resume_wakeup) (void *_p);
+ /* Suspend function */
+ int (*suspend) (void *_p);
+ /* Session Start (SRP) */
+ int (*session_start) (void *_p);
+ /* Pointer passed to start() and stop() */
+ void *p;
+};
+
+extern void dwc_otg_cil_register_pcd_callbacks(struct core_if *core_if,
+ struct cil_callbacks *cb,
+ void *p);
+extern void dwc_otg_cil_register_hcd_callbacks(struct core_if *core_if,
+ struct cil_callbacks *cb,
+ void *p);
+
+#define DWC_LIMITED_XFER 0x00000000
+#define DWC_DEVICE_ONLY 0x00000000
+#define DWC_HOST_ONLY 0x00000000
+
+#ifdef DWC_LIMITED_XFER_SIZE
+#undef DWC_LIMITED_XFER
+#define DWC_LIMITED_XFER 0x00000001
+#endif
+
+#ifdef CONFIG_DWC_DEVICE_ONLY
+#undef DWC_DEVICE_ONLY
+#define DWC_DEVICE_ONLY 0x00000002
+static inline void dwc_otg_hcd_remove(struct device *dev)
+{
+}
+static inline int dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev)
+{
+ return 0;
+}
+#else
+extern int dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev);
+extern void dwc_otg_hcd_remove(struct device *_dev);
+#endif
+
+#ifdef CONFIG_DWC_HOST_ONLY
+#undef DWC_HOST_ONLY
+#define DWC_HOST_ONLY 0x00000004
+static inline void dwc_otg_pcd_remove(struct device *dev)
+{
+}
+static inline int dwc_otg_pcd_init(struct device *dev)
+{
+ return 0;
+}
+#else
+extern void dwc_otg_pcd_remove(struct device *dev);
+extern int dwc_otg_pcd_init(struct device *dev);
+#endif
+
+extern void dwc_otg_cil_remove(struct core_if *core_if);
+extern struct core_if *dwc_otg_cil_init(const __iomem u32 * base,
+ struct core_params *params);
+
+static inline void dwc_set_feature(struct core_if *core_if)
+{
+ core_if->features = DWC_LIMITED_XFER | DWC_DEVICE_ONLY | DWC_HOST_ONLY;
+}
+
+static inline int dwc_has_feature(struct core_if *core_if,
+ unsigned long feature)
+{
+ return core_if->features & feature;
+}
+extern struct core_params dwc_otg_module_params;
+extern int check_parameters(struct core_if *core_if);
+#endif
diff --git a/drivers/usb/dwc/cil_intr.c b/drivers/usb/dwc/cil_intr.c
new file mode 100644
index 0000000..a42b0e6
--- /dev/null
+++ b/drivers/usb/dwc/cil_intr.c
@@ -0,0 +1,616 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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 Core Interface Layer provides basic services for accessing and
+ * managing the DWC_otg hardware. These services are used by both the
+ * Host Controller Driver and the Peripheral Controller Driver.
+ *
+ * This file contains the Common Interrupt handlers.
+ */
+#include <linux/delay.h>
+
+#include "cil.h"
+
+/**
+ * This function will log a debug message
+ */
+static int dwc_otg_handle_mode_mismatch_intr(struct core_if *core_if)
+{
+ u32 gintsts = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ pr_warning("Mode Mismatch Interrupt: currently in %s mode\n",
+ dwc_otg_mode(core_if) ? "Host" : "Device");
+
+ /* Clear interrupt */
+ gintsts |= DWC_INTSTS_MODE_MISMTC;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+
+ return 1;
+}
+
+/**
+ * Start the HCD. Helper function for using the HCD callbacks.
+ */
+static inline void hcd_start(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->start)
+ core_if->hcd_cb->start(core_if->hcd_cb->p);
+}
+
+/**
+ * Stop the HCD. Helper function for using the HCD callbacks.
+ */
+static inline void hcd_stop(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->stop)
+ core_if->hcd_cb->stop(core_if->hcd_cb->p);
+}
+
+/**
+ * Disconnect the HCD. Helper function for using the HCD callbacks.
+ */
+static inline void hcd_disconnect(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->disconnect)
+ core_if->hcd_cb->disconnect(core_if->hcd_cb->p);
+}
+
+/**
+ * Inform the HCD the a New Session has begun. Helper function for using the
+ * HCD callbacks.
+ */
+static inline void hcd_session_start(struct core_if *core_if)
+{
+ if (core_if->hcd_cb && core_if->hcd_cb->session_start)
+ core_if->hcd_cb->session_start(core_if->hcd_cb->p);
+}
+
+/**
+ * Start the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_start(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->start) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->start(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * Stop the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_stop(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->stop) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->stop(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * Suspend the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_suspend(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->suspend) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->suspend(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * Resume the PCD. Helper function for using the PCD callbacks.
+ */
+static inline void pcd_resume(struct core_if *core_if)
+{
+ if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) {
+ struct dwc_pcd *pcd;
+
+ pcd = (struct dwc_pcd *)core_if->pcd_cb->p;
+ spin_lock(&pcd->lock);
+ core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+ spin_unlock(&pcd->lock);
+ }
+}
+
+/**
+ * This function handles the OTG Interrupts. It reads the OTG
+ * Interrupt Register (GOTGINT) to determine what interrupt has
+ * occurred.
+ */
+static int dwc_otg_handle_otg_intr(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 gotgint;
+ u32 gotgctl;
+ u32 gintmsk;
+
+ gotgint = dwc_reg_read(global_regs, DWC_GOTGINT);
+ if (gotgint & DWC_GINT_SES_ENDDET) {
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ } else {
+ /*
+ * If not B_HOST and Device HNP still set. HNP did not
+ * succeed
+ */
+ if (gotgctl & DWC_GCTL_DEV_HNP_ENA)
+ pr_err("Device Not Connected / "
+ "Responding\n");
+ /*
+ * If Session End Detected the B-Cable has been
+ * disconnected. Reset PCD and Gadget driver to a
+ * clean state.
+ */
+ pcd_stop(core_if);
+ }
+ gotgctl = 0;
+ gotgctl |= DWC_GCTL_DEV_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+ }
+ if (gotgint & DWC_GINT_SES_REQSUC) {
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (gotgctl & DWC_GCTL_SES_REQ_SUCCESS) {
+ if (core_if->core_params->phy_type ==
+ DWC_PHY_TYPE_PARAM_FS &&
+ core_if->core_params->i2c_enable) {
+ core_if->srp_success = 1;
+ } else {
+ pcd_resume(core_if);
+
+ /* Clear Session Request */
+ gotgctl = 0;
+ gotgctl |= DWC_GCTL_SES_REQ;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL,
+ gotgctl, 0);
+ }
+ }
+ }
+ if (gotgint & DWC_GINT_HST_NEGSUC) {
+ /*
+ * Print statements during the HNP interrupt handling can cause
+ * it to fail.
+ */
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (gotgctl & DWC_GCTL_HOST_NEG_SUCCES) {
+ if (dwc_otg_is_host_mode(core_if)) {
+ core_if->xceiv->state = OTG_STATE_B_HOST;
+ /*
+ * Need to disable SOF interrupt immediately.
+ * When switching from device to host, the PCD
+ * interrupt handler won't handle the
+ * interrupt if host mode is already set. The
+ * HCD interrupt handler won't get called if
+ * the HCD state is HALT. This means that the
+ * interrupt does not get handled and Linux
+ * complains loudly.
+ */
+ gintmsk = 0;
+ gintmsk |= DWC_INTMSK_STRT_OF_FRM;
+ dwc_reg_modify(global_regs, DWC_GINTMSK,
+ gintmsk, 0);
+ pcd_stop(core_if);
+ /* Initialize the Core for Host mode. */
+ hcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_HOST;
+ }
+ } else {
+ gotgctl = 0;
+ gotgctl |= DWC_GCTL_HNP_REQ;
+ gotgctl |= DWC_GCTL_DEV_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+
+ pr_err("Device Not Connected / Responding\n");
+ }
+ }
+ if (gotgint & DWC_GINT_HST_NEGDET) {
+ /*
+ * The disconnect interrupt is set at the same time as
+ * Host Negotiation Detected. During the mode
+ * switch all interrupts are cleared so the disconnect
+ * interrupt handler will not get executed.
+ */
+ if (dwc_otg_is_device_mode(core_if)) {
+ hcd_disconnect(core_if);
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_A_PERIPHERAL;
+ } else {
+ /*
+ * Need to disable SOF interrupt immediately. When
+ * switching from device to host, the PCD interrupt
+ * handler won't handle the interrupt if host mode is
+ * already set. The HCD interrupt handler won't get
+ * called if the HCD state is HALT. This means that
+ * the interrupt does not get handled and Linux
+ * complains loudly.
+ */
+ gintmsk = 0;
+ gintmsk |= DWC_INTMSK_STRT_OF_FRM;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, gintmsk, 0);
+ pcd_stop(core_if);
+ hcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ }
+ }
+ if (gotgint & DWC_GINT_DEVTOUT)
+ pr_info(" ++OTG Interrupt: A-Device Timeout " "Change++\n");
+ if (gotgint & DWC_GINT_DEBDONE)
+ pr_info(" ++OTG Interrupt: Debounce Done++\n");
+
+ /* Clear GOTGINT */
+ dwc_reg_write(global_regs, DWC_GOTGINT, gotgint);
+ return 1;
+}
+
+/*
+ * Wakeup Workqueue implementation
+ */
+static void port_otg_wqfunc(struct work_struct *work)
+{
+ struct core_if *core_if = container_of(work, struct core_if,
+ usb_port_otg);
+ ulong global_regs = core_if->core_global_regs;
+ u32 count = 0;
+ u32 gotgctl;
+
+ pr_info("%s\n", __func__);
+
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+ if (gotgctl & DWC_GCTL_CONN_ID_STATUS) {
+ /*
+ * B-Device connector (device mode) wait for switch to device
+ * mode.
+ */
+ while (!dwc_otg_is_device_mode(core_if) && ++count <= 10000) {
+ pr_info("Waiting for Peripheral Mode, "
+ "Mode=%s\n", dwc_otg_is_host_mode(core_if) ?
+ "Host" : "Peripheral");
+ msleep(100);
+ }
+ BUG_ON(count > 10000);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ dwc_otg_core_init(core_if);
+ dwc_otg_enable_global_interrupts(core_if);
+ pcd_start(core_if);
+ } else {
+ /*
+ * A-Device connector (host mode) wait for switch to host
+ * mode.
+ */
+ while (!dwc_otg_is_host_mode(core_if) && ++count <= 10000) {
+ pr_info("Waiting for Host Mode, Mode=%s\n",
+ dwc_otg_is_host_mode(core_if) ?
+ "Host" : "Peripheral");
+ msleep(100);
+ }
+ BUG_ON(count > 10000);
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ dwc_otg_core_init(core_if);
+ dwc_otg_enable_global_interrupts(core_if);
+ hcd_start(core_if);
+ }
+}
+
+/**
+ * This function handles the Connector ID Status Change Interrupt. It
+ * reads the OTG Interrupt Register (GOTCTL) to determine whether this
+ * is a Device to Host Mode transition or a Host Mode to Device
+ * Transition.
+ *
+ * This only occurs when the cable is connected/removed from the PHY
+ * connector.
+ */
+static int dwc_otg_handle_conn_id_status_change_intr(struct core_if *core_if)
+{
+ u32 gintsts = 0;
+ u32 gintmsk = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ /*
+ * Need to disable SOF interrupt immediately. If switching from device
+ * to host, the PCD interrupt handler won't handle the interrupt if
+ * host mode is already set. The HCD interrupt handler won't get
+ * called if the HCD state is HALT. This means that the interrupt does
+ * not get handled and Linux complains loudly.
+ */
+ gintmsk |= DWC_INTSTS_STRT_OF_FRM;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, gintmsk, 0);
+
+ INIT_WORK(&core_if->usb_port_otg, port_otg_wqfunc);
+ schedule_work(&core_if->usb_port_otg);
+
+ /* Set flag and clear interrupt */
+ gintsts |= DWC_INTSTS_CON_ID_STS_CHG;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that a device is initiating the Session
+ * Request Protocol to request the host to turn on bus power so a new
+ * session can begin. The handler responds by turning on bus power. If
+ * the DWC_otg controller is in low power mode, the handler brings the
+ * controller out of low power mode before turning on bus power.
+ */
+static int dwc_otg_handle_session_req_intr(struct core_if *core_if)
+{
+ u32 gintsts = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) {
+ u32 hprt0;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ pr_info("SRP: Device mode\n");
+ } else {
+ pr_info("SRP: Host mode\n");
+
+ /* Turn on the port power bit. */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /*
+ * Start the Connection timer.
+ * A message can be displayed,
+ * if connect does not occur within 10 seconds.
+ */
+ hcd_session_start(core_if);
+ }
+ }
+ /* Clear interrupt */
+ gintsts |= DWC_INTSTS_NEW_SES_DET;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that the DWC_otg controller has detected a
+ * resume or remote wakeup sequence. If the DWC_otg controller is in
+ * low power mode, the handler must brings the controller out of low
+ * power mode. The controller automatically begins resume
+ * signaling. The handler schedules a time to stop resume signaling.
+ */
+static int dwc_otg_handle_wakeup_detected_intr(struct core_if *core_if)
+{
+ u32 gintsts = 0;
+ struct device_if *dev_if = core_if->dev_if;
+ ulong global_regs = core_if->core_global_regs;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ u32 dctl = 0;
+
+ /* Clear the Remote Wakeup Signalling */
+ dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1);
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DCTL, dctl, 0);
+
+ if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup)
+ core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+ } else {
+ u32 pcgcctl = 0;
+
+ /* Restart the Phy Clock */
+ pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl);
+ dwc_reg_modify(core_if->pcgcctl, 0, pcgcctl, 0);
+ schedule_delayed_work(&core_if->usb_port_wakeup, 10);
+ }
+
+ /* Clear interrupt */
+ gintsts |= DWC_INTSTS_WKP;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that a device has been disconnected from
+ * the root port.
+ */
+static int dwc_otg_handle_disconnect_intr(struct core_if *core_if)
+{
+ u32 gintsts = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) {
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ hcd_disconnect(core_if);
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ } else if (dwc_otg_is_device_mode(core_if)) {
+ u32 gotgctl;
+
+ gotgctl = dwc_reg_read(global_regs, DWC_GOTGCTL);
+
+ /*
+ * If HNP is in process, do nothing.
+ * The OTG "Host Negotiation Detected"
+ * interrupt will do the mode switch.
+ * Otherwise, since we are in device mode,
+ * disconnect and stop the HCD,
+ * then start the PCD.
+ */
+ if ((gotgctl) & DWC_GCTL_DEV_HNP_ENA) {
+ hcd_disconnect(core_if);
+ pcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_B_PERIPHERAL;
+ }
+ } else if (core_if->xceiv->state == OTG_STATE_A_HOST) {
+ /* A-Cable still connected but device disconnected. */
+ hcd_disconnect(core_if);
+ }
+ }
+ gintsts |= DWC_INTSTS_SES_DISCON_DET;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that SUSPEND state has been detected on
+ * the USB.
+ *
+ * For HNP the USB Suspend interrupt signals the change from
+ * "a_peripheral" to "a_host".
+ *
+ * When power management is enabled the core will be put in low power
+ * mode.
+ */
+static int dwc_otg_handle_usb_suspend_intr(struct core_if *core_if)
+{
+ u32 dsts = 0;
+ u32 gintsts = 0;
+ ulong global_regs = core_if->core_global_regs;
+ struct device_if *dev_if = core_if->dev_if;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ /*
+ * Check the Device status register to determine if the Suspend
+ * state is active.
+ */
+ dsts = dwc_reg_read(dev_if->dev_global_regs, DWC_DSTS);
+ /* PCD callback for suspend. */
+ pcd_suspend(core_if);
+ } else {
+ if (core_if->xceiv->state == OTG_STATE_A_PERIPHERAL) {
+ /* Clear the a_peripheral flag, back to a_host. */
+ pcd_stop(core_if);
+ hcd_start(core_if);
+ core_if->xceiv->state = OTG_STATE_A_HOST;
+ }
+ }
+
+ /* Clear interrupt */
+ gintsts |= DWC_INTMSK_USB_SUSP;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This function returns the Core Interrupt register.
+ *
+ * Although the Host Port interrupt (portintr) is documented as host mode
+ * only, it appears to occur in device mode when Port Enable / Disable Changed
+ * bit in HPRT0 is set. The code in dwc_otg_handle_common_intr checks if in
+ * device mode and just clears the interrupt.
+ */
+static inline u32 dwc_otg_read_common_intr(struct core_if *core_if)
+{
+ u32 gintsts;
+ u32 gintmsk;
+ u32 gintmsk_common = 0;
+ ulong global_regs = core_if->core_global_regs;
+
+ gintmsk_common |= DWC_INTMSK_WKP;
+ gintmsk_common |= DWC_INTMSK_NEW_SES_DET;
+ gintmsk_common |= DWC_INTMSK_CON_ID_STS_CHG;
+ gintmsk_common |= DWC_INTMSK_OTG;
+ gintmsk_common |= DWC_INTMSK_MODE_MISMTC;
+ gintmsk_common |= DWC_INTMSK_SES_DISCON_DET;
+ gintmsk_common |= DWC_INTMSK_USB_SUSP;
+ gintmsk_common |= DWC_INTMSK_HST_PORT;
+
+ gintsts = dwc_reg_read(global_regs, DWC_GINTSTS);
+ gintmsk = dwc_reg_read(global_regs, DWC_GINTMSK);
+
+ return (gintsts & gintmsk) & gintmsk_common;
+}
+
+/**
+ * Common interrupt handler.
+ *
+ * The common interrupts are those that occur in both Host and Device mode.
+ * This handler handles the following interrupts:
+ * - Mode Mismatch Interrupt
+ * - Disconnect Interrupt
+ * - OTG Interrupt
+ * - Connector ID Status Change Interrupt
+ * - Session Request Interrupt.
+ * - Resume / Remote Wakeup Detected Interrupt.
+ *
+ * - Host Port Interrupt. Although this interrupt is documented as only
+ * occurring in Host mode, it also occurs in Device mode when Port Enable /
+ * Disable Changed bit in HPRT0 is set. If it is seen here, while in Device
+ * mode, the interrupt is just cleared.
+ *
+ */
+int dwc_otg_handle_common_intr(struct core_if *core_if)
+{
+ int retval = 0;
+ u32 gintsts;
+ ulong global_regs = core_if->core_global_regs;
+
+ gintsts = dwc_otg_read_common_intr(core_if);
+
+ if (gintsts & DWC_INTSTS_MODE_MISMTC)
+ retval |= dwc_otg_handle_mode_mismatch_intr(core_if);
+ if (gintsts & DWC_INTSTS_OTG)
+ retval |= dwc_otg_handle_otg_intr(core_if);
+ if (gintsts & DWC_INTSTS_CON_ID_STS_CHG)
+ retval |= dwc_otg_handle_conn_id_status_change_intr(core_if);
+ if (gintsts & DWC_INTSTS_SES_DISCON_DET)
+ retval |= dwc_otg_handle_disconnect_intr(core_if);
+ if (gintsts & DWC_INTSTS_NEW_SES_DET)
+ retval |= dwc_otg_handle_session_req_intr(core_if);
+ if (gintsts & DWC_INTSTS_WKP)
+ retval |= dwc_otg_handle_wakeup_detected_intr(core_if);
+ if (gintsts & DWC_INTMSK_USB_SUSP)
+ retval |= dwc_otg_handle_usb_suspend_intr(core_if);
+
+ if ((gintsts & DWC_INTSTS_HST_PORT) &&
+ dwc_otg_is_device_mode(core_if)) {
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_HST_PORT;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+ retval |= 1;
+ pr_info("RECEIVED PORTINT while in Device mode\n");
+ }
+
+ return retval;
+}
diff --git a/drivers/usb/dwc/driver.h b/drivers/usb/dwc/driver.h
new file mode 100644
index 0000000..a86532b
--- /dev/null
+++ b/drivers/usb/dwc/driver.h
@@ -0,0 +1,76 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#if !defined(__DWC_OTG_DRIVER_H__)
+#define __DWC_OTG_DRIVER_H__
+
+/*
+ * This file contains the interface to the Linux driver.
+ */
+#include "cil.h"
+
+/*
+ * This structure is a wrapper that encapsulates the driver components used to
+ * manage a single DWC_otg controller.
+ */
+struct dwc_otg_device {
+ /* Base address returned from ioremap() */
+ __iomem void *base;
+
+ /* Pointer to the core interface structure. */
+ struct core_if *core_if;
+
+ /* Pointer to the PCD structure. */
+ struct dwc_pcd *pcd;
+
+ /* Pointer to the HCD structure. */
+ struct dwc_hcd *hcd;
+
+ /* Flag to indicate whether the common IRQ handler is installed. */
+ u8 common_irq_installed;
+
+ /* Interrupt request number. */
+ unsigned int irq;
+
+ /*
+ * Physical address of Control and Status registers, used by
+ * release_mem_region().
+ */
+ resource_size_t phys_addr;
+
+ /* Length of memory region, used by release_mem_region(). */
+ unsigned long base_len;
+};
+#endif
diff --git a/drivers/usb/dwc/hcd.c b/drivers/usb/dwc/hcd.c
new file mode 100644
index 0000000..2718b95
--- /dev/null
+++ b/drivers/usb/dwc/hcd.c
@@ -0,0 +1,2473 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+/*
+ * This file contains the implementation of the HCD. In Linux, the HCD
+ * implements the hc_driver API.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/dma-mapping.h>
+
+#include "hcd.h"
+
+static const char dwc_otg_hcd_name[] = "dwc_otg_hcd";
+
+/**
+ * Clears the transfer state for a host channel. This function is normally
+ * called after a transfer is done and the host channel is being released. It
+ * clears the channel interrupt enables and any unhandled channel interrupt
+ * conditions.
+ */
+void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc)
+{
+ ulong regs;
+
+ hc->xfer_started = 0;
+ regs = core_if->host_if->hc_regs[hc->hc_num];
+ dwc_reg_write(regs, DWC_HCINTMSK, 0);
+ dwc_reg_write(regs, DWC_HCINT, 0xFFFFFFFF);
+}
+
+/**
+ * This function enables the Host mode interrupts.
+ */
+static void dwc_otg_enable_host_interrupts(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /* Disable all interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTMSK, 0);
+
+ /* Clear any pending interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /*
+ * Enable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * host mode.
+ *
+ * This function flushes the Tx and Rx FIFOs and it flushes any entries in the
+ * request queues. Host channels are reset to ensure that they are ready for
+ * performing transfers.
+ */
+static void dwc_otg_core_host_init(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ struct dwc_host_if *host_if = core_if->host_if;
+ struct core_params *params = core_if->core_params;
+ u32 hprt0 = 0;
+ u32 nptxfifosize = 0;
+ u32 ptxfifosize = 0;
+ u32 i;
+ u32 hcchar;
+ ulong hcfg;
+ ulong hc_regs;
+ int num_channels;
+ u32 gotgctl = 0;
+
+ /* Restart the Phy Clock */
+ dwc_reg_write(core_if->pcgcctl, 0, 0);
+
+ /* Initialize Host Configuration Register */
+ init_fslspclksel(core_if);
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) {
+ hcfg = dwc_reg_read(host_if->host_global_regs, DWC_HCFG);
+ hcfg = DWC_HCFG_FSLSUPP_RW(hcfg, 1);
+ dwc_reg_write(host_if->host_global_regs, DWC_HCFG, hcfg);
+ }
+
+ /* Configure data FIFO sizes */
+ if (DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2)
+ && params->enable_dynamic_fifo) {
+ /* Rx FIFO */
+ dwc_reg_write(global_regs, DWC_GRXFSIZ,
+ params->host_rx_fifo_size);
+
+ /* Non-periodic Tx FIFO */
+ nptxfifosize = DWC_RX_FIFO_DEPTH_WR(nptxfifosize,
+ params->
+ host_nperio_tx_fifo_size);
+ nptxfifosize =
+ DWC_RX_FIFO_START_ADDR_WR(nptxfifosize,
+ params->host_rx_fifo_size);
+ dwc_reg_write(global_regs, DWC_GNPTXFSIZ, nptxfifosize);
+
+ /* Periodic Tx FIFO */
+ ptxfifosize = DWC_RX_FIFO_DEPTH_WR(ptxfifosize,
+ params->
+ host_perio_tx_fifo_size);
+ ptxfifosize =
+ DWC_RX_FIFO_START_ADDR_WR(ptxfifosize,
+ (DWC_RX_FIFO_START_ADDR_RD
+ (nptxfifosize) +
+ DWC_RX_FIFO_DEPTH_RD
+ (nptxfifosize)));
+ dwc_reg_write(global_regs, DWC_HPTXFSIZ, ptxfifosize);
+ }
+
+ /* Clear Host Set HNP Enable in the OTG Control Register */
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+
+ /* Make sure the FIFOs are flushed. */
+ dwc_otg_flush_tx_fifo(core_if, DWC_GRSTCTL_TXFNUM_ALL);
+ dwc_otg_flush_rx_fifo(core_if);
+
+ /* Flush out any leftover queued requests. */
+ num_channels = core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+ }
+
+ /* Halt all channels to put them into a known state. */
+ for (i = 0; i < num_channels; i++) {
+ int count = 0;
+
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ do {
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (++count > 200) {
+ pr_err("%s: Unable to clear halt on "
+ "channel %d\n", __func__, i);
+ break;
+ }
+ udelay(100);
+ } while (DWC_HCCHAR_ENA_RD(hcchar));
+ }
+
+ /* Turn on the vbus power. */
+ pr_info("Init: Port Power? op_state=%s\n",
+ otg_state_string(core_if->xceiv->state));
+
+ if (core_if->xceiv->state == OTG_STATE_A_HOST) {
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ pr_info("Init: Power Port (%d)\n", DWC_HPRT0_PRT_PWR_RD(hprt0));
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0) == 0) {
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(host_if->hprt0, 0, hprt0);
+ }
+ }
+ dwc_otg_enable_host_interrupts(core_if);
+}
+
+/**
+ * Initializes dynamic portions of the DWC_otg HCD state.
+ */
+static void hcd_reinit(struct dwc_hcd *hcd)
+{
+ struct list_head *item;
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+
+ hcd->flags.d32 = 0;
+ hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
+ hcd->available_host_channels = hcd->core_if->core_params->host_channels;
+
+ /*
+ * Put all channels in the free channel list and clean up channel
+ * states.
+ */
+ item = hcd->free_hc_list.next;
+ while (item != &hcd->free_hc_list) {
+ list_del(item);
+ item = hcd->free_hc_list.next;
+ }
+
+ num_channels = hcd->core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ list_add_tail(&channel->hc_list_entry, &hcd->free_hc_list);
+ dwc_otg_hc_cleanup(hcd->core_if, channel);
+ }
+
+ /* Initialize the DWC core for host mode operation. */
+ dwc_otg_core_host_init(hcd->core_if);
+}
+
+/* Gets the dwc_hcd from a struct usb_hcd */
+static inline struct dwc_hcd *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd)
+{
+ return (struct dwc_hcd *)hcd->hcd_priv;
+}
+
+/**
+ * Initializes the DWC_otg controller and its root hub and prepares it for host
+ * mode operation. Activates the root port. Returns 0 on success and a negative
+ * error code on failure.
+*/
+static int dwc_otg_hcd_start(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct usb_bus *bus = hcd_to_bus(hcd);
+
+ hcd->state = HC_STATE_RUNNING;
+
+ /* Inform the HUB driver to resume. */
+ if (bus->root_hub)
+ usb_hcd_resume_root_hub(hcd);
+
+ hcd_reinit(dwc_hcd);
+ return 0;
+}
+
+/**
+ * Work queue function for starting the HCD when A-Cable is connected.
+ * The dwc_otg_hcd_start() must be called in a process context.
+ */
+static void hcd_start_func(struct work_struct *work)
+{
+ struct dwc_hcd *priv = container_of(work, struct dwc_hcd, start_work);
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)priv->_p;
+
+ if (usb_hcd)
+ dwc_otg_hcd_start(usb_hcd);
+}
+
+/**
+ * HCD Callback function for starting the HCD when A-Cable is
+ * connected.
+ */
+static int dwc_otg_hcd_start_cb(void *_p)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = dwc_hcd->core_if;
+ u32 hprt0;
+
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ /*
+ * Reset the port. During a HNP mode switch the reset
+ * needs to occur within 1ms and have a duration of at
+ * least 50ms.
+ */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ ((struct usb_hcd *)_p)->self.is_b_host = 1;
+ } else {
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ }
+
+ /* Need to start the HCD in a non-interrupt context. */
+ dwc_hcd->_p = _p;
+ schedule_work(&dwc_hcd->start_work);
+ return 1;
+}
+
+/**
+ * This function disables the Host Mode interrupts.
+ */
+static void dwc_otg_disable_host_interrupts(struct core_if *core_if)
+{
+ u32 global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /*
+ * Disable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ intr_mask |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0);
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+static void dwc_otg_hcd_stop(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hprt0 = 0;
+
+ /* Turn off all host-specific interrupts. */
+ spin_lock(&dwc_hcd->lock);
+ dwc_otg_disable_host_interrupts(dwc_hcd->core_if);
+ spin_unlock(&dwc_hcd->lock);
+
+ /*
+ * The root hub should be disconnected before this function is called.
+ * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue)
+ * and the QH lists (via ..._hcd_endpoint_disable).
+ */
+
+ /* Turn off the vbus power */
+ pr_info("PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(dwc_hcd->core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * HCD Callback function for stopping the HCD.
+ */
+static int dwc_otg_hcd_stop_cb(void *_p)
+{
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)_p;
+
+ dwc_otg_hcd_stop(usb_hcd);
+ return 1;
+}
+
+static void del_timers(struct dwc_hcd *hcd)
+{
+ del_timer_sync(&hcd->conn_timer);
+}
+
+/**
+ * Processes all the URBs in a single list of QHs. Completes them with
+ * -ETIMEDOUT and frees the QTD.
+ */
+static void kill_urbs_in_qh_list(struct dwc_hcd *hcd, struct list_head *qh_list)
+{
+ struct list_head *qh_item, *q;
+
+ qh_item = qh_list->next;
+ list_for_each_safe(qh_item, q, qh_list) {
+ struct dwc_qh *qh;
+ struct list_head *qtd_item;
+ struct dwc_qtd *qtd;
+
+ qh = list_entry(qh_item, struct dwc_qh, qh_list_entry);
+ qtd_item = qh->qtd_list.next;
+ qtd = list_entry(qtd_item, struct dwc_qtd, qtd_list_entry);
+ if (qtd->urb != NULL) {
+ spin_lock(&hcd->lock);
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -ETIMEDOUT);
+ dwc_otg_hcd_qtd_remove_and_free(qtd);
+ spin_unlock(&hcd->lock);
+ }
+ }
+}
+
+/**
+ * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic
+ * and periodic schedules. The QTD associated with each URB is removed from
+ * the schedule and freed. This function may be called when a disconnect is
+ * detected or when the HCD is being stopped.
+ */
+static void kill_all_urbs(struct dwc_hcd *hcd)
+{
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_deferred);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_disconnect_cb(void *_p)
+{
+ u32 intr;
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = hcd->core_if;
+
+ /* Set status flags for the hub driver. */
+ hcd->flags.b.port_connect_status_change = 1;
+ hcd->flags.b.port_connect_status = 0;
+
+ /*
+ * Shutdown any transfers in process by clearing the Tx FIFO Empty
+ * interrupt mask and status bits and disabling subsequent host
+ * channel interrupts.
+ */
+ intr = 0;
+ intr |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ intr |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr |= DWC_INTMSK_HST_CHAN;
+ spin_lock(&hcd->lock);
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr, 0);
+ dwc_reg_modify(gintsts_reg(hcd), 0, intr, 0);
+ spin_unlock(&hcd->lock);
+
+ del_timers(hcd);
+
+ /*
+ * Turn off the vbus power only if the core has transitioned to device
+ * mode. If still in host mode, need to keep power on to detect a
+ * reconnection.
+ */
+ if (dwc_otg_is_device_mode(core_if)) {
+ if (core_if->xceiv->state != OTG_STATE_A_SUSPEND) {
+ u32 hprt0 = 0;
+
+ pr_info("Disconnect: PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+ dwc_otg_disable_host_interrupts(core_if);
+ }
+
+ /* Respond with an error status to all URBs in the schedule. */
+ kill_all_urbs(hcd);
+ if (dwc_otg_is_host_mode(core_if)) {
+ /* Clean up any host channels that were in use. */
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ ulong regs;
+ u32 hcchar;
+
+ num_channels = core_if->core_params->host_channels;
+ if (!core_if->dma_enable) {
+ /* Flush out any channel requests in slave mode. */
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs =
+ core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hcchar =
+ DWC_HCCHAR_ENA_RW(hcchar,
+ 0);
+ hcchar =
+ DWC_HCCHAR_DIS_RW(hcchar,
+ 1);
+ hcchar =
+ DWC_HCCHAR_EPDIR_RW(hcchar,
+ 0);
+ dwc_reg_write(regs, DWC_HCCHAR,
+ hcchar);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ /* Halt the channel. */
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ dwc_reg_write(regs, DWC_HCCHAR, hcchar);
+ }
+ dwc_otg_hc_cleanup(core_if, channel);
+ list_add_tail(&channel->hc_list_entry,
+ &hcd->free_hc_list);
+ }
+ }
+ }
+
+ /*
+ * A disconnect will end the session so the B-Device is no
+ * longer a B-host.
+ */
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ return 1;
+}
+
+/**
+ * Connection timeout function. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds.
+ */
+static void dwc_otg_hcd_connect_timeout(unsigned long _ptr)
+{
+ pr_info("Connect Timeout\n");
+ pr_err("Device Not Connected/Responding\n");
+}
+
+/**
+ * Start the connection timer. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds. The
+ * timer is deleted if a port connect interrupt occurs before the
+ * timer expires.
+ */
+static void dwc_otg_hcd_start_connect_timer(struct dwc_hcd *hcd)
+{
+ init_timer(&hcd->conn_timer);
+ hcd->conn_timer.function = dwc_otg_hcd_connect_timeout;
+ hcd->conn_timer.data = (unsigned long)0;
+ hcd->conn_timer.expires = jiffies + (HZ * 10);
+ add_timer(&hcd->conn_timer);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_session_start_cb(void *_p)
+{
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+
+ dwc_otg_hcd_start_connect_timer(hcd);
+ return 1;
+}
+
+/* HCD Callback structure for handling mode switching. */
+static struct cil_callbacks hcd_cil_callbacks = {
+ .start = dwc_otg_hcd_start_cb,
+ .stop = dwc_otg_hcd_stop_cb,
+ .disconnect = dwc_otg_hcd_disconnect_cb,
+ .session_start = dwc_otg_hcd_session_start_cb,
+ .p = NULL,
+};
+
+/*
+ * Reset Workqueue implementation
+ */
+static void port_reset_wqfunc(struct work_struct *work)
+{
+ struct dwc_hcd *hcd = container_of(work, struct dwc_hcd,
+ usb_port_reset);
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ pr_info("%s\n", __func__);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ hcd->flags.b.port_reset_change = 1;
+ spin_unlock_irqrestore(&hcd->lock, flags);
+}
+
+/*
+ * Wakeup Workqueue implementation
+ */
+static void port_wakeup_wqfunc(struct work_struct *work)
+{
+ struct core_if *core_if = container_of(to_delayed_work(work),
+ struct core_if, usb_port_wakeup);
+ u32 hprt0;
+
+ pr_info("%s\n", __func__);
+ /* Now wait for 70 ms. */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ msleep(70);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * Starts processing a USB transfer request specified by a USB Request Block
+ * (URB). mem_flags indicates the type of memory allocation to use while
+ * processing this URB.
+ */
+static int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t _mem_flags)
+{
+ int retval;
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct dwc_qtd *qtd;
+
+ if (!dwc_hcd->flags.b.port_connect_status) {
+ /* No longer connected. */
+ retval = -ENODEV;
+ goto err_enq;
+ }
+
+ qtd = dwc_otg_hcd_qtd_create(urb, _mem_flags);
+ if (!qtd) {
+ pr_err("DWC OTG HCD URB Enqueue failed creating " "QTD\n");
+ retval = -ENOMEM;
+ goto err_enq;
+ }
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ retval = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (unlikely(retval))
+ goto fail;
+
+ retval = dwc_otg_hcd_qtd_add(qtd, dwc_hcd);
+ if (retval < 0) {
+ pr_err("DWC OTG HCD URB Enqueue failed adding QTD. "
+ "Error status %d\n", retval);
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ goto fail;
+ }
+
+fail:
+ if (retval)
+ dwc_otg_hcd_qtd_free(qtd);
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+err_enq:
+
+ return retval;
+}
+
+/**
+ * Attempts to halt a host channel. This function should only be called in
+ * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under
+ * normal circumstances in DMA mode, the controller halts the channel when the
+ * transfer is complete or a condition occurs that requires application
+ * intervention.
+ *
+ * In slave mode, checks for a free request queue entry, then sets the Channel
+ * Enable and Channel Disable bits of the Host Channel Characteristics
+ * register of the specified channel to intiate the halt. If there is no free
+ * request queue entry, sets only the Channel Disable bit of the HCCHARn
+ * register to flush requests for this channel. In the latter case, sets a
+ * flag to indicate that the host channel needs to be halted when a request
+ * queue slot is open.
+ *
+ * In DMA mode, always sets the Channel Enable and Channel Disable bits of the
+ * HCCHARn register. The controller ensures there is space in the request
+ * queue before submitting the halt request.
+ *
+ * Some time may elapse before the core flushes any posted requests for this
+ * host channel and halts. The Channel Halted interrupt handler completes the
+ * deactivation of the host channel.
+ */
+void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status hlt_sts)
+{
+ u32 nptxsts;
+ u32 hptxsts = 0;
+ u32 hcchar;
+ ulong hc_regs;
+ ulong global_regs = core_if->core_global_regs;
+ ulong host_global_regs;
+
+ hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ host_global_regs = core_if->host_if->host_global_regs;
+
+ WARN_ON(hlt_sts == DWC_OTG_HC_XFER_NO_HALT_STATUS);
+
+ if (hlt_sts == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+ hlt_sts == DWC_OTG_HC_XFER_AHB_ERR) {
+ /*
+ * Disable all channel interrupts except Ch Halted. The QTD
+ * and QH state associated with this transfer has been cleared
+ * (in the case of URB_DEQUEUE), so the channel needs to be
+ * shut down carefully to prevent crashes.
+ */
+ u32 hcintmsk;
+ hcintmsk = 0;
+ hcintmsk = DWC_HCINTMSK_CHAN_HALTED_RW(hcintmsk, 1);
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hcintmsk);
+
+ /*
+ * Make sure no other interrupts besides halt are currently
+ * pending. Handling another interrupt could cause a crash due
+ * to the QTD and QH state.
+ */
+ dwc_reg_write(hc_regs, DWC_HCINT, ~hcintmsk);
+
+ /*
+ * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR
+ * even if the channel was already halted for some other reason.
+ */
+ hc->halt_status = hlt_sts;
+
+ /*
+ * If the channel is not enabled, the channel is either already
+ * halted or it hasn't started yet. In DMA mode, the transfer
+ * may halt if it finishes normally or a condition occurs that
+ * requires driver intervention. Don't want to halt the channel
+ * again. In either Slave or DMA mode, it's possible that the
+ * transfer has been assigned to a channel, but not started yet
+ * when an URB is dequeued. Don't want to halt a channel that
+ * hasn't started yet.
+ */
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (!DWC_HCCHAR_ENA_RD(hcchar))
+ return;
+ }
+
+ if (hc->halt_pending)
+ /*
+ * A halt has already been issued for this channel. This might
+ * happen when a transfer is aborted by a higher level in
+ * the stack.
+ */
+ return;
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ if (!core_if->dma_enable) {
+ /* Check for space in the request queue to issue the halt. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+ hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+ nptxsts = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+
+ if (!DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(nptxsts))
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ } else {
+ hptxsts =
+ dwc_reg_read(host_global_regs, DWC_HPTXSTS);
+
+ if (!DWC_HPTXSTS_PTXSPC_AVAIL_RD(hptxsts) ||
+ core_if->queuing_high_bandwidth)
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->halt_status = hlt_sts;
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hc->halt_pending = 1;
+ hc->halt_on_queue = 0;
+ } else {
+ hc->halt_on_queue = 1;
+ }
+}
+
+/**
+ * Aborts/cancels a USB transfer request. Always returns 0 to indicate
+ * success.
+ */
+static int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+ int status)
+{
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_qtd *urb_qtd;
+ struct dwc_qh *qh;
+ int retval;
+
+ urb_qtd = (struct dwc_qtd *)urb->hcpriv;
+ if (!urb_qtd)
+ return -EINVAL;
+ qh = (struct dwc_qh *)urb_qtd->qtd_qh_ptr;
+ if (!qh)
+ return -EINVAL;
+
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ retval = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (retval) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+ }
+
+ if (urb_qtd == qh->qtd_in_process) {
+ /* The QTD is in process (it has been assigned to a channel). */
+ if (dwc_hcd->flags.b.port_connect_status) {
+ /*
+ * If still connected (i.e. in host mode), halt the
+ * channel so it can be used for other transfers. If
+ * no longer connected, the host registers can't be
+ * written to halt the channel since the core is in
+ * device mode.
+ */
+ dwc_otg_hc_halt(dwc_hcd->core_if, qh->channel,
+ DWC_OTG_HC_XFER_URB_DEQUEUE);
+ }
+ }
+
+ /*
+ * Free the QTD and clean up the associated QH. Leave the QH in the
+ * schedule if it has any remaining QTDs.
+ */
+ dwc_otg_hcd_qtd_remove_and_free(urb_qtd);
+ if (qh && urb_qtd == qh->qtd_in_process) {
+ dwc_otg_hcd_qh_deactivate(dwc_hcd, qh, 0);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ } else if (qh && list_empty(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(dwc_hcd, qh);
+ }
+
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+
+ /* Higher layer software sets URB status. */
+ usb_hcd_giveback_urb(hcd, urb, status);
+
+ return 0;
+}
+
+/* Remove and free a QH */
+static inline void dwc_otg_hcd_qh_remove_and_free(struct dwc_hcd *hcd,
+ struct dwc_qh *qh)
+{
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ dwc_otg_hcd_qh_free(qh);
+}
+
+static void qh_list_free(struct dwc_hcd *hcd, struct list_head *_qh_list)
+{
+ struct list_head *item, *tmp;
+ struct dwc_qh *qh;
+
+ /* If the list hasn't been initialized yet, return. */
+ if (_qh_list->next == NULL)
+ return;
+
+ /* Ensure there are no QTDs or URBs left. */
+ kill_urbs_in_qh_list(hcd, _qh_list);
+
+ list_for_each_safe(item, tmp, _qh_list) {
+ qh = list_entry(item, struct dwc_qh, qh_list_entry);
+ dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+ }
+}
+
+/**
+ * Frees resources in the DWC_otg controller related to a given endpoint. Also
+ * clears state in the HCD related to the endpoint. Any URBs for the endpoint
+ * must already be dequeued.
+ */
+static void dwc_otg_hcd_endpoint_disable(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct dwc_qh *qh;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ qh = (struct dwc_qh *)ep->hcpriv;
+ if (qh) {
+ dwc_otg_hcd_qh_remove_and_free(dwc_hcd, qh);
+ ep->hcpriv = NULL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+}
+
+/**
+ * Creates Status Change bitmap for the root hub and root port. The bitmap is
+ * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1
+ * is the status change indicator for the single root port. Returns 1 if either
+ * change indicator is 1, otherwise returns 0.
+ */
+static int dwc_otg_hcd_hub_status_data(struct usb_hcd *_hcd, char *buf)
+{
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_hcd);
+
+ buf[0] = 0;
+ buf[0] |= (hcd->flags.b.port_connect_status_change
+ || hcd->flags.b.port_reset_change
+ || hcd->flags.b.port_enable_change
+ || hcd->flags.b.port_suspend_change
+ || hcd->flags.b.port_over_current_change) << 1;
+
+ return (buf[0] != 0);
+}
+
+/* Handles the hub class-specific ClearPortFeature request.*/
+static int do_clear_port_feature(struct dwc_hcd *hcd, u16 val)
+{
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hcd->lock, flags);
+ switch (val) {
+ case USB_PORT_FEAT_ENABLE:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Clear Resume bit */
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(100);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Port inidicator not supported */
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ /* Clears drivers internal connect status change flag */
+ hcd->flags.b.port_connect_status_change = 0;
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ /* Clears driver's internal Port Reset Change flag */
+ hcd->flags.b.port_reset_change = 0;
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ /* Clears driver's internal Port Enable/Disable Change flag */
+ hcd->flags.b.port_enable_change = 0;
+ break;
+ case USB_PORT_FEAT_C_SUSPEND:
+ /*
+ * Clears the driver's internal Port Suspend
+ * Change flag, which is set when resume signaling on
+ * the host port is complete
+ */
+ hcd->flags.b.port_suspend_change = 0;
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ hcd->flags.b.port_over_current_change = 0;
+ break;
+ default:
+ pr_err("DWC OTG HCD - ClearPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return 0;
+}
+
+/* Handles the hub class-specific SetPortFeature request.*/
+static int do_set_port_feature(struct usb_hcd *hcd, u16 val, u16 index)
+{
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ u32 hprt0 = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+ u32 pcgcctl = 0;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ switch (val) {
+ case USB_PORT_FEAT_SUSPEND:
+ if (hcd->self.otg_port == index && hcd->self.b_hnp_enable) {
+ u32 gotgctl = 0;
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(core_if->core_global_regs,
+ DWC_GOTGCTL, 0, gotgctl);
+ core_if->xceiv->state = OTG_STATE_A_SUSPEND;
+ }
+
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SUS_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Suspend the Phy Clock */
+ pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl);
+ dwc_reg_write(core_if->pcgcctl, 0, pcgcctl);
+
+ /* For HNP the bus must be suspended for at least 200ms. */
+ if (hcd->self.b_hnp_enable) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(200);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ }
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_RESET:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+
+ /*
+ * When B-Host the Port reset bit is set in the Start HCD
+ * Callback function, so that the reset is started within 1ms
+ * of the HNP success interrupt.
+ */
+ if (!hcd->self.is_b_host) {
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+
+ /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Not supported */
+ break;
+ default:
+ pr_err("DWC OTG HCD - "
+ "SetPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return 0;
+}
+
+/* Handles hub class-specific requests.*/
+static int dwc_otg_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 val,
+ u16 index, char *buf, u16 len)
+{
+ int retval = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ struct usb_hub_descriptor *desc;
+ u32 hprt0 = 0;
+ u32 port_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ switch (req_type) {
+ case ClearHubFeature:
+ switch (val) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* Nothing required here */
+ break;
+ default:
+ retval = -EINVAL;
+ pr_err("DWC OTG HCD - ClearHubFeature request"
+ " %xh unknown\n", val);
+ }
+ break;
+ case ClearPortFeature:
+ if (!index || index > 1)
+ goto error;
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_clear_port_feature(dwc_hcd, val);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ case GetHubDescriptor:
+ desc = (struct usb_hub_descriptor *)buf;
+ desc->bDescLength = 9;
+ desc->bDescriptorType = 0x29;
+ desc->bNbrPorts = 1;
+ desc->wHubCharacteristics = 0x08;
+ desc->bPwrOn2PwrGood = 1;
+ desc->bHubContrCurrent = 0;
+ break;
+ case GetHubStatus:
+ memset(buf, 0, 4);
+ break;
+ case GetPortStatus:
+ if (!index || index > 1)
+ goto error;
+
+ port_status = 0;
+ if (dwc_hcd->flags.b.port_connect_status_change)
+ port_status |= (1 << USB_PORT_FEAT_C_CONNECTION);
+ if (dwc_hcd->flags.b.port_enable_change)
+ port_status |= (1 << USB_PORT_FEAT_C_ENABLE);
+ if (dwc_hcd->flags.b.port_suspend_change)
+ port_status |= (1 << USB_PORT_FEAT_C_SUSPEND);
+ if (dwc_hcd->flags.b.port_reset_change)
+ port_status |= (1 << USB_PORT_FEAT_C_RESET);
+ if (dwc_hcd->flags.b.port_over_current_change) {
+ pr_err("Device Not Supported\n");
+ port_status |= (1 << USB_PORT_FEAT_C_OVER_CURRENT);
+ }
+ if (!dwc_hcd->flags.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.
+ */
+ *((__le32 *) buf) = cpu_to_le32(port_status);
+ break;
+ }
+
+ hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0);
+
+ if (DWC_HPRT0_PRT_STS_RD(hprt0))
+ port_status |= USB_PORT_STAT_CONNECTION;
+ if (DWC_HPRT0_PRT_ENA_RD(hprt0))
+ port_status |= USB_PORT_STAT_ENABLE;
+ if (DWC_HPRT0_PRT_SUS_RD(hprt0))
+ port_status |= USB_PORT_STAT_SUSPEND;
+ if (DWC_HPRT0_PRT_OVRCURR_ACT_RD(hprt0))
+ port_status |= USB_PORT_STAT_OVERCURRENT;
+ if (DWC_HPRT0_PRT_RST_RD(hprt0))
+ port_status |= USB_PORT_STAT_RESET;
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0))
+ port_status |= USB_PORT_STAT_POWER;
+
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+ port_status |= USB_PORT_STAT_HIGH_SPEED;
+ else if (DWC_HPRT0_PRT_SPD_RD(hprt0) ==
+ DWC_HPRT0_PRTSPD_LOW_SPEED)
+ port_status |= USB_PORT_STAT_LOW_SPEED;
+
+ if (DWC_HPRT0_PRT_TST_CTL_RD(hprt0))
+ port_status |= (1 << USB_PORT_FEAT_TEST);
+
+ /* USB_PORT_FEAT_INDICATOR unsupported always 0 */
+ *((__le32 *) buf) = cpu_to_le32(port_status);
+ break;
+ case SetHubFeature:
+ /* No HUB features supported */
+ break;
+ case SetPortFeature:
+ if (val != USB_PORT_FEAT_TEST && (!index || index > 1))
+ goto error;
+
+ if (!dwc_hcd->flags.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.
+ */
+ break;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_set_port_feature(hcd, val, index);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ default:
+error:
+ retval = -EINVAL;
+ pr_warning("DWC OTG HCD - Unknown hub control request"
+ " type or invalid req_type: %xh index: %xh "
+ "val: %xh\n", req_type, index, val);
+ break;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+}
+
+/**
+ * Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
+ * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid
+ * interrupt.
+ *
+ * This function is called by the USB core when an interrupt occurs
+ */
+static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+ return IRQ_RETVAL(dwc_otg_hcd_handle_intr(dwc_hcd));
+}
+
+static const struct hc_driver dwc_otg_hc_driver = {
+ .description = dwc_otg_hcd_name,
+ .product_desc = "DWC OTG Controller",
+ .hcd_priv_size = sizeof(struct dwc_hcd),
+ .irq = dwc_otg_hcd_irq,
+ .flags = HCD_MEMORY | HCD_USB2,
+ .start = dwc_otg_hcd_start,
+ .stop = dwc_otg_hcd_stop,
+ .urb_enqueue = dwc_otg_hcd_urb_enqueue,
+ .urb_dequeue = dwc_otg_hcd_urb_dequeue,
+ .endpoint_disable = dwc_otg_hcd_endpoint_disable,
+ .get_frame_number = dwc_otg_hcd_get_frame_number,
+ .hub_status_data = dwc_otg_hcd_hub_status_data,
+ .hub_control = dwc_otg_hcd_hub_control,
+};
+
+/**
+ * Frees secondary storage associated with the dwc_hcd structure contained
+ * in the struct usb_hcd field.
+ */
+static void dwc_otg_hcd_free(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 i;
+
+ del_timers(dwc_hcd);
+
+ /* Free memory for QH/QTD lists */
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_deferred);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_active);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_ready);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_assigned);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_queued);
+
+ /* Free memory for the host channels. */
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ struct dwc_hc *hc = dwc_hcd->hc_ptr_array[i];
+
+ kfree(hc);
+ }
+ if (dwc_hcd->core_if->dma_enable) {
+ if (dwc_hcd->status_buf_dma)
+ dma_free_coherent(hcd->self.controller,
+ DWC_OTG_HCD_STATUS_BUF_SIZE,
+ dwc_hcd->status_buf,
+ dwc_hcd->status_buf_dma);
+ } else {
+ kfree(dwc_hcd->status_buf);
+ }
+
+}
+
+/**
+ * Initializes the HCD. This function allocates memory for and initializes the
+ * static parts of the usb_hcd and dwc_hcd structures. It also registers the
+ * USB bus with the core and calls the hc_driver->start() function. It returns
+ * a negative error on failure.
+ */
+int dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_otg_device)
+{
+ struct usb_hcd *hcd;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ int retval = 0;
+
+ /*
+ * Allocate memory for the base HCD plus the DWC OTG HCD.
+ * Initialize the base HCD.
+ */
+ hcd = usb_create_hcd(&dwc_otg_hc_driver, _dev, dwc_otg_hcd_name);
+ if (!hcd) {
+ retval = -ENOMEM;
+ goto error1;
+ }
+ dev_set_drvdata(_dev, dwc_otg_device);
+ hcd->regs = otg_dev->base;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+ hcd->self.otg_port = 1;
+
+ /* Initialize the DWC OTG HCD. */
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ dwc_hcd->core_if = otg_dev->core_if;
+ spin_lock_init(&dwc_hcd->lock);
+ otg_dev->hcd = dwc_hcd;
+
+ /* Register the HCD CIL Callbacks */
+ dwc_otg_cil_register_hcd_callbacks(otg_dev->core_if, &hcd_cil_callbacks,
+ hcd);
+
+ /* Initialize the non-periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_active);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_deferred);
+
+ /* Initialize the periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_ready);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_assigned);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_queued);
+
+ /*
+ * Create a host channel descriptor for each host channel implemented
+ * in the controller. Initialize the channel descriptor array.
+ */
+ INIT_LIST_HEAD(&dwc_hcd->free_hc_list);
+ num_channels = dwc_hcd->core_if->core_params->host_channels;
+
+ for (i = 0; i < num_channels; i++) {
+ channel = kzalloc(sizeof(struct dwc_hc), GFP_KERNEL);
+ if (!channel) {
+ retval = -ENOMEM;
+ pr_err("%s: host channel allocation failed\n",
+ __func__);
+ goto error2;
+ }
+
+ channel->hc_num = i;
+ dwc_hcd->hc_ptr_array[i] = channel;
+ }
+
+ /* Initialize the Connection timeout timer. */
+ init_timer(&dwc_hcd->conn_timer);
+
+ /* Initialize workqueue */
+ INIT_WORK(&dwc_hcd->usb_port_reset, port_reset_wqfunc);
+ INIT_WORK(&dwc_hcd->start_work, hcd_start_func);
+ INIT_WORK(&dwc_hcd->core_if->usb_port_otg, NULL);
+ INIT_DELAYED_WORK(&dwc_hcd->core_if->usb_port_wakeup,
+ port_wakeup_wqfunc);
+
+ /* Set device flags indicating whether the HCD supports DMA. */
+ if (otg_dev->core_if->dma_enable) {
+ static u64 dummy_mask = DMA_BIT_MASK(32);
+
+ pr_info("Using DMA mode\n");
+ _dev->dma_mask = (void *)&dummy_mask;
+ _dev->coherent_dma_mask = ~0;
+ } else {
+ pr_info("Using Slave mode\n");
+ _dev->dma_mask = (void *)0;
+ _dev->coherent_dma_mask = 0;
+ }
+
+ init_hcd_usecs(dwc_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 dwc_otg_hcd_start method.
+ */
+ printk("before hcd\n");msleep(100);
+ retval = usb_add_hcd(hcd, otg_dev->irq, IRQF_SHARED);
+ printk("after hcd\n");msleep(100);
+ if (retval < 0)
+ goto error2;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+
+ /*
+ * Allocate space for storing data on status transactions. Normally no
+ * data is sent, but this space acts as a bit bucket. This must be
+ * done after usb_add_hcd since that function allocates the DMA buffer
+ * pool.
+ */
+ if (otg_dev->core_if->dma_enable) {
+ dwc_hcd->status_buf =
+ dma_alloc_coherent(_dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+ &dwc_hcd->status_buf_dma,
+ GFP_KERNEL | GFP_DMA);
+ } else {
+ dwc_hcd->status_buf = kmalloc(DWC_OTG_HCD_STATUS_BUF_SIZE,
+ GFP_KERNEL);
+ }
+ if (!dwc_hcd->status_buf) {
+ retval = -ENOMEM;
+ pr_err("%s: status_buf allocation failed\n", __func__);
+ goto error3;
+ }
+ return 0;
+
+error3:
+ usb_remove_hcd(hcd);
+error2:
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+error1:
+ return retval;
+}
+
+/**
+ * Removes the HCD.
+ * Frees memory and resources associated with the HCD and deregisters the bus.
+ */
+void __devexit dwc_otg_hcd_remove(struct device *_dev)
+{
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ struct dwc_hcd *dwc_hcd = otg_dev->hcd;
+ struct usb_hcd *hcd = dwc_otg_hcd_to_hcd(dwc_hcd);
+
+ /* Turn off all interrupts */
+ dwc_reg_write(gintmsk_reg(dwc_hcd), 0, 0);
+ spin_lock(&dwc_hcd->lock);
+ dwc_reg_modify(gahbcfg_reg(dwc_hcd), 0, 1, 0);
+ spin_unlock(&dwc_hcd->lock);
+
+ cancel_work_sync(&dwc_hcd->start_work);
+ cancel_work_sync(&dwc_hcd->usb_port_reset);
+ cancel_work_sync(&dwc_hcd->core_if->usb_port_otg);
+
+ usb_remove_hcd(hcd);
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+}
+
+/** Returns the current frame number. */
+int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(dwc_hcd->core_if->host_if->
+ host_global_regs, DWC_HFNUM);
+
+ return DWC_HFNUM_FRNUM_RD(hfnum);
+}
+
+/**
+ * Prepares a host channel for transferring packets to/from a specific
+ * endpoint. The HCCHARn register is set up with the characteristics specified
+ * in _hc. Host channel interrupts that may need to be serviced while this
+ * transfer is in progress are enabled.
+ */
+static void dwc_otg_hc_init(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 intr_enable;
+ ulong global_regs = core_if->core_global_regs;
+ u32 hc_intr_mask = 0;
+ u32 gintmsk = 0;
+ u32 hcchar;
+ u32 hcsplt;
+ u8 hc_num = hc->hc_num;
+ struct dwc_host_if *host_if = core_if->host_if;
+ ulong hc_regs = host_if->hc_regs[hc_num];
+
+ /* Clear old interrupt conditions for this host channel. */
+ hc_intr_mask = 0x3FF;
+ dwc_reg_write(hc_regs, DWC_HCINT, hc_intr_mask);
+
+ /* Enable channel interrupts required for this transfer. */
+ hc_intr_mask = 0;
+ hc_intr_mask = DWC_HCINTMSK_CHAN_HALTED_RW(hc_intr_mask, 1);
+ if (core_if->dma_enable) {
+ hc_intr_mask = DWC_HCINTMSK_AHB_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->error_state && !hc->do_split &&
+ hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask,
+ 1);
+ if (hc->ep_type != DWC_OTG_EP_TYPE_INTR)
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ }
+ } else {
+ switch (hc->ep_type) {
+ case DWC_OTG_EP_TYPE_CONTROL:
+ case DWC_OTG_EP_TYPE_BULK:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ } else {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->do_ping)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->do_split) {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ break;
+ case DWC_OTG_EP_TYPE_INTR:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in)
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+
+ if (hc->do_split) {
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ break;
+ case DWC_OTG_EP_TYPE_ISOC:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ }
+ break;
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hc_intr_mask);
+
+ /* Enable the top level host channel interrupt. */
+ intr_enable = (1 << hc_num);
+ dwc_reg_modify(host_if->host_global_regs, DWC_HAINTMSK, 0,
+ intr_enable);
+
+ /* Make sure host channel interrupts are enabled. */
+ gintmsk |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmsk);
+
+ /*
+ * Program the HCCHARn register with the endpoint characteristics for
+ * the current transfer.
+ */
+ hcchar = 0;
+ hcchar = DWC_HCCHAR_DEV_ADDR_RW(hcchar, hc->dev_addr);
+ hcchar = DWC_HCCHAR_EP_NUM_RW(hcchar, hc->ep_num);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, hc->ep_is_in);
+ hcchar = DWC_HCCHAR_LSP_DEV_RW(hcchar, (hc->speed ==
+ DWC_OTG_EP_SPEED_LOW));
+ hcchar = DWC_HCCHAR_EPTYPE_RW(hcchar, hc->ep_type);
+ hcchar = DWC_HCCHAR_MPS_RW(hcchar, hc->max_packet);
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCCHAR, hcchar);
+
+ /* Program the HCSPLIT register for SPLITs */
+ hcsplt = 0;
+ if (hc->do_split) {
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, hc->complete_split);
+ hcsplt = DWC_HCSPLT_TRANS_POS_RW(hcsplt, hc->xact_pos);
+ hcsplt = DWC_HCSPLT_HUB_ADDR_RW(hcsplt, hc->hub_addr);
+ hcsplt = DWC_HCSPLT_PRT_ADDR_RW(hcsplt, hc->port_addr);
+ }
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCSPLT, hcsplt);
+}
+
+/**
+ * Assigns transactions from a QTD to a free host channel and initializes the
+ * host channel to perform the transactions. The host channel is removed from
+ * the free list.
+ */
+static void assign_and_init_hc(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ struct dwc_hc *hc;
+ struct dwc_qtd *qtd;
+ struct urb *urb;
+ struct usb_iso_packet_descriptor *frame_desc;
+
+ hc = list_entry(hcd->free_hc_list.next, struct dwc_hc, hc_list_entry);
+
+ /* Remove the host channel from the free list. */
+ list_del_init(&hc->hc_list_entry);
+ qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+ urb = qtd->urb;
+ qh->channel = hc;
+ qh->qtd_in_process = qtd;
+
+ /*
+ * Use usb_pipedevice to determine device address. This address is
+ * 0 before the SET_ADDRESS command and the correct address afterward.
+ */
+ hc->dev_addr = usb_pipedevice(urb->pipe);
+ hc->ep_num = usb_pipeendpoint(urb->pipe);
+
+ if (urb->dev->speed == USB_SPEED_LOW)
+ hc->speed = DWC_OTG_EP_SPEED_LOW;
+ else if (urb->dev->speed == USB_SPEED_FULL)
+ hc->speed = DWC_OTG_EP_SPEED_FULL;
+ else
+ hc->speed = DWC_OTG_EP_SPEED_HIGH;
+
+ hc->max_packet = dwc_max_packet(qh->maxp);
+ hc->xfer_started = 0;
+ hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS;
+ hc->error_state = (qtd->error_count > 0);
+ hc->halt_on_queue = 0;
+ hc->halt_pending = 0;
+ hc->requests = 0;
+
+ /*
+ * The following values may be modified in the transfer type section
+ * below. The xfer_len value may be reduced when the transfer is
+ * started to accommodate the max widths of the XferSize and PktCnt
+ * fields in the HCTSIZn register.
+ */
+ hc->do_ping = qh->ping_state;
+ hc->ep_is_in = (usb_pipein(urb->pipe) != 0);
+ hc->data_pid_start = qh->data_toggle;
+ hc->multi_count = 1;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = urb->transfer_dma + (u8 *) urb->actual_length;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer +
+ urb->actual_length;
+
+ hc->xfer_len = urb->transfer_buffer_length - urb->actual_length;
+ hc->xfer_count = 0;
+
+ /*
+ * Set the split attributes
+ */
+ hc->do_split = 0;
+ if (qh->do_split) {
+ hc->do_split = 1;
+ hc->xact_pos = qtd->isoc_split_pos;
+ hc->complete_split = qtd->complete_split;
+ hc->hub_addr = urb->dev->tt->hub->devnum;
+ hc->port_addr = urb->dev->ttport;
+ }
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ hc->ep_type = DWC_OTG_EP_TYPE_CONTROL;
+
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ hc->do_ping = 0;
+ hc->ep_is_in = 0;
+ hc->data_pid_start = DWC_OTG_HC_PID_SETUP;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->setup_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->setup_packet;
+
+ hc->xfer_len = 8;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ hc->data_pid_start = qtd->data_toggle;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ /*
+ * Direction is opposite of data direction or IN if no
+ * data.
+ */
+ if (urb->transfer_buffer_length == 0)
+ hc->ep_is_in = 1;
+ else
+ hc->ep_is_in = (usb_pipein(urb->pipe) !=
+ USB_DIR_IN);
+
+ if (hc->ep_is_in)
+ hc->do_ping = 0;
+
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ hc->xfer_len = 0;
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff =
+ (u8 *) (u32) hcd->status_buf_dma;
+ else
+ hc->xfer_buff = (u8 *) hcd->status_buf;
+ break;
+ }
+ break;
+ case PIPE_BULK:
+ hc->ep_type = DWC_OTG_EP_TYPE_BULK;
+ break;
+ case PIPE_INTERRUPT:
+ hc->ep_type = DWC_OTG_EP_TYPE_INTR;
+ break;
+ case PIPE_ISOCHRONOUS:
+ frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index];
+ hc->ep_type = DWC_OTG_EP_TYPE_ISOC;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->transfer_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer;
+
+ hc->xfer_buff += frame_desc->offset + qtd->isoc_split_offset;
+ hc->xfer_len = frame_desc->length - qtd->isoc_split_offset;
+
+ if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+ if (hc->xfer_len <= 188)
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ else
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_BEGIN;
+ }
+ break;
+ }
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * This value may be modified when the transfer is started to
+ * reflect the actual transfer length.
+ */
+ hc->multi_count = dwc_hb_mult(qh->maxp);
+
+ dwc_otg_hc_init(hcd->core_if, hc);
+ hc->qh = qh;
+}
+
+/**
+ * This function selects transactions from the HCD transfer schedule and
+ * assigns them to available host channels. It is called from HCD interrupt
+ * handler functions.
+ */
+enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd *hcd)
+{
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int num_channels;
+ enum dwc_transaction_type ret_val = DWC_OTG_TRANSACTION_NONE;
+
+ /* Process entries in the periodic ready list. */
+ num_channels = hcd->core_if->core_params->host_channels;
+ qh_ptr = hcd->periodic_sched_ready.next;
+ while (qh_ptr != &hcd->periodic_sched_ready &&
+ !list_empty(&hcd->free_hc_list)) {
+ /* Leave one channel for non periodic transactions. */
+ if (hcd->available_host_channels <= 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the periodic ready schedule to the
+ * periodic assigned schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->periodic_sched_assigned);
+ ret_val = DWC_OTG_TRANSACTION_PERIODIC;
+ }
+
+ /*
+ * Process entries in the deferred portion of the non-periodic list.
+ * A NAK put them here and, at the right time, they need to be
+ * placed on the sched_inactive list.
+ */
+ qh_ptr = hcd->non_periodic_sched_deferred.next;
+ while (qh_ptr != &hcd->non_periodic_sched_deferred) {
+ u16 frame_number =
+ dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ qh_ptr = qh_ptr->next;
+
+ if (dwc_frame_num_le(qh->sched_frame, frame_number))
+ /*
+ * Move the QH from the non periodic deferred schedule
+ * to the non periodic inactive schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_inactive);
+ }
+
+ /*
+ * Process entries in the inactive portion of the non-periodic
+ * schedule. Some free host channels may not be used if they are
+ * reserved for periodic transfers.
+ */
+ qh_ptr = hcd->non_periodic_sched_inactive.next;
+ num_channels = hcd->core_if->core_params->host_channels;
+
+ while (qh_ptr != &hcd->non_periodic_sched_inactive
+ && !list_empty(&hcd->free_hc_list)) {
+ if (hcd->available_host_channels < 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the non-periodic inactive schedule to the
+ * non-periodic active schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->non_periodic_sched_active);
+ if (ret_val == DWC_OTG_TRANSACTION_NONE)
+ ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
+ else
+ ret_val = DWC_OTG_TRANSACTION_ALL;
+
+ }
+ return ret_val;
+}
+
+/**
+ * Sets the channel property that indicates in which frame a periodic transfer
+ * should occur. This is always set to the _next_ frame. This function has no
+ * effect on non-periodic transfers.
+ */
+static inline void hc_set_even_odd_frame(struct core_if *core_if,
+ struct dwc_hc *hc, u32 * hcchar)
+{
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(core_if->host_if->host_global_regs,
+ DWC_HFNUM);
+
+ /* 1 if _next_ frame is odd, 0 if it's even */
+ *hcchar = DWC_HCCHAR_ODD_FRAME_RW(*hcchar,
+ ((DWC_HFNUM_FRNUM_RD(hfnum) &
+ 0x1) ? 0 : 1));
+ }
+}
+
+static void set_initial_xfer_pid(struct dwc_hc *hc)
+{
+ if (hc->speed == DWC_OTG_EP_SPEED_HIGH) {
+ if (hc->ep_is_in) {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else if (hc->multi_count == 2)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA2;
+ } else {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_MDATA;
+ }
+ } else {
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ }
+}
+
+/**
+ * Starts a PING transfer. This function should only be called in Slave mode.
+ * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled.
+ */
+static void dwc_otg_hc_do_ping(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hctsiz = 0;
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, 1);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+static void dwc_otg_hc_write_packet(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 i;
+ u32 remaining_count;
+ u32 byte_count;
+ u32 dword_count;
+ u32 *data_buff = (u32 *) (hc->xfer_buff);
+ u32 data_fifo = core_if->data_fifo[hc->hc_num];
+
+ remaining_count = hc->xfer_len - hc->xfer_count;
+ if (remaining_count > hc->max_packet)
+ byte_count = hc->max_packet;
+ else
+ byte_count = remaining_count;
+
+ dword_count = (byte_count + 3) / 4;
+
+ if (((unsigned long)data_buff) & 0x3)
+ /* xfer_buff is not DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo,
+ get_unaligned(data_buff));
+ else
+ /* xfer_buff is DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo, *data_buff);
+
+ hc->xfer_count += byte_count;
+ hc->xfer_buff += byte_count;
+}
+
+/**
+ * This function does the setup for a data transfer for a host channel and
+ * starts the transfer. May be called in either Slave mode or DMA mode. In
+ * Slave mode, the caller must ensure that there is sufficient space in the
+ * request queue and Tx Data FIFO.
+ *
+ * For an OUT transfer in Slave mode, it loads a data packet into the
+ * appropriate FIFO. If necessary, additional data packets will be loaded in
+ * the Host ISR.
+ *
+ * For an IN transfer in Slave mode, a data packet is requested. The data
+ * packets are unloaded from the Rx FIFO in the Host ISR. If necessary,
+ * additional data packets are requested in the Host ISR.
+ *
+ * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ
+ * register along with a packet count of 1 and the channel is enabled. This
+ * causes a single PING transaction to occur. Other fields in HCTSIZ are
+ * simply set to 0 since no data transfer occurs in this case.
+ *
+ * For a PING transfer in DMA mode, the HCTSIZ register is initialized with
+ * all the information required to perform the subsequent data transfer. In
+ * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the
+ * controller performs the entire PING protocol, then starts the data
+ * transfer.
+ */
+static void dwc_otg_hc_start_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+ u16 num_packets;
+ u32 max_hc_xfer_size = core_if->core_params->max_transfer_size;
+ u16 max_hc_pkt_count = core_if->core_params->max_packet_count;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ hctsiz = 0;
+
+ if (hc->do_ping) {
+ if (!core_if->dma_enable) {
+ dwc_otg_hc_do_ping(core_if, hc);
+ hc->xfer_started = 1;
+ return;
+ } else {
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ }
+ }
+
+ if (hc->do_split) {
+ num_packets = 1;
+
+ if (hc->complete_split && !hc->ep_is_in)
+ /*
+ * For CSPLIT OUT Transfer, set the size to 0 so the
+ * core doesn't expect any data written to the FIFO
+ */
+ hc->xfer_len = 0;
+ else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet))
+ hc->xfer_len = hc->max_packet;
+ else if (!hc->ep_is_in && (hc->xfer_len > 188))
+ hc->xfer_len = 188;
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ } else {
+ /*
+ * Ensure that the transfer length and packet count will fit
+ * in the widths allocated for them in the HCTSIZn register.
+ */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 max_len = hc->multi_count * hc->max_packet;
+
+ /*
+ * Make sure the transfer size is no larger than one
+ * (micro)frame's worth of data. (A check was done
+ * when the periodic transfer was accepted to ensure
+ * that a (micro)frame's worth of data can be
+ * programmed into a channel.)
+ */
+ if (hc->xfer_len > max_len)
+ hc->xfer_len = max_len;
+ } else if (hc->xfer_len > max_hc_xfer_size) {
+ /*
+ * Make sure that xfer_len is a multiple of max packet
+ * size.
+ */
+ hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1;
+ }
+ if (hc->xfer_len > 0) {
+ num_packets = (hc->xfer_len + hc->max_packet - 1) /
+ hc->max_packet;
+ if (num_packets > max_hc_pkt_count) {
+ num_packets = max_hc_pkt_count;
+ hc->xfer_len = num_packets * hc->max_packet;
+ }
+ } else {
+ /* Need 1 packet for transfer length of 0. */
+ num_packets = 1;
+ }
+
+ if (hc->ep_is_in)
+ /*
+ * Always program an integral # of max packets for IN
+ * transfers.
+ */
+ hc->xfer_len = num_packets * hc->max_packet;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * Make sure that the multi_count field matches the
+ * actual transfer length.
+ */
+ hc->multi_count = num_packets;
+
+ /* Set up the initial PID for the transfer. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ set_initial_xfer_pid(hc);
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ }
+
+ hc->start_pkt_count = num_packets;
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, num_packets);
+ hctsiz = DWC_HCTSIZ_PKT_PID_RW(hctsiz, hc->data_pid_start);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ if (core_if->dma_enable)
+ dwc_reg_write(hc_regs, DWC_HCDMA, (u32) hc->xfer_buff);
+
+ /* Start the split */
+ if (hc->do_split) {
+ u32 hcsplt;
+
+ hcsplt = dwc_reg_read(hc_regs, DWC_HCSPLT);
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, 1);
+ dwc_reg_write(hc_regs, DWC_HCSPLT, hcsplt);
+ }
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_MULTI_CNT_RW(hcchar, hc->multi_count);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ /* Set host channel enable after all other setup is complete. */
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->xfer_started = 1;
+ hc->requests++;
+ if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0)
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+}
+
+/**
+ * This function continues a data transfer that was started by previous call
+ * to dwc_otg_hc_start_transfer</code>. The caller must ensure there is
+ * sufficient space in the request queue and Tx Data FIFO. This function
+ * should only be called in Slave mode. In DMA mode, the controller acts
+ * autonomously to complete transfers programmed to a host channel.
+ *
+ * For an OUT transfer, a new data packet is loaded into the appropriate FIFO
+ * if there is any data remaining to be queued. For an IN transfer, another
+ * data packet is always requested. For the SETUP phase of a control transfer,
+ * this function does nothing.
+ */
+static int dwc_otg_hc_continue_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ if (hc->do_split) {
+ /* SPLITs always queue just once per channel */
+ return 0;
+ } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) {
+ /* SETUPs are queued only once since they can't be NAKed. */
+ return 0;
+ } else if (hc->ep_is_in) {
+ /*
+ * Always queue another request for other IN transfers. If
+ * back-to-back INs are issued and NAKs are received for both,
+ * the driver may still be processing the first NAK when the
+ * second NAK is received. When the interrupt handler clears
+ * the NAK interrupt for the first NAK, the second NAK will
+ * not be seen. So we can't depend on the NAK interrupt
+ * handler to requeue a NAKed request. Instead, IN requests
+ * are issued each time this function is called. When the
+ * transfer completes, the extra requests for the channel will
+ * be flushed.
+ */
+ u32 hcchar;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->requests++;
+ return 1;
+ } else {
+ /* OUT transfers. */
+ if (hc->xfer_count < hc->xfer_len) {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hcchar;
+ u32 hc_regs;
+
+ hc_regs =
+ core_if->host_if->hc_regs[hc->hc_num];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+ }
+
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+ hc->requests++;
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+
+/**
+ * Attempts to queue a single transaction request for a host channel
+ * associated with either a periodic or non-periodic transfer. This function
+ * assumes that there is space available in the appropriate request queue. For
+ * an OUT transfer or SETUP transaction in Slave mode, it checks whether space
+ * is available in the appropriate Tx FIFO.
+ */
+static int queue_transaction(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u16 _fifo_dwords_avail)
+{
+ int retval;
+
+ if (hcd->core_if->dma_enable) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ hc->qh->ping_state = 0;
+ }
+ retval = 0;
+ } else if (hc->halt_pending) {
+ /* Don't queue a request if the channel has been halted. */
+ retval = 0;
+ } else if (hc->halt_on_queue) {
+ dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status);
+ retval = 0;
+ } else if (hc->do_ping) {
+ if (!hc->xfer_started)
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 0;
+ } else if (!hc->ep_is_in || hc->data_pid_start ==
+ DWC_OTG_HC_PID_SETUP) {
+ if ((_fifo_dwords_avail * 4) >= hc->max_packet) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval =
+ dwc_otg_hc_continue_transfer(hcd->core_if,
+ hc);
+ }
+ } else {
+ retval = -1;
+ }
+ } else {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc);
+ }
+ }
+ return retval;
+}
+
+/**
+ * Processes active non-periodic channels and queues transactions for these
+ * channels to the DWC_otg controller. After queueing transactions, the NP Tx
+ * FIFO Empty interrupt is enabled if there are more transactions to queue as
+ * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx
+ * FIFO Empty interrupt is disabled.
+ */
+static void process_non_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *orig_qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ int more_to_do = 0;
+ ulong regs = hcd->core_if->core_global_regs;
+
+ /*
+ * Keep track of the starting point. Skip over the start-of-list
+ * entry.
+ */
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ orig_qh_ptr = hcd->non_periodic_qh_ptr;
+
+ /*
+ * Process once through the active list or until no more space is
+ * available in the request queue or the Tx FIFO.
+ */
+ do {
+ tx_status = dwc_reg_read(regs, DWC_GNPTXSTS);
+ if (!hcd->core_if->dma_enable &&
+ DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(hcd->non_periodic_qh_ptr, struct dwc_qh,
+ qh_list_entry);
+ status = queue_transaction(hcd, qh->channel,
+ DWC_GNPTXSTS_NPTXFSPCAVAIL_RD
+ (tx_status));
+
+ if (status > 0) {
+ more_to_do = 1;
+ } else if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /* Advance to next QH, skipping start-of-list entry. */
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr =
+ hcd->non_periodic_qh_ptr->next;
+ } while (hcd->non_periodic_qh_ptr != orig_qh_ptr);
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ if (more_to_do || no_queue_space || no_fifo_space) {
+ /*
+ * May need to queue more transactions as the request
+ * queue or Tx FIFO empties. Enable the non-periodic
+ * Tx FIFO empty interrupt. (Always use the half-empty
+ * level to ensure that new requests are loaded as
+ * soon as possible.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ } else {
+ /*
+ * Disable the Tx FIFO empty interrupt since there are
+ * no more transactions that need to be queued right
+ * now. This function is called from interrupt
+ * handlers to queue more transactions as transfer
+ * states change.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+ }
+}
+
+/**
+ * Processes periodic channels for the next frame and queues transactions for
+ * these channels to the DWC_otg controller. After queueing transactions, the
+ * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions
+ * to queue as Periodic Tx FIFO or request queue space becomes available.
+ * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled.
+ */
+static void process_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ ulong host_regs;
+
+ host_regs = hcd->core_if->host_if->host_global_regs;
+
+ qh_ptr = hcd->periodic_sched_assigned.next;
+ while (qh_ptr != &hcd->periodic_sched_assigned) {
+ tx_status = dwc_reg_read(host_regs, DWC_HPTXSTS);
+ if (DWC_HPTXSTS_PTXSPC_AVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+
+ /*
+ * Set a flag if we're queuing high-bandwidth in slave mode.
+ * The flag prevents any halts to get into the request queue in
+ * the middle of multiple high-bandwidth packets getting queued.
+ */
+ if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1)
+ hcd->core_if->queuing_high_bandwidth = 1;
+
+ status = queue_transaction(hcd, qh->channel,
+ DWC_HPTXSTS_PTXFSPC_AVAIL_RD
+ (tx_status));
+ if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /*
+ * In Slave mode, stay on the current transfer until there is
+ * nothing more to do or the high-bandwidth request count is
+ * reached. In DMA mode, only need to queue one request. The
+ * controller automatically handles multiple packets for
+ * high-bandwidth transfers.
+ */
+ if (hcd->core_if->dma_enable || (status == 0 ||
+ qh->channel->requests ==
+ qh->channel->multi_count)) {
+ qh_ptr = qh_ptr->next;
+
+ /*
+ * Move the QH from the periodic assigned schedule to
+ * the periodic queued schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_queued);
+
+ /* done queuing high bandwidth */
+ hcd->core_if->queuing_high_bandwidth = 0;
+ }
+ }
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+
+ if (!list_empty(&hcd->periodic_sched_assigned) ||
+ no_queue_space || no_fifo_space)
+ /*
+ * May need to queue more transactions as the request
+ * queue or Tx FIFO empties. Enable the periodic Tx
+ * FIFO empty interrupt. (Always use the half-empty
+ * level to ensure that new requests are loaded as
+ * soon as possible.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ else
+ /*
+ * Disable the Tx FIFO empty interrupt since there are
+ * no more transactions that need to be queued right
+ * now. This function is called from interrupt
+ * handlers to queue more transactions as transfer
+ * states change.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+}
+
+/**
+ * This function processes the currently active host channels and queues
+ * transactions for these channels to the DWC_otg controller. It is called
+ * from HCD interrupt handler functions.
+ */
+void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type)
+{
+ /* Process host channels associated with periodic transfers. */
+ if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) &&
+ !list_empty(&hcd->periodic_sched_assigned))
+ process_periodic_channels(hcd);
+
+ /* Process host channels associated with non-periodic transfers. */
+ if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) {
+ if (!list_empty(&hcd->non_periodic_sched_active)) {
+ process_non_periodic_channels(hcd);
+ } else {
+ /*
+ * Ensure NP Tx FIFO empty interrupt is disabled when
+ * there are no non-periodic transfers to process.
+ */
+ u32 gintmsk = 0;
+ gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, gintmsk, 0);
+ }
+ }
+}
+
+/**
+ * Sets the final status of an URB and returns it to the device driver. Any
+ * required cleanup of the URB is performed.
+ */
+void dwc_otg_hcd_complete_urb(struct dwc_hcd *hcd, struct urb *urb, int status)
+__releases(hcd->lock) __acquires(hcd->lock)
+{
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
+
+ spin_unlock(&hcd->lock);
+ usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status);
+ spin_lock(&hcd->lock);
+}
diff --git a/drivers/usb/dwc/hcd.h b/drivers/usb/dwc/hcd.h
new file mode 100644
index 0000000..2d25abc
--- /dev/null
+++ b/drivers/usb/dwc/hcd.h
@@ -0,0 +1,416 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#if !defined(__DWC_HCD_H__)
+#define __DWC_HCD_H__
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "driver.h"
+
+/*
+ * This file contains the structures, constants, and interfaces for
+ * the Host Contoller Driver (HCD).
+ *
+ * The Host Controller Driver (HCD) is responsible for translating requests
+ * from the USB Driver into the appropriate actions on the DWC_otg controller.
+ * It isolates the USBD from the specifics of the controller by providing an
+ * API to the USBD.
+ */
+
+/* Phases for control transfers. */
+enum dwc_control_phase {
+ DWC_OTG_CONTROL_SETUP,
+ DWC_OTG_CONTROL_DATA,
+ DWC_OTG_CONTROL_STATUS
+};
+
+/* Transaction types. */
+enum dwc_transaction_type {
+ DWC_OTG_TRANSACTION_NONE,
+ DWC_OTG_TRANSACTION_PERIODIC,
+ DWC_OTG_TRANSACTION_NON_PERIODIC,
+ DWC_OTG_TRANSACTION_ALL
+};
+
+/*
+ * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
+ * interrupt, or isochronous transfer. A single QTD is created for each URB
+ * (of one of these types) submitted to the HCD. The transfer associated with
+ * a QTD may require one or multiple transactions.
+ *
+ * A QTD is linked to a Queue Head, which is entered in either the
+ * non-periodic or periodic schedule for execution. When a QTD is chosen for
+ * execution, some or all of its transactions may be executed. After
+ * execution, the state of the QTD is updated. The QTD may be retired if all
+ * its transactions are complete or if an error occurred. Otherwise, it
+ * remains in the schedule so more transactions can be executed later.
+ */
+struct dwc_qtd {
+ /*
+ * Determines the PID of the next data packet for the data phase of
+ * control transfers. Ignored for other transfer types.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Current phase for control transfers (Setup, Data, or Status). */
+ enum dwc_control_phase control_phase;
+
+ /*
+ * Keep track of the current split type
+ * for FS/LS endpoints on a HS Hub
+ */
+ u8 complete_split;
+
+ /* How many bytes transferred during SSPLIT OUT */
+ u32 ssplit_out_xfer_count;
+
+ /*
+ * Holds the number of bus errors that have occurred for a transaction
+ * within this transfer.
+ */
+ u8 error_count;
+
+ /*
+ * Index of the next frame descriptor for an isochronous transfer. A
+ * frame descriptor describes the buffer position and length of the
+ * data to be transferred in the next scheduled (micro)frame of an
+ * isochronous transfer. It also holds status for that transaction.
+ * The frame index starts at 0.
+ */
+ int isoc_frame_index;
+
+ /* Position of the ISOC split on full/low speed */
+ u8 isoc_split_pos;
+
+ /* Position of the ISOC split in the buffer for the current frame */
+ u16 isoc_split_offset;
+
+ /* URB for this transfer */
+ struct urb *urb;
+
+ /* This list of QTDs */
+ struct list_head qtd_list_entry;
+
+ /* Field to track the qh pointer */
+ struct dwc_qh *qtd_qh_ptr;
+};
+
+/*
+ * A Queue Head (QH) holds the static characteristics of an endpoint and
+ * maintains a list of transfers (QTDs) for that endpoint. A QH structure may
+ * be entered in either the non-periodic or periodic schedule.
+ */
+struct dwc_qh {
+ /*
+ * Endpoint type.
+ * One of the following values:
+ * - USB_ENDPOINT_XFER_CONTROL
+ * - USB_ENDPOINT_XFER_ISOC
+ * - USB_ENDPOINT_XFER_BULK
+ * - USB_ENDPOINT_XFER_INT
+ */
+ u8 ep_type;
+ u8 ep_is_in;
+
+ /* wMaxPacketSize Field of Endpoint Descriptor. */
+ u16 maxp;
+
+ /*
+ * Determines the PID of the next data packet for non-control
+ * transfers. Ignored for control transfers.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Ping state if 1. */
+ u8 ping_state;
+
+ /* List of QTDs for this QH. */
+ struct list_head qtd_list;
+
+ /* Host channel currently processing transfers for this QH. */
+ struct dwc_hc *channel;
+
+ /* QTD currently assigned to a host channel for this QH. */
+ struct dwc_qtd *qtd_in_process;
+
+ /* Full/low speed endpoint on high-speed hub requires split. */
+ u8 do_split;
+
+ /* Periodic schedule information */
+
+ /* Bandwidth in microseconds per (micro)frame. */
+ u8 usecs;
+
+ /* Interval between transfers in (micro)frames. */
+ u16 interval;
+
+ /*
+ * (micro)frame to initialize a periodic transfer. The transfer
+ * executes in the following (micro)frame.
+ */
+ u16 sched_frame;
+
+ /* (micro)frame at which last start split was initialized. */
+ u16 start_split_frame;
+
+ u16 speed;
+ u16 frame_usecs[8];
+
+ /* Entry for QH in either the periodic or non-periodic schedule. */
+ struct list_head qh_list_entry;
+};
+
+/* Gets the struct usb_hcd that contains a struct dwc_hcd. */
+static inline struct usb_hcd *dwc_otg_hcd_to_hcd(struct dwc_hcd *dwc_hcd)
+{
+ return container_of((void *)dwc_hcd, struct usb_hcd, hcd_priv);
+}
+
+/* HCD Create/Destroy Functions */
+extern int dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev);
+extern void dwc_otg_hcd_remove(struct device *_dev);
+
+/*
+ * The following functions support managing the DWC_otg controller in host
+ * mode.
+ */
+extern int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd);
+extern void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc);
+extern void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status _halt_status);
+
+/* Transaction Execution Functions */
+extern enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd
+ *hcd);
+extern void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type);
+extern void dwc_otg_hcd_complete_urb(struct dwc_hcd *_hcd, struct urb *urb,
+ int status);
+
+/* Interrupt Handler Functions */
+extern int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd);
+
+/* Schedule Queue Functions */
+extern int init_hcd_usecs(struct dwc_hcd *hcd);
+extern void dwc_otg_hcd_qh_free(struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_csplit);
+extern int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int delay);
+extern struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb,
+ gfp_t _mem_flags);
+extern int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *dwc_hcd);
+
+/*
+ * Frees the memory for a QTD structure. QTD should already be removed from
+ * list.
+ */
+static inline void dwc_otg_hcd_qtd_free(struct dwc_qtd *_qtd)
+{
+ kfree(_qtd);
+}
+
+/* Removes a QTD from list. */
+static inline void dwc_otg_hcd_qtd_remove(struct dwc_qtd *_qtd)
+{
+ list_del(&_qtd->qtd_list_entry);
+}
+
+/* Remove and free a QTD */
+static inline void dwc_otg_hcd_qtd_remove_and_free(struct dwc_qtd *_qtd)
+{
+ dwc_otg_hcd_qtd_remove(_qtd);
+ dwc_otg_hcd_qtd_free(_qtd);
+}
+
+struct dwc_qh *dwc_urb_to_qh(struct urb *_urb);
+
+/* Gets the usb_host_endpoint associated with an URB. */
+static inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *_urb)
+{
+ struct usb_device *dev = _urb->dev;
+ int ep_num = usb_pipeendpoint(_urb->pipe);
+
+ if (usb_pipein(_urb->pipe))
+ return dev->ep_in[ep_num];
+ else
+ return dev->ep_out[ep_num];
+}
+
+/*
+ * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is
+ * qualified with its direction (possible 32 endpoints per device).
+ */
+#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) \
+ ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \
+ ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4)
+
+/* Gets the QH that contains the list_head */
+#define dwc_list_to_qh(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qh, qh_list_entry))
+
+/* Gets the QTD that contains the list_head */
+#define dwc_list_to_qtd(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qtd, qtd_list_entry))
+
+/* Check if QH is non-periodic */
+#define dwc_qh_is_non_per(_qh_ptr_) \
+ ((_qh_ptr_->ep_type == USB_ENDPOINT_XFER_BULK) || \
+ (_qh_ptr_->ep_type == USB_ENDPOINT_XFER_CONTROL))
+
+/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */
+#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+/* Packet size for any kind of endpoint descriptor */
+#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
+
+/*
+ * Returns true if _frame1 is less than or equal to _frame2. The comparison is
+ * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the
+ * frame number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_le(u16 _frame1, u16 _frame2)
+{
+ return ((_frame2 - _frame1) & DWC_HFNUM_MAX_FRNUM) <=
+ (DWC_HFNUM_MAX_FRNUM >> 1);
+}
+
+/*
+ * Returns true if _frame1 is greater than _frame2. The comparison is done
+ * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame
+ * number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_gt(u16 _frame1, u16 _frame2)
+{
+ return (_frame1 != _frame2) &&
+ (((_frame1 - _frame2) &
+ DWC_HFNUM_MAX_FRNUM) < (DWC_HFNUM_MAX_FRNUM >> 1));
+}
+
+/*
+ * Increments _frame by the amount specified by _inc. The addition is done
+ * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value.
+ */
+static inline u16 dwc_frame_num_inc(u16 _frame, u16 _inc)
+{
+ return (_frame + _inc) & DWC_HFNUM_MAX_FRNUM;
+}
+
+static inline u16 dwc_full_frame_num(u16 _frame)
+{
+ return ((_frame) & DWC_HFNUM_MAX_FRNUM) >> 3;
+}
+
+static inline u16 dwc_micro_frame_num(u16 _frame)
+{
+ return (_frame) & 0x7;
+}
+
+static inline ulong gintsts_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTSTS;
+}
+
+static inline ulong gintmsk_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTMSK;
+}
+
+static inline ulong gahbcfg_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GAHBCFG;
+}
+
+static inline const char *pipetype_str(unsigned int pipe)
+{
+ switch (usb_pipetype(pipe)) {
+ case PIPE_CONTROL:
+ return "control";
+ case PIPE_BULK:
+ return "bulk";
+ case PIPE_INTERRUPT:
+ return "interrupt";
+ case PIPE_ISOCHRONOUS:
+ return "isochronous";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *dev_speed_str(enum usb_device_speed speed)
+{
+ switch (speed) {
+ case USB_SPEED_HIGH:
+ return "high";
+ case USB_SPEED_FULL:
+ return "full";
+ case USB_SPEED_LOW:
+ return "low";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *ep_type_str(u8 type)
+{
+ switch (type) {
+ case USB_ENDPOINT_XFER_ISOC:
+ return "isochronous";
+ case USB_ENDPOINT_XFER_INT:
+ return "interrupt";
+ case USB_ENDPOINT_XFER_CONTROL:
+ return "control";
+ case USB_ENDPOINT_XFER_BULK:
+ return "bulk";
+ default:
+ return "?";
+ }
+}
+#endif
diff --git a/drivers/usb/dwc/hcd_intr.c b/drivers/usb/dwc/hcd_intr.c
new file mode 100644
index 0000000..b16934d
--- /dev/null
+++ b/drivers/usb/dwc/hcd_intr.c
@@ -0,0 +1,1477 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#include "hcd.h"
+
+/* This file contains the implementation of the HCD Interrupt handlers. */
+static const int erratum_usb09_patched;
+static const int deferral_on = 1;
+static const int nak_deferral_delay = 8;
+static const int nyet_deferral_delay = 1;
+
+/**
+ * Handles the start-of-frame interrupt in host mode. Non-periodic
+ * transactions may be queued to the DWC_otg controller for the current
+ * (micro)frame. Periodic transactions may be queued to the controller for the
+ * next (micro)frame.
+ */
+static int dwc_otg_hcd_handle_sof_intr(struct dwc_hcd *hcd)
+{
+ u32 hfnum = 0;
+ struct list_head *qh_entry;
+ struct dwc_qh *qh;
+ enum dwc_transaction_type tr_type;
+ u32 gintsts = 0;
+
+ hfnum =
+ dwc_reg_read(hcd->core_if->host_if->host_global_regs,
+ DWC_HFNUM);
+
+ hcd->frame_number = DWC_HFNUM_FRNUM_RD(hfnum);
+
+ /* Determine whether any periodic QHs should be executed. */
+ qh_entry = hcd->periodic_sched_inactive.next;
+ while (qh_entry != &hcd->periodic_sched_inactive) {
+ qh = list_entry(qh_entry, struct dwc_qh, qh_list_entry);
+ qh_entry = qh_entry->next;
+
+ /*
+ * If needed, move QH to the ready list to be executed next
+ * (micro)frame.
+ */
+ if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number))
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_ready);
+ }
+
+ tr_type = dwc_otg_hcd_select_transactions(hcd);
+ if (tr_type != DWC_OTG_TRANSACTION_NONE)
+ dwc_otg_hcd_queue_transactions(hcd, tr_type);
+
+ /* Clear interrupt */
+ gintsts |= DWC_INTMSK_STRT_OF_FRM;
+ dwc_reg_write(gintsts_reg(hcd), 0, gintsts);
+ return 1;
+}
+
+/**
+ * Handles the Rx Status Queue Level Interrupt, which indicates that there is at
+ * least one packet in the Rx FIFO. The packets are moved from the FIFO to
+ * memory if the DWC_otg controller is operating in Slave mode.
+ */
+static int dwc_otg_hcd_handle_rx_status_q_level_intr(struct dwc_hcd *hcd)
+{
+ u32 grxsts;
+ struct dwc_hc *hc;
+
+ grxsts = dwc_reg_read(hcd->core_if->core_global_regs, DWC_GRXSTSP);
+ hc = hcd->hc_ptr_array[grxsts & DWC_HM_RXSTS_CHAN_NUM_RD(grxsts)];
+
+ /* Packet Status */
+ switch (DWC_HM_RXSTS_PKT_STS_RD(grxsts)) {
+ case DWC_GRXSTS_PKTSTS_IN:
+ /* Read the data into the host buffer. */
+ if (DWC_HM_RXSTS_BYTE_CNT_RD(grxsts) > 0) {
+ dwc_otg_read_packet(hcd->core_if, hc->xfer_buff,
+ DWC_HM_RXSTS_BYTE_CNT_RD(grxsts));
+ /* Update the HC fields for the next packet received. */
+ hc->xfer_count += DWC_HM_RXSTS_BYTE_CNT_RD(grxsts);
+ hc->xfer_buff += DWC_HM_RXSTS_BYTE_CNT_RD(grxsts);
+ }
+ case DWC_GRXSTS_PKTSTS_IN_XFER_COMP:
+ case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR:
+ case DWC_GRXSTS_PKTSTS_CH_HALTED:
+ /* Handled in interrupt, just ignore data */
+ break;
+ default:
+ pr_err("RX_STS_Q Interrupt: Unknown status %d\n",
+ DWC_HM_RXSTS_PKT_STS_RD(grxsts));
+ break;
+ }
+ return 1;
+}
+
+/**
+ * This interrupt occurs when the non-periodic Tx FIFO is half-empty. More
+ * data packets may be written to the FIFO for OUT transfers. More requests
+ * may be written to the non-periodic request queue for IN transfers. This
+ * interrupt is enabled only in Slave mode.
+ */
+static int dwc_otg_hcd_handle_np_tx_fifo_empty_intr(struct dwc_hcd *hcd)
+{
+ dwc_otg_hcd_queue_transactions(hcd, DWC_OTG_TRANSACTION_NON_PERIODIC);
+ return 1;
+}
+
+/**
+ * This interrupt occurs when the periodic Tx FIFO is half-empty. More data
+ * packets may be written to the FIFO for OUT transfers. More requests may be
+ * written to the periodic request queue for IN transfers. This interrupt is
+ * enabled only in Slave mode.
+ */
+static int dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(struct dwc_hcd *hcd)
+{
+ dwc_otg_hcd_queue_transactions(hcd, DWC_OTG_TRANSACTION_PERIODIC);
+ return 1;
+}
+
+/**
+ * When the port changes to enabled it may be necessary to adjust the phy clock
+ * speed.
+ */
+static int adjusted_phy_clock_speed(struct dwc_hcd *hcd, u32 hprt0)
+{
+ int adjusted = 0;
+ u32 usbcfg;
+ ulong global_regs = hcd->core_if->core_global_regs;
+ struct core_params *params = hcd->core_if->core_params;
+ ulong h_regs = hcd->core_if->host_if->host_global_regs;
+
+ usbcfg = dwc_reg_read(global_regs, DWC_GUSBCFG);
+
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_LOW_SPEED ||
+ DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_FULL_SPEED) {
+ /* Low power */
+ u32 hcfg;
+
+ if (!(usbcfg & DWC_USBCFG_PHYLPWRCLKSEL)) {
+ /* Set PHY low power clock select for FS/LS devices */
+ usbcfg |= DWC_USBCFG_PHYLPWRCLKSEL;
+ dwc_reg_write(global_regs, DWC_GUSBCFG, usbcfg);
+ adjusted = 1;
+ }
+
+ hcfg = dwc_reg_read(h_regs, DWC_HCFG);
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_LOW_SPEED &&
+ params->host_ls_low_power_phy_clk ==
+ DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) {
+ /* 6 MHZ, check for 6 MHZ clock select */
+ if (DWC_HCFG_FSLSP_CLK_RD(hcfg) != DWC_HCFG_6_MHZ) {
+ hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg,
+ DWC_HCFG_6_MHZ);
+ dwc_reg_write(h_regs, DWC_HCFG, hcfg);
+ adjusted = 1;
+ }
+ } else if (DWC_HCFG_FSLSP_CLK_RD(hcfg) != DWC_HCFG_48_MHZ) {
+ /* 48 MHZ and clock select is not 48 MHZ */
+ hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, DWC_HCFG_48_MHZ);
+ dwc_reg_write(h_regs, DWC_HCFG, hcfg);
+ adjusted = 1;
+ }
+ } else if (usbcfg & DWC_USBCFG_PHYLPWRCLKSEL) {
+ usbcfg &= ~((u32) DWC_USBCFG_PHYLPWRCLKSEL);
+ dwc_reg_write(global_regs, DWC_GUSBCFG, usbcfg);
+ adjusted = 1;
+ }
+ if (adjusted)
+ schedule_work(&hcd->usb_port_reset);
+
+ return adjusted;
+}
+
+/**
+ * Helper function to handle the port enable changed interrupt when the port
+ * becomes enabled. Checks if we need to adjust the PHY clock speed for low
+ * power and adjusts it if needed.
+ */
+static void port_enabled(struct dwc_hcd *hcd, u32 hprt0)
+{
+ if (hcd->core_if->core_params->host_support_fs_ls_low_power)
+ if (!adjusted_phy_clock_speed(hcd, hprt0))
+ hcd->flags.b.port_reset_change = 1;
+}
+
+/**
+ * There are multiple conditions that can cause a port interrupt. This function
+ * determines which interrupt conditions have occurred and handles them
+ * appropriately.
+ */
+static int dwc_otg_hcd_handle_port_intr(struct dwc_hcd *hcd)
+{
+ int retval = 0;
+ u32 hprt0;
+ u32 hprt0_modify;
+
+ hprt0 = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ hprt0_modify = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+
+ /*
+ * Clear appropriate bits in HPRT0 to clear the interrupt bit in
+ * GINTSTS
+ */
+ hprt0_modify = DWC_HPRT0_PRT_ENA_RW(hprt0_modify, 0);
+ hprt0_modify = DWC_HPRT0_PRT_CONN_DET_RW(hprt0_modify, 0);
+ hprt0_modify = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0_modify, 0);
+ hprt0_modify = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0_modify, 0);
+
+ /* Port connect detected interrupt */
+ if (DWC_HPRT0_PRT_CONN_DET_RD(hprt0)) {
+ /* Set the status flags and clear interrupt */
+ hcd->flags.b.port_connect_status_change = 1;
+ hcd->flags.b.port_connect_status = 1;
+ hprt0_modify = DWC_HPRT0_PRT_CONN_DET_RW(hprt0_modify, 1);
+
+ /* B-Device has connected, Delete the connection timer. */
+ del_timer_sync(&hcd->conn_timer);
+
+ /*
+ * The Hub driver asserts a reset when it sees port connect
+ * status change flag
+ */
+ retval |= 1;
+ }
+
+ /* Port enable changed interrupt */
+ if (DWC_HPRT0_PRT_ENA_DIS_CHG_RD(hprt0)) {
+ /* Set the internal flag if the port was disabled */
+ if (DWC_HPRT0_PRT_ENA_RD(hprt0))
+ port_enabled(hcd, hprt0);
+ else
+ hcd->flags.b.port_enable_change = 1;
+
+ /* Clear the interrupt */
+ hprt0_modify = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0_modify, 1);
+ retval |= 1;
+ }
+
+ /* Overcurrent change interrupt */
+ if (DWC_HPRT0_PRT_OVRCURR_CHG_RD(hprt0)) {
+ hcd->flags.b.port_over_current_change = 1;
+ hprt0_modify = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0_modify, 1);
+ retval |= 1;
+ }
+
+ /* Clear the port interrupts */
+ dwc_reg_write(hcd->core_if->host_if->hprt0, 0, hprt0_modify);
+ return retval;
+}
+
+/**
+ * Gets the actual length of a transfer after the transfer halts. halt_status
+ * holds the reason for the halt.
+ *
+ * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE, _short_read
+ * is set to 1 upon return if less than the requested number of bytes were
+ * transferred. Otherwise, _short_read is set to 0 upon return. _short_read may
+ * also be NULL on entry, in which case it remains unchanged.
+ */
+static u32 get_actual_xfer_length(struct dwc_hc *hc, ulong regs,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status,
+ int *_short_read)
+{
+ u32 hctsiz = 0;
+ u32 length;
+
+ if (_short_read)
+ *_short_read = 0;
+
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+ if (halt_status == DWC_OTG_HC_XFER_COMPLETE) {
+ if (hc->ep_is_in) {
+ length = hc->xfer_len - DWC_HCTSIZ_XFER_SIZE_RD(hctsiz);
+ if (_short_read)
+ *_short_read =
+ (DWC_HCTSIZ_XFER_SIZE_RD(hctsiz) != 0);
+ } else if (hc->qh->do_split) {
+ length = qtd->ssplit_out_xfer_count;
+ } else {
+ length = hc->xfer_len;
+ }
+ } else {
+ /*
+ * Must use the hctsiz.pktcnt field to determine how much data
+ * has been transferred. This field reflects the number of
+ * packets that have been transferred via the USB. This is
+ * always an integral number of packets if the transfer was
+ * halted before its normal completion. (Can't use the
+ * hctsiz.xfersize field because that reflects the number of
+ * bytes transferred via the AHB, not the USB).
+ */
+ length = (hc->start_pkt_count - DWC_HCTSIZ_PKT_CNT_RD(hctsiz)) *
+ hc->max_packet;
+ }
+ return length;
+}
+
+/**
+ * Updates the state of the URB after a Transfer Complete interrupt on the
+ * host channel. Updates the actual_length field of the URB based on the
+ * number of bytes transferred via the host channel. Sets the URB status
+ * if the data transfer is finished.
+ */
+static int update_urb_state_xfer_comp(struct dwc_hc *hc,
+ ulong regs, struct urb *urb,
+ struct dwc_qtd *qtd, int *status)
+{
+ int xfer_done = 0;
+ int short_read = 0;
+
+ urb->actual_length += get_actual_xfer_length(hc, regs, qtd,
+ DWC_OTG_HC_XFER_COMPLETE,
+ &short_read);
+
+ if (short_read || urb->actual_length == urb->transfer_buffer_length) {
+ xfer_done = 1;
+ if (short_read && (urb->transfer_flags & URB_SHORT_NOT_OK))
+ *status = -EREMOTEIO;
+ else
+ *status = 0;
+ }
+ return xfer_done;
+}
+
+/*
+ * Save the starting data toggle for the next transfer. The data toggle is
+ * saved in the QH for non-control transfers and it's saved in the QTD for
+ * control transfers.
+ */
+static void save_data_toggle(struct dwc_hc *hc, ulong regs, struct dwc_qtd *qtd)
+{
+ u32 hctsiz = 0;
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+
+ if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) {
+ struct dwc_qh *qh = hc->qh;
+
+ if (DWC_HCTSIZ_PKT_PID_RD(hctsiz) == DWC_HCTSIZ_DATA0)
+ qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+ else
+ qh->data_toggle = DWC_OTG_HC_PID_DATA1;
+ } else {
+ if (DWC_HCTSIZ_PKT_PID_RD(hctsiz) == DWC_HCTSIZ_DATA0)
+ qtd->data_toggle = DWC_OTG_HC_PID_DATA0;
+ else
+ qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
+ }
+}
+
+/**
+ * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic
+ * QHs, removes the QH from the active non-periodic schedule. If any QTDs are
+ * still linked to the QH, the QH is added to the end of the inactive
+ * non-periodic schedule. For periodic QHs, removes the QH from the periodic
+ * schedule if no more QTDs are linked to the QH.
+ */
+static void deactivate_qh(struct dwc_hcd *hcd, struct dwc_qh *qh, int free_qtd)
+{
+ int continue_split = 0;
+ struct dwc_qtd *qtd;
+
+ qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+ if (qtd->complete_split)
+ continue_split = 1;
+ else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID ||
+ qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END)
+ continue_split = 1;
+
+ if (free_qtd) {
+ dwc_otg_hcd_qtd_remove(qtd);
+ continue_split = 0;
+ }
+
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split);
+}
+
+/**
+ * Updates the state of an Isochronous URB when the transfer is stopped for
+ * any reason. The fields of the current entry in the frame descriptor array
+ * are set based on the transfer state and the input status. Completes the
+ * Isochronous URB if all the URB frames have been completed.
+ */
+static enum dwc_halt_status update_isoc_urb_state(struct dwc_hcd *hcd,
+ struct dwc_hc *hc, u32 regs,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status status)
+{
+ struct urb *urb = qtd->urb;
+ enum dwc_halt_status ret_val = status;
+ struct usb_iso_packet_descriptor *frame_desc;
+ frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index];
+
+ switch (status) {
+ case DWC_OTG_HC_XFER_COMPLETE:
+ frame_desc->status = 0;
+ frame_desc->actual_length =
+ get_actual_xfer_length(hc, regs, qtd, status, NULL);
+ break;
+ case DWC_OTG_HC_XFER_FRAME_OVERRUN:
+ urb->error_count++;
+ if (hc->ep_is_in)
+ frame_desc->status = -ENOSR;
+ else
+ frame_desc->status = -ECOMM;
+
+ frame_desc->actual_length = 0;
+ break;
+ case DWC_OTG_HC_XFER_BABBLE_ERR:
+ /* Don't need to update actual_length in this case. */
+ urb->error_count++;
+ frame_desc->status = -EOVERFLOW;
+ break;
+ case DWC_OTG_HC_XFER_XACT_ERR:
+ urb->error_count++;
+ frame_desc->status = -EPROTO;
+ frame_desc->actual_length =
+ get_actual_xfer_length(hc, regs, qtd, status, NULL);
+ default:
+ pr_err("%s: Unhandled halt_status (%d)\n", __func__, status);
+ BUG();
+ break;
+ }
+
+ if (++qtd->isoc_frame_index == urb->number_of_packets) {
+ /*
+ * urb->status is not used for isoc transfers.
+ * The individual frame_desc statuses are used instead.
+ */
+ dwc_otg_hcd_complete_urb(hcd, urb, 0);
+ ret_val = DWC_OTG_HC_XFER_URB_COMPLETE;
+ } else {
+ ret_val = DWC_OTG_HC_XFER_COMPLETE;
+ }
+ return ret_val;
+}
+
+/**
+ * Releases a host channel for use by other transfers. Attempts to select and
+ * queue more transactions since at least one host channel is available.
+ */
+static void release_channel(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status, int *must_free)
+{
+ enum dwc_transaction_type tr_type;
+ int free_qtd;
+ int deact = 1;
+ struct dwc_qh *qh;
+ int retry_delay = 1;
+
+ switch (halt_status) {
+ case DWC_OTG_HC_XFER_NYET:
+ case DWC_OTG_HC_XFER_NAK:
+ if (halt_status == DWC_OTG_HC_XFER_NYET)
+ retry_delay = nyet_deferral_delay;
+ else
+ retry_delay = nak_deferral_delay;
+ free_qtd = 0;
+ if (deferral_on && hc->do_split) {
+ qh = hc->qh;
+ if (qh)
+ deact = dwc_otg_hcd_qh_deferr(hcd, qh,
+ retry_delay);
+ }
+ break;
+ case DWC_OTG_HC_XFER_URB_COMPLETE:
+ free_qtd = 1;
+ break;
+ case DWC_OTG_HC_XFER_AHB_ERR:
+ case DWC_OTG_HC_XFER_STALL:
+ case DWC_OTG_HC_XFER_BABBLE_ERR:
+ free_qtd = 1;
+ break;
+ case DWC_OTG_HC_XFER_XACT_ERR:
+ if (qtd->error_count >= 3) {
+ free_qtd = 1;
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPROTO);
+ } else {
+ free_qtd = 0;
+ }
+ break;
+ case DWC_OTG_HC_XFER_URB_DEQUEUE:
+ /*
+ * The QTD has already been removed and the QH has been
+ * deactivated. Don't want to do anything except release the
+ * host channel and try to queue more transfers.
+ */
+ goto cleanup;
+ case DWC_OTG_HC_XFER_NO_HALT_STATUS:
+ pr_err("%s: No halt_status, channel %d\n", __func__,
+ hc->hc_num);
+ free_qtd = 0;
+ break;
+ default:
+ free_qtd = 0;
+ break;
+ }
+ if (free_qtd)
+ /* must_free pre-initialized to zero */
+ *must_free = 1;
+ if (deact)
+ deactivate_qh(hcd, hc->qh, free_qtd);
+
+cleanup:
+ /*
+ * Release the host channel for use by other transfers. The cleanup
+ * function clears the channel interrupt enables and conditions, so
+ * there's no need to clear the Channel Halted interrupt separately.
+ */
+ dwc_otg_hc_cleanup(hcd->core_if, hc);
+ list_add_tail(&hc->hc_list_entry, &hcd->free_hc_list);
+ hcd->available_host_channels++;
+ /* Try to queue more transfers now that there's a free channel. */
+ if (!erratum_usb09_patched) {
+ tr_type = dwc_otg_hcd_select_transactions(hcd);
+ if (tr_type != DWC_OTG_TRANSACTION_NONE)
+ dwc_otg_hcd_queue_transactions(hcd, tr_type);
+ }
+}
+
+/**
+ * Halts a host channel. If the channel cannot be halted immediately because
+ * the request queue is full, this function ensures that the FIFO empty
+ * interrupt for the appropriate queue is enabled so that the halt request can
+ * be queued when there is space in the request queue.
+ *
+ * This function may also be called in DMA mode. In that case, the channel is
+ * simply released since the core always halts the channel automatically in
+ * DMA mode.
+ */
+static void halt_channel(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ struct dwc_qtd *qtd, enum dwc_halt_status halt_status,
+ int *must_free)
+{
+ if (hcd->core_if->dma_enable) {
+ release_channel(hcd, hc, qtd, halt_status, must_free);
+ return;
+ }
+
+ /* Slave mode processing... */
+ dwc_otg_hc_halt(hcd->core_if, hc, halt_status);
+ if (hc->halt_on_queue) {
+ u32 gintmsk = 0;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+ hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+ /*
+ * Make sure the Non-periodic Tx FIFO empty interrupt
+ * is enabled so that the non-periodic schedule will
+ * be processed.
+ */
+ gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, gintmsk);
+ } else {
+ /*
+ * Move the QH from the periodic queued schedule to
+ * the periodic assigned schedule. This allows the
+ * halt to be queued when the periodic schedule is
+ * processed.
+ */
+ list_move(&hc->qh->qh_list_entry,
+ &hcd->periodic_sched_assigned);
+
+ /*
+ * Make sure the Periodic Tx FIFO Empty interrupt is
+ * enabled so that the periodic schedule will be
+ * processed.
+ */
+ gintmsk |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, gintmsk);
+ }
+ }
+}
+
+/**
+ * Performs common cleanup for non-periodic transfers after a Transfer
+ * Complete interrupt. This function should be called after any endpoint type
+ * specific handling is finished to release the host channel.
+ */
+static void complete_non_periodic_xfer(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status,
+ int *must_free)
+{
+ u32 hcint;
+
+ qtd->error_count = 0;
+ hcint = dwc_reg_read(regs, DWC_HCINT);
+ if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) {
+ u32 hcint_clear = 0;
+
+ hcint_clear = DWC_HCINT_NYET_RESP_REC_RW(hcint_clear, 1);
+ /*
+ * Got a NYET on the last transaction of the transfer. This
+ * means that the endpoint should be in the PING state at the
+ * beginning of the next transfer.
+ */
+ hc->qh->ping_state = 1;
+ dwc_reg_write(regs, DWC_HCINT, hcint_clear);
+ }
+
+ /*
+ * Always halt and release the host channel to make it available for
+ * more transfers. There may still be more phases for a control
+ * transfer or more data packets for a bulk transfer at this point,
+ * but the host channel is still halted. A channel will be reassigned
+ * to the transfer when the non-periodic schedule is processed after
+ * the channel is released. This allows transactions to be queued
+ * properly via dwc_otg_hcd_queue_transactions, which also enables the
+ * Tx FIFO Empty interrupt if necessary.
+ *
+ * IN transfers in Slave mode require an explicit disable to
+ * halt the channel. (In DMA mode, this call simply releases
+ * the channel.)
+ *
+ * The channel is automatically disabled by the core for OUT
+ * transfers in Slave mode.
+ */
+ if (hc->ep_is_in)
+ halt_channel(hcd, hc, qtd, halt_status, must_free);
+ else
+ release_channel(hcd, hc, qtd, halt_status, must_free);
+}
+
+/**
+ * Performs common cleanup for periodic transfers after a Transfer Complete
+ * interrupt. This function should be called after any endpoint type specific
+ * handling is finished to release the host channel.
+ */
+static void complete_periodic_xfer(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status,
+ int *must_free)
+{
+ u32 hctsiz = 0;
+
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+ qtd->error_count = 0;
+
+ /*
+ * For OUT transfers and 0 packet count, the Core halts the channel,
+ * otherwise, Flush any outstanding requests from the Tx queue.
+ */
+ if (!hc->ep_is_in || (DWC_HCTSIZ_PKT_CNT_RD(hctsiz) == 0))
+ release_channel(hcd, hc, qtd, halt_status, must_free);
+ else
+ halt_channel(hcd, hc, qtd, halt_status, must_free);
+}
+
+/**
+ * Handles a host channel Transfer Complete interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_xfercomp_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ int *must_free)
+{
+ int urb_xfer_done;
+ enum dwc_halt_status halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ struct urb *urb = qtd->urb;
+ int pipe_type = usb_pipetype(urb->pipe);
+ int status = -EINPROGRESS;
+ u32 hcintmsk = 0;
+
+ /* Handle xfer complete on CSPLIT. */
+ if (hc->qh->do_split)
+ qtd->complete_split = 0;
+
+ /* Update the QTD and URB states. */
+ switch (pipe_type) {
+ case PIPE_CONTROL:
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ if (urb->transfer_buffer_length > 0)
+ qtd->control_phase = DWC_OTG_CONTROL_DATA;
+ else
+ qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+ halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ urb_xfer_done = update_urb_state_xfer_comp(hc, regs,
+ urb, qtd,
+ &status);
+ if (urb_xfer_done)
+ qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+ else
+ save_data_toggle(hc, regs, qtd);
+ halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ if (status == -EINPROGRESS)
+ status = 0;
+ dwc_otg_hcd_complete_urb(hcd, urb, status);
+ halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+ break;
+ }
+ complete_non_periodic_xfer(hcd, hc, regs, qtd,
+ halt_status, must_free);
+ break;
+ case PIPE_BULK:
+ urb_xfer_done = update_urb_state_xfer_comp(hc, regs, urb, qtd,
+ &status);
+ if (urb_xfer_done) {
+ dwc_otg_hcd_complete_urb(hcd, urb, status);
+ halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+ } else {
+ halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ }
+
+ save_data_toggle(hc, regs, qtd);
+ complete_non_periodic_xfer(hcd, hc, regs, qtd,
+ halt_status, must_free);
+ break;
+ case PIPE_INTERRUPT:
+ update_urb_state_xfer_comp(hc, regs, urb, qtd, &status);
+ /*
+ * Interrupt URB is done on the first transfer complete
+ * interrupt.
+ */
+ dwc_otg_hcd_complete_urb(hcd, urb, status);
+ save_data_toggle(hc, regs, qtd);
+ complete_periodic_xfer(hcd, hc, regs, qtd,
+ DWC_OTG_HC_XFER_URB_COMPLETE, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+ halt_status = update_isoc_urb_state(hcd, hc, regs, qtd,
+ DWC_OTG_HC_XFER_COMPLETE);
+ }
+ complete_periodic_xfer(hcd, hc, regs, qtd,
+ halt_status, must_free);
+ break;
+ }
+
+ /* disable xfercompl */
+ hcintmsk = DWC_HCINTMSK_TXFER_CMPL_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel STALL interrupt. This handler may be called in
+ * either DMA mode or Slave mode.
+ */
+static int handle_hc_stall_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ struct urb *urb = qtd->urb;
+ int pipe_type = usb_pipetype(urb->pipe);
+ u32 hcintmsk = 0;
+
+ if (pipe_type == PIPE_CONTROL)
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPIPE);
+
+ if (pipe_type == PIPE_BULK || pipe_type == PIPE_INTERRUPT) {
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPIPE);
+ /*
+ * USB protocol requires resetting the data toggle for bulk
+ * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT)
+ * setup command is issued to the endpoint. Anticipate the
+ * CLEAR_FEATURE command since a STALL has occurred and reset
+ * the data toggle now.
+ */
+ hc->qh->data_toggle = 0;
+ }
+
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL, must_free);
+ /* disable stall */
+ hcintmsk = DWC_HCINTMSK_STALL_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Updates the state of the URB when a transfer has been stopped due to an
+ * abnormal condition before the transfer completes. Modifies the
+ * actual_length field of the URB to reflect the number of bytes that have
+ * actually been transferred via the host channel.
+ */
+static void update_urb_state_xfer_intr(struct dwc_hc *hc,
+ u32 regs, struct urb *urb,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status sts)
+{
+ u32 xfr_len = get_actual_xfer_length(hc, regs, qtd, sts, NULL);
+ urb->actual_length += xfr_len;
+}
+
+/**
+ * Handles a host channel NAK interrupt. This handler may be called in either
+ * DMA mode or Slave mode.
+ */
+static int handle_hc_nak_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+
+ /*
+ * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and
+ * interrupt. Re-start the SSPLIT transfer.
+ */
+ if (hc->do_split) {
+ if (hc->complete_split)
+ qtd->error_count = 0;
+
+ qtd->complete_split = 0;
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free);
+ goto handle_nak_done;
+ }
+ switch (usb_pipetype(qtd->urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ if (hcd->core_if->dma_enable && hc->ep_is_in) {
+ /*
+ * NAK interrupts are enabled on bulk/control IN
+ * transfers in DMA mode for the sole purpose of
+ * resetting the error count after a transaction error
+ * occurs. The core will continue transferring data.
+ */
+ qtd->error_count = 0;
+ goto handle_nak_done;
+ }
+
+ /*
+ * NAK interrupts normally occur during OUT transfers in DMA
+ * or Slave mode. For IN transfers, more requests will be
+ * queued as request queue space is available.
+ */
+ qtd->error_count = 0;
+ if (!hc->qh->ping_state) {
+ update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd,
+ DWC_OTG_HC_XFER_NAK);
+
+ save_data_toggle(hc, regs, qtd);
+ if (qtd->urb->dev->speed == USB_SPEED_HIGH)
+ hc->qh->ping_state = 1;
+ }
+
+ /*
+ * Halt the channel so the transfer can be re-started from
+ * the appropriate point or the PING protocol will
+ * start/continue.
+ */
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free);
+ break;
+ case PIPE_INTERRUPT:
+ qtd->error_count = 0;
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ /* Should never get called for isochronous transfers. */
+ BUG();
+ break;
+ }
+
+handle_nak_done:
+ /* disable nak */
+ hcintmsk = DWC_HCINTMSK_NAK_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Helper function for handle_hc_ack_intr(). Sets the split values for an ACK
+ * on SSPLIT for ISOC OUT.
+ */
+static void set_isoc_out_vals(struct dwc_hc *hc, struct dwc_qtd *qtd)
+{
+ struct usb_iso_packet_descriptor *frame_desc;
+
+ switch (hc->xact_pos) {
+ case DWC_HCSPLIT_XACTPOS_ALL:
+ break;
+ case DWC_HCSPLIT_XACTPOS_END:
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ qtd->isoc_split_offset = 0;
+ break;
+ case DWC_HCSPLIT_XACTPOS_BEGIN:
+ case DWC_HCSPLIT_XACTPOS_MID:
+ /*
+ * For BEGIN or MID, calculate the length for the next
+ * microframe to determine the correct SSPLIT token, either MID
+ * or END.
+ */
+ frame_desc = &qtd->urb->iso_frame_desc[qtd->isoc_frame_index];
+ qtd->isoc_split_offset += 188;
+
+ if ((frame_desc->length - qtd->isoc_split_offset) <= 188)
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_END;
+ else
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_MID;
+
+ break;
+ }
+}
+
+/**
+ * Handles a host channel ACK interrupt. This interrupt is enabled when
+ * performing the PING protocol in Slave mode, when errors occur during
+ * either Slave mode or DMA mode, and during Start Split transactions.
+ */
+static int handle_hc_ack_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+
+ if (hc->do_split) {
+ /* Handle ACK on SSPLIT. ACK should not occur in CSPLIT. */
+ if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP)
+ qtd->ssplit_out_xfer_count = hc->xfer_len;
+
+ /* Don't need complete for isochronous out transfers. */
+ if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in))
+ qtd->complete_split = 1;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)
+ set_isoc_out_vals(hc, qtd);
+ else
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK,
+ must_free);
+ } else {
+ qtd->error_count = 0;
+ if (hc->qh->ping_state) {
+ hc->qh->ping_state = 0;
+
+ /*
+ * Halt the channel so the transfer can be re-started
+ * from the appropriate point. This only happens in
+ * Slave mode. In DMA mode, the ping_state is cleared
+ * when the transfer is started because the core
+ * automatically executes the PING, then the transfer.
+ */
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK,
+ must_free);
+ }
+ }
+
+ /*
+ * If the ACK occurred when _not_ in the PING state, let the channel
+ * continue transferring data after clearing the error count.
+ */
+ /* disable ack */
+ hcintmsk = DWC_HCINTMSK_ACK_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel NYET interrupt. This interrupt should only occur on
+ * Bulk and Control OUT endpoints and for complete split transactions. If a
+ * NYET occurs at the same time as a Transfer Complete interrupt, it is
+ * handled in the xfercomp interrupt handler, not here. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_nyet_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+ u32 hcint_clear = 0;
+
+ /*
+ * NYET on CSPLIT
+ * re-do the CSPLIT immediately on non-periodic
+ */
+ if (hc->do_split && hc->complete_split) {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ int frnum =
+ dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd
+ (hcd));
+ if (dwc_full_frame_num(frnum) !=
+ dwc_full_frame_num(hc->qh->sched_frame)) {
+ qtd->complete_split = 0;
+ halt_channel(hcd, hc, qtd,
+ DWC_OTG_HC_XFER_XACT_ERR,
+ must_free);
+ goto handle_nyet_done;
+ }
+ }
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET, must_free);
+ goto handle_nyet_done;
+ }
+ hc->qh->ping_state = 1;
+ qtd->error_count = 0;
+ update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd,
+ DWC_OTG_HC_XFER_NYET);
+ save_data_toggle(hc, regs, qtd);
+ /*
+ * Halt the channel and re-start the transfer so the PING
+ * protocol will start.
+ */
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET, must_free);
+
+handle_nyet_done:
+ /* disable nyet */
+ hcintmsk = DWC_HCINTMSK_NYET_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+ /* clear nyet */
+ hcint_clear = DWC_HCINT_NYET_RESP_REC_RW(hcint_clear, 1);
+ dwc_reg_write(regs, DWC_HCINT, hcint_clear);
+ return 1;
+}
+
+/**
+ * Handles a host channel babble interrupt. This handler may be called in
+ * either DMA mode or Slave mode.
+ */
+static int handle_hc_babble_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+
+ if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EOVERFLOW);
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR,
+ must_free);
+ } else {
+ enum dwc_halt_status halt_status;
+ halt_status = update_isoc_urb_state(hcd, hc, regs, qtd,
+ DWC_OTG_HC_XFER_BABBLE_ERR);
+ halt_channel(hcd, hc, qtd, halt_status, must_free);
+ }
+ /* disable bblerr */
+ hcintmsk = DWC_HCINTMSK_BBL_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+ return 1;
+}
+
+/**
+ * Handles a host channel AHB error interrupt. This handler is only called in
+ * DMA mode.
+ */
+static int handle_hc_ahberr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd)
+{
+ u32 hcchar;
+ u32 hcsplt;
+ u32 hctsiz = 0;
+ u32 hcdma;
+ struct urb *urb = qtd->urb;
+ u32 hcintmsk = 0;
+
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+ hcsplt = dwc_reg_read(regs, DWC_HCSPLT);
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+ hcdma = dwc_reg_read(regs, DWC_HCDMA);
+
+ pr_err("AHB ERROR, Channel %d\n", hc->hc_num);
+ pr_err(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar, hcsplt);
+ pr_err(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz, hcdma);
+
+ pr_err(" Device address: %d\n", usb_pipedevice(urb->pipe));
+ pr_err(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe),
+ (usb_pipein(urb->pipe) ? "IN" : "OUT"));
+
+ pr_err(" Endpoint type: %s\n", pipetype_str(urb->pipe));
+ pr_err(" Speed: %s\n", dev_speed_str(urb->dev->speed));
+ pr_err(" Max packet size: %d\n",
+ usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)));
+ pr_err(" Data buffer length: %d\n", urb->transfer_buffer_length);
+ pr_err(" Transfer buffer: %p, Transfer DMA: %p\n",
+ urb->transfer_buffer, (void *)(u32) urb->transfer_dma);
+ pr_err(" Setup buffer: %p, Setup DMA: %p\n",
+ urb->setup_packet, (void *)(u32) urb->setup_dma);
+ pr_err(" Interval: %d\n", urb->interval);
+
+ dwc_otg_hcd_complete_urb(hcd, urb, -EIO);
+
+ /*
+ * Force a channel halt. Don't call halt_channel because that won't
+ * write to the HCCHARn register in DMA mode to force the halt.
+ */
+ dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR);
+ /* disable ahberr */
+ hcintmsk = DWC_HCINTMSK_AHB_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel transaction error interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_xacterr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ enum dwc_halt_status status = DWC_OTG_HC_XFER_XACT_ERR;
+ u32 hcintmsk = 0;
+
+ switch (usb_pipetype(qtd->urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ qtd->error_count++;
+ if (!hc->qh->ping_state) {
+ update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd,
+ status);
+ save_data_toggle(hc, regs, qtd);
+
+ if (!hc->ep_is_in && qtd->urb->dev->speed ==
+ USB_SPEED_HIGH)
+ hc->qh->ping_state = 1;
+ }
+ /*
+ * Halt the channel so the transfer can be re-started from
+ * the appropriate point or the PING protocol will start.
+ */
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ case PIPE_INTERRUPT:
+ qtd->error_count++;
+ if (hc->do_split && hc->complete_split)
+ qtd->complete_split = 0;
+
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ status = update_isoc_urb_state(hcd, hc, regs, qtd, status);
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ }
+ /* Disable xacterr */
+ hcintmsk = DWC_HCINTMSK_TRANS_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel frame overrun interrupt. This handler may be called
+ * in either DMA mode or Slave mode.
+ */
+static int handle_hc_frmovrun_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd,
+ int *must_free)
+{
+ enum dwc_halt_status status = DWC_OTG_HC_XFER_FRAME_OVERRUN;
+ u32 hcintmsk = 0;
+
+ switch (usb_pipetype(qtd->urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ break;
+ case PIPE_INTERRUPT:
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ status = update_isoc_urb_state(hcd, hc, regs, qtd, status);
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ }
+ /* Disable frmovrun */
+ hcintmsk = DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel data toggle error interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_datatglerr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd)
+{
+ u32 hcintmsk = 0;
+
+ if (hc->ep_is_in)
+ qtd->error_count = 0;
+ else
+ pr_err("Data Toggle Error on OUT transfer, channel "
+ "%d\n", hc->hc_num);
+
+ /* disable datatglerr */
+ hcintmsk = DWC_HCINTMSK_DATA_TOG_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host Channel Halted interrupt in DMA mode. This handler
+ * determines the reason the channel halted and proceeds accordingly.
+ */
+static void handle_hc_chhltd_intr_dma(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ int *must_free)
+{
+ u32 hcint;
+ u32 hcintmsk = 0;
+
+ if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+ hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR) {
+ /*
+ * Just release the channel. A dequeue can happen on a
+ * transfer timeout. In the case of an AHB Error, the channel
+ * was forced to halt because there's no way to gracefully
+ * recover.
+ */
+ release_channel(hcd, hc, qtd, hc->halt_status, must_free);
+ return;
+ }
+
+ /* Read the HCINTn register to determine the cause for the halt. */
+ hcint = dwc_reg_read(regs, DWC_HCINT);
+ hcintmsk = dwc_reg_read(regs, DWC_HCINTMSK);
+ if (DWC_HCINT_TXFER_CMPL_RD(hcint)) {
+ /*
+ * This is here because of a possible hardware bug. Spec
+ * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT
+ * interrupt w/ACK bit set should occur, but I only see the
+ * XFERCOMP bit, even with it masked out. This is a workaround
+ * for that behavior. Should fix this when hardware is fixed.
+ */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)
+ handle_hc_ack_intr(hcd, hc, regs, qtd, must_free);
+
+ handle_hc_xfercomp_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_STALL_RESP_REC_RD(hcint)) {
+ handle_hc_stall_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_TRANS_ERR_RD(hcint)) {
+ /*
+ * Must handle xacterr before nak or ack. Could get a xacterr
+ * at the same time as either of these on a BULK/CONTROL OUT
+ * that started with a PING. The xacterr takes precedence.
+ */
+ handle_hc_xacterr_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) {
+ /*
+ * Must handle nyet before nak or ack. Could get a nyet at the
+ * same time as either of those on a BULK/CONTROL OUT that
+ * started with a PING. The nyet takes precedence.
+ */
+ handle_hc_nyet_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_BBL_ERR_RD(hcint)) {
+ handle_hc_babble_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_FRAME_OVERN_ERR_RD(hcint)) {
+ handle_hc_frmovrun_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_DATA_TOG_ERR_RD(hcint)) {
+ handle_hc_datatglerr_intr(hcd, hc, regs, qtd);
+ hc->qh->data_toggle = 0;
+ halt_channel(hcd, hc, qtd, hc->halt_status, must_free);
+ } else if (DWC_HCINT_NAK_RESP_REC_RD(hcint) &&
+ !DWC_HCINTMSK_NAK_RESP_REC_RD(hcintmsk)) {
+ /*
+ * If nak is not masked, it's because a non-split IN transfer
+ * is in an error state. In that case, the nak is handled by
+ * the nak interrupt handler, not here. Handle nak here for
+ * BULK/CONTROL OUT transfers, which halt on a NAK to allow
+ * rewinding the buffer pointer.
+ */
+ handle_hc_nak_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_ACK_RESP_REC_RD(hcint) &&
+ !DWC_HCINTMSK_ACK_RESP_REC_RD(hcintmsk)) {
+ /*
+ * If ack is not masked, it's because a non-split IN transfer
+ * is in an error state. In that case, the ack is handled by
+ * the ack interrupt handler, not here. Handle ack here for
+ * split transfers. Start splits halt on ACK.
+ */
+ handle_hc_ack_intr(hcd, hc, regs, qtd, must_free);
+ } else {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ /*
+ * A periodic transfer halted with no other channel
+ * interrupts set. Assume it was halted by the core
+ * because it could not be completed in its scheduled
+ * (micro)frame.
+ */
+ halt_channel(hcd, hc, qtd,
+ DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE,
+ must_free);
+ } else {
+ pr_err("%s: Channel %d, DMA Mode -- ChHltd "
+ "set, but reason for halting is unknown, "
+ "hcint 0x%08x, intsts 0x%08x\n",
+ __func__, hc->hc_num, hcint,
+ dwc_reg_read(gintsts_reg(hcd), 0));
+ }
+ }
+}
+
+/**
+ * Handles a host channel Channel Halted interrupt.
+ *
+ * In slave mode, this handler is called only when the driver specifically
+ * requests a halt. This occurs during handling other host channel interrupts
+ * (e.g. nak, xacterr, stall, nyet, etc.).
+ *
+ * In DMA mode, this is the interrupt that occurs when the core has finished
+ * processing a transfer on a channel. Other host channel interrupts (except
+ * ahberr) are disabled in DMA mode.
+ */
+static int handle_hc_chhltd_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd, int *must_free)
+{
+ if (hcd->core_if->dma_enable)
+ handle_hc_chhltd_intr_dma(hcd, hc, regs, qtd, must_free);
+ else
+ release_channel(hcd, hc, qtd, hc->halt_status, must_free);
+
+ return 1;
+}
+
+/* Handles interrupt for a specific Host Channel */
+static int dwc_otg_hcd_handle_hc_n_intr(struct dwc_hcd *hcd, u32 num)
+{
+ int must_free = 0;
+ int retval = 0;
+ u32 hcint;
+ u32 hcintmsk = 0;
+ struct dwc_hc *hc;
+ ulong hc_regs;
+ struct dwc_qtd *qtd;
+
+ hc = hcd->hc_ptr_array[num];
+ hc_regs = hcd->core_if->host_if->hc_regs[num];
+ qtd = list_entry(hc->qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+
+ hcint = dwc_reg_read(hc_regs, DWC_HCINT);
+ hcintmsk = dwc_reg_read(hc_regs, DWC_HCINTMSK);
+
+ hcint = hcint & hcintmsk;
+ if (!hcd->core_if->dma_enable && DWC_HCINT_CHAN_HALTED_RD(hcint)
+ && hcint != 0x2)
+ hcint = DWC_HCINT_CHAN_HALTED_RW(hcint, 0);
+
+ if (DWC_HCINT_TXFER_CMPL_RD(hcint)) {
+ retval |= handle_hc_xfercomp_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ /*
+ * If NYET occurred at same time as Xfer Complete, the NYET is
+ * handled by the Xfer Complete interrupt handler. Don't want
+ * to call the NYET interrupt handler in this case.
+ */
+ hcint = DWC_HCINT_NYET_RESP_REC_RW(hcint, 0);
+ }
+
+ if (DWC_HCINT_CHAN_HALTED_RD(hcint))
+ retval |= handle_hc_chhltd_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_AHB_ERR_RD(hcint))
+ retval |= handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
+ if (DWC_HCINT_STALL_RESP_REC_RD(hcint))
+ retval |= handle_hc_stall_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_NAK_RESP_REC_RD(hcint))
+ retval |= handle_hc_nak_intr(hcd, hc, hc_regs, qtd, &must_free);
+ if (DWC_HCINT_ACK_RESP_REC_RD(hcint))
+ retval |= handle_hc_ack_intr(hcd, hc, hc_regs, qtd, &must_free);
+ if (DWC_HCINT_NYET_RESP_REC_RD(hcint))
+ retval |= handle_hc_nyet_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_TRANS_ERR_RD(hcint))
+ retval |= handle_hc_xacterr_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_BBL_ERR_RD(hcint))
+ retval |= handle_hc_babble_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_FRAME_OVERN_ERR_RD(hcint))
+ retval |= handle_hc_frmovrun_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_DATA_TOG_ERR_RD(hcint))
+ retval |= handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd);
+
+ if (must_free)
+ /* Free the qtd here now that we are done using it. */
+ dwc_otg_hcd_qtd_free(qtd);
+ return retval;
+}
+
+/**
+ * This function returns the Host All Channel Interrupt register
+ */
+static inline u32 dwc_otg_read_host_all_channels_intr(struct core_if
+ *core_if)
+{
+ return dwc_reg_read(core_if->host_if->host_global_regs, DWC_HAINT);
+}
+
+/**
+ * This interrupt indicates that one or more host channels has a pending
+ * interrupt. There are multiple conditions that can cause each host channel
+ * interrupt. This function determines which conditions have occurred for each
+ * host channel interrupt and handles them appropriately.
+ */
+static int dwc_otg_hcd_handle_hc_intr(struct dwc_hcd *hcd)
+{
+ u32 i;
+ int retval = 0;
+ u32 haint;
+
+ /*
+ * Clear appropriate bits in HCINTn to clear the interrupt bit in
+ * GINTSTS
+ */
+ haint = dwc_otg_read_host_all_channels_intr(hcd->core_if);
+ for (i = 0; i < hcd->core_if->core_params->host_channels; i++)
+ if (DWC_HAINT_RD(haint) & (1 << i))
+ retval |= dwc_otg_hcd_handle_hc_n_intr(hcd, i);
+
+ return retval;
+}
+
+/* This function handles interrupts for the HCD.*/
+int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd)
+{
+ int ret = 0;
+ struct core_if *core_if = hcd->core_if;
+ u32 gintsts;
+
+ /* Check if HOST Mode */
+ if (dwc_otg_is_host_mode(core_if)) {
+ spin_lock(&hcd->lock);
+ gintsts = dwc_otg_read_core_intr(core_if);
+ if (!gintsts) {
+ spin_unlock(&hcd->lock);
+ return IRQ_NONE;
+ }
+
+ if (gintsts & DWC_INTMSK_STRT_OF_FRM)
+ ret |= dwc_otg_hcd_handle_sof_intr(hcd);
+ if (gintsts & DWC_INTMSK_RXFIFO_NOT_EMPT)
+ ret |= dwc_otg_hcd_handle_rx_status_q_level_intr(hcd);
+ if (gintsts & DWC_INTMSK_NP_TXFIFO_EMPT)
+ ret |= dwc_otg_hcd_handle_np_tx_fifo_empty_intr(hcd);
+ if (gintsts & DWC_INTMSK_HST_PORT)
+ ret |= dwc_otg_hcd_handle_port_intr(hcd);
+ if (gintsts & DWC_INTMSK_HST_CHAN)
+ ret |= dwc_otg_hcd_handle_hc_intr(hcd);
+ if (gintsts & DWC_INTMSK_P_TXFIFO_EMPTY)
+ ret |= dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(hcd);
+
+ spin_unlock(&hcd->lock);
+ }
+ return ret;
+}
diff --git a/drivers/usb/dwc/hcd_queue.c b/drivers/usb/dwc/hcd_queue.c
new file mode 100644
index 0000000..67f0409
--- /dev/null
+++ b/drivers/usb/dwc/hcd_queue.c
@@ -0,0 +1,696 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+/*
+ * This file contains the functions to manage Queue Heads and Queue
+ * Transfer Descriptors.
+ */
+
+#include "hcd.h"
+
+static inline int is_fs_ls(enum usb_device_speed speed)
+{
+ return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW;
+}
+
+/* Allocates memory for a QH structure. */
+static inline struct dwc_qh *dwc_otg_hcd_qh_alloc(void)
+{
+ return kmalloc(sizeof(struct dwc_qh), GFP_ATOMIC);
+}
+
+/**
+ * Initializes a QH structure to initialize the QH.
+ */
+#define SCHEDULE_SLOP 10
+static void dwc_otg_hcd_qh_init(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ struct urb *urb)
+{
+ memset(qh, 0, sizeof(struct dwc_qh));
+
+ /* Initialize QH */
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ qh->ep_type = USB_ENDPOINT_XFER_CONTROL;
+ break;
+ case PIPE_BULK:
+ qh->ep_type = USB_ENDPOINT_XFER_BULK;
+ break;
+ case PIPE_ISOCHRONOUS:
+ qh->ep_type = USB_ENDPOINT_XFER_ISOC;
+ break;
+ case PIPE_INTERRUPT:
+ qh->ep_type = USB_ENDPOINT_XFER_INT;
+ break;
+ }
+
+ qh->ep_is_in = usb_pipein(urb->pipe) ? 1 : 0;
+ qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+ qh->maxp = usb_maxpacket(urb->dev, urb->pipe, !(usb_pipein(urb->pipe)));
+
+ INIT_LIST_HEAD(&qh->qtd_list);
+ INIT_LIST_HEAD(&qh->qh_list_entry);
+
+ qh->channel = NULL;
+ qh->speed = urb->dev->speed;
+
+ /*
+ * FS/LS Enpoint on HS Hub NOT virtual root hub
+ */
+ qh->do_split = 0;
+ if (is_fs_ls(urb->dev->speed) && urb->dev->tt && urb->dev->tt->hub &&
+ urb->dev->tt->hub->devnum != 1)
+ qh->do_split = 1;
+
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT ||
+ qh->ep_type == USB_ENDPOINT_XFER_ISOC) {
+ /* Compute scheduling parameters once and save them. */
+ u32 hprt;
+ int bytecount = dwc_hb_mult(qh->maxp) *
+ dwc_max_packet(qh->maxp);
+
+ qh->usecs = NS_TO_US(usb_calc_bus_time(urb->dev->speed,
+ usb_pipein(urb->pipe),
+ (qh->ep_type ==
+ USB_ENDPOINT_XFER_ISOC),
+ bytecount));
+
+ /* Start in a slightly future (micro)frame. */
+ qh->sched_frame = dwc_frame_num_inc(hcd->frame_number,
+ SCHEDULE_SLOP);
+ qh->interval = urb->interval;
+
+ hprt = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ if (DWC_HPRT0_PRT_SPD_RD(hprt) == DWC_HPRT0_PRTSPD_HIGH_SPEED &&
+ is_fs_ls(urb->dev->speed)) {
+ qh->interval *= 8;
+ qh->sched_frame |= 0x7;
+ qh->start_split_frame = qh->sched_frame;
+ }
+ }
+}
+
+/**
+ * This function allocates and initializes a QH.
+ */
+static struct dwc_qh *dwc_otg_hcd_qh_create(struct dwc_hcd *hcd,
+ struct urb *urb)
+{
+ struct dwc_qh *qh;
+
+ /* Allocate memory */
+ qh = dwc_otg_hcd_qh_alloc();
+ if (qh == NULL)
+ return NULL;
+
+ dwc_otg_hcd_qh_init(hcd, qh, urb);
+ return qh;
+}
+
+/**
+ * Free each QTD in the QH's QTD-list then free the QH. QH should already be
+ * removed from a list. QTD list should already be empty if called from URB
+ * Dequeue.
+ */
+void dwc_otg_hcd_qh_free(struct dwc_qh *qh)
+{
+ struct dwc_qtd *qtd;
+ struct list_head *pos, *temp;
+
+ /* Free each QTD in the QTD list */
+ list_for_each_safe(pos, temp, &qh->qtd_list) {
+ list_del(pos);
+ qtd = dwc_list_to_qtd(pos);
+ dwc_otg_hcd_qtd_free(qtd);
+ }
+ kfree(qh);
+}
+
+/**
+ * Microframe scheduler
+ * track the total use in hcd->frame_usecs
+ * keep each qh use in qh->frame_usecs
+ * when surrendering the qh then donate the time back
+ */
+static const u16 max_uframe_usecs[] = { 100, 100, 100, 100, 100, 100, 30, 0 };
+
+/*
+ * called from dwc_otg_hcd.c:dwc_otg_hcd_init
+ */
+int init_hcd_usecs(struct dwc_hcd *hcd)
+{
+ int i;
+
+ for (i = 0; i < 8; i++)
+ hcd->frame_usecs[i] = max_uframe_usecs[i];
+
+ return 0;
+}
+
+static int find_single_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int i;
+ u16 utime;
+ int t_left;
+ int ret;
+ int done;
+
+ ret = -1;
+ utime = qh->usecs;
+ t_left = utime;
+ i = 0;
+ done = 0;
+ while (done == 0) {
+ /* At the start hcd->frame_usecs[i] = max_uframe_usecs[i]; */
+ if (utime <= hcd->frame_usecs[i]) {
+ hcd->frame_usecs[i] -= utime;
+ qh->frame_usecs[i] += utime;
+ t_left -= utime;
+ ret = i;
+ done = 1;
+ return ret;
+ } else {
+ i++;
+ if (i == 8) {
+ done = 1;
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+/*
+ * use this for FS apps that can span multiple uframes
+ */
+static int find_multi_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int i;
+ int j;
+ u16 utime;
+ int t_left;
+ int ret;
+ int done;
+ u16 xtime;
+
+ ret = -1;
+ utime = qh->usecs;
+ t_left = utime;
+ i = 0;
+ done = 0;
+loop:
+ while (done == 0) {
+ if (hcd->frame_usecs[i] <= 0) {
+ i++;
+ if (i == 8) {
+ done = 1;
+ ret = -1;
+ }
+ goto loop;
+ }
+
+ /*
+ * We need n consequtive slots so use j as a start slot.
+ * j plus j+1 must be enough time (for now)
+ */
+ xtime = hcd->frame_usecs[i];
+ for (j = i + 1; j < 8; j++) {
+ /*
+ * if we add this frame remaining time to xtime we may
+ * be OK, if not we need to test j for a complete frame.
+ */
+ if ((xtime + hcd->frame_usecs[j]) < utime) {
+ if (hcd->frame_usecs[j] < max_uframe_usecs[j]) {
+ j = 8;
+ ret = -1;
+ continue;
+ }
+ }
+ if (xtime >= utime) {
+ ret = i;
+ j = 8; /* stop loop with a good value ret */
+ continue;
+ }
+ /* add the frame time to x time */
+ xtime += hcd->frame_usecs[j];
+ /* we must have a fully available next frame or break */
+ if ((xtime < utime) &&
+ (hcd->frame_usecs[j] == max_uframe_usecs[j])) {
+ ret = -1;
+ j = 8; /* stop loop with a bad value ret */
+ continue;
+ }
+ }
+ if (ret >= 0) {
+ t_left = utime;
+ for (j = i; (t_left > 0) && (j < 8); j++) {
+ t_left -= hcd->frame_usecs[j];
+ if (t_left <= 0) {
+ qh->frame_usecs[j] +=
+ hcd->frame_usecs[j] + t_left;
+ hcd->frame_usecs[j] = -t_left;
+ ret = i;
+ done = 1;
+ } else {
+ qh->frame_usecs[j] +=
+ hcd->frame_usecs[j];
+ hcd->frame_usecs[j] = 0;
+ }
+ }
+ } else {
+ i++;
+ if (i == 8) {
+ done = 1;
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+static int find_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int ret = -1;
+
+ if (qh->speed == USB_SPEED_HIGH)
+ /* if this is a hs transaction we need a full frame */
+ ret = find_single_uframe(hcd, qh);
+ else
+ /* FS transaction may need a sequence of frames */
+ ret = find_multi_uframe(hcd, qh);
+
+ return ret;
+}
+
+/**
+ * Checks that the max transfer size allowed in a host channel is large enough
+ * to handle the maximum data transfer in a single (micro)frame for a periodic
+ * transfer.
+ */
+static int check_max_xfer_size(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int status = 0;
+ u32 max_xfer_size;
+ u32 max_channel_xfer_size;
+
+ max_xfer_size = dwc_max_packet(qh->maxp) * dwc_hb_mult(qh->maxp);
+ max_channel_xfer_size = hcd->core_if->core_params->max_transfer_size;
+
+ if (max_xfer_size > max_channel_xfer_size) {
+ pr_notice("%s: Periodic xfer length %d > max xfer "
+ "length for channel %d\n", __func__, max_xfer_size,
+ max_channel_xfer_size);
+ status = -ENOSPC;
+ }
+
+ return status;
+}
+
+/**
+ * Schedules an interrupt or isochronous transfer in the periodic schedule.
+ */
+static int schedule_periodic(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int status;
+ struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
+ int frame;
+
+ status = find_uframe(hcd, qh);
+ frame = -1;
+ if (status == 0) {
+ frame = 7;
+ } else {
+ if (status > 0)
+ frame = status - 1;
+ }
+ /* Set the new frame up */
+ if (frame > -1) {
+ qh->sched_frame &= ~0x7;
+ qh->sched_frame |= (frame & 7);
+ }
+ if (status != -1)
+ status = 0;
+ if (status) {
+ pr_notice("%s: Insufficient periodic bandwidth for "
+ "periodic transfer.\n", __func__);
+ return status;
+ }
+ status = check_max_xfer_size(hcd, qh);
+ if (status) {
+ pr_notice("%s: Channel max transfer size too small "
+ "for periodic transfer.\n", __func__);
+ return status;
+ }
+ /* Always start in the inactive schedule. */
+ list_add_tail(&qh->qh_list_entry, &hcd->periodic_sched_inactive);
+
+ /* Update claimed usecs per (micro)frame. */
+ hcd->periodic_usecs += qh->usecs;
+
+ /*
+ * Update average periodic bandwidth claimed and # periodic reqs for
+ * usbfs.
+ */
+ bus->bandwidth_allocated += qh->usecs / qh->interval;
+
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT)
+ bus->bandwidth_int_reqs++;
+ else
+ bus->bandwidth_isoc_reqs++;
+
+ return status;
+}
+
+/**
+ * This function adds a QH to either the non periodic or periodic schedule if
+ * it is not already in the schedule. If the QH is already in the schedule, no
+ * action is taken.
+ */
+static int dwc_otg_hcd_qh_add(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int status = 0;
+
+ /* QH may already be in a schedule. */
+ if (!list_empty(&qh->qh_list_entry))
+ goto done;
+ /*
+ * Add the new QH to the appropriate schedule. For non-periodic, always
+ * start in the inactive schedule.
+ */
+ if (dwc_qh_is_non_per(qh))
+ list_add_tail(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_inactive);
+ else
+ status = schedule_periodic(hcd, qh);
+
+done:
+ return status;
+}
+
+/**
+ * This function adds a QH to the non periodic deferred schedule.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+static int dwc_otg_hcd_qh_add_deferred(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ if (!list_empty(&qh->qh_list_entry))
+ /* QH already in a schedule. */
+ goto done;
+
+ /* Add the new QH to the non periodic deferred schedule */
+ if (dwc_qh_is_non_per(qh))
+ list_add_tail(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_deferred);
+done:
+ return 0;
+}
+
+/**
+ * Removes an interrupt or isochronous transfer from the periodic schedule.
+ */
+static void deschedule_periodic(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
+ int i;
+
+ list_del_init(&qh->qh_list_entry);
+ /* Update claimed usecs per (micro)frame. */
+ hcd->periodic_usecs -= qh->usecs;
+ for (i = 0; i < 8; i++) {
+ hcd->frame_usecs[i] += qh->frame_usecs[i];
+ qh->frame_usecs[i] = 0;
+ }
+ /*
+ * Update average periodic bandwidth claimed and # periodic reqs for
+ * usbfs.
+ */
+ bus->bandwidth_allocated -= qh->usecs / qh->interval;
+
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT)
+ bus->bandwidth_int_reqs--;
+ else
+ bus->bandwidth_isoc_reqs--;
+}
+
+/**
+ * Removes a QH from either the non-periodic or periodic schedule. Memory is
+ * not freed.
+ */
+void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ /* Do nothing if QH is not in a schedule */
+ if (list_empty(&qh->qh_list_entry))
+ return;
+
+ if (dwc_qh_is_non_per(qh)) {
+ if (hcd->non_periodic_qh_ptr == &qh->qh_list_entry)
+ hcd->non_periodic_qh_ptr =
+ hcd->non_periodic_qh_ptr->next;
+ list_del_init(&qh->qh_list_entry);
+ } else {
+ deschedule_periodic(hcd, qh);
+ }
+}
+
+/**
+ * Defers a QH. For non-periodic QHs, removes the QH from the active
+ * non-periodic schedule. The QH is added to the deferred non-periodic
+ * schedule if any QTDs are still attached to the QH.
+ */
+int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh, int delay)
+{
+ int deact = 1;
+
+ if (dwc_qh_is_non_per(qh)) {
+ qh->sched_frame = dwc_frame_num_inc(hcd->frame_number, delay);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ deact = 0;
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ if (!list_empty(&qh->qtd_list))
+ /* Add back to deferred non-periodic schedule. */
+ dwc_otg_hcd_qh_add_deferred(hcd, qh);
+ }
+ return deact;
+}
+
+/**
+ * Schedule the next continuing periodic split transfer
+ */
+static void sched_next_per_split_xfr(struct dwc_qh *qh, u16 fr_num,
+ int sched_split)
+{
+ if (sched_split) {
+ qh->sched_frame = fr_num;
+ if (dwc_frame_num_le(fr_num,
+ dwc_frame_num_inc(qh->start_split_frame,
+ 1))) {
+ /*
+ * Allow one frame to elapse after start split
+ * microframe before scheduling complete split, but DONT
+ * if we are doing the next start split in the
+ * same frame for an ISOC out.
+ */
+ if (qh->ep_type != USB_ENDPOINT_XFER_ISOC ||
+ qh->ep_is_in)
+ qh->sched_frame =
+ dwc_frame_num_inc(qh->sched_frame, 1);
+ }
+ } else {
+ qh->sched_frame = dwc_frame_num_inc(qh->start_split_frame,
+ qh->interval);
+
+ if (dwc_frame_num_le(qh->sched_frame, fr_num))
+ qh->sched_frame = fr_num;
+ qh->sched_frame |= 0x7;
+ qh->start_split_frame = qh->sched_frame;
+ }
+}
+
+/**
+ * Deactivates a periodic QH. The QH is removed from the periodic queued
+ * schedule. If there are any QTDs still attached to the QH, the QH is added to
+ * either the periodic inactive schedule or the periodic ready schedule and its
+ * next scheduled frame is calculated. The QH is placed in the ready schedule if
+ * the scheduled frame has been reached already. Otherwise it's placed in the
+ * inactive schedule. If there are no QTDs attached to the QH, the QH is
+ * completely removed from the periodic schedule.
+ */
+static void deactivate_periodic_qh(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_next_split)
+{
+ /* unsigned long flags; */
+ u16 fr_num = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+
+ if (qh->do_split) {
+ sched_next_per_split_xfr(qh, fr_num, sched_next_split);
+ } else {
+ qh->sched_frame = dwc_frame_num_inc(qh->sched_frame,
+ qh->interval);
+ if (dwc_frame_num_le(qh->sched_frame, fr_num))
+ qh->sched_frame = fr_num;
+ }
+
+ if (list_empty(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ } else {
+ /*
+ * Remove from periodic_sched_queued and move to appropriate
+ * queue.
+ */
+ if (qh->sched_frame == fr_num)
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_ready);
+ else
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_inactive);
+ }
+}
+
+/**
+ * Deactivates a non-periodic QH. Removes the QH from the active non-periodic
+ * schedule. The QH is added to the inactive non-periodic schedule if any QTDs
+ * are still attached to the QH.
+ */
+static void deactivate_non_periodic_qh(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ if (!list_empty(&qh->qtd_list))
+ dwc_otg_hcd_qh_add(hcd, qh);
+}
+
+/**
+ * Deactivates a QH. Determines if the QH is periodic or non-periodic and takes
+ * the appropriate action.
+ */
+void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_next_periodic_split)
+{
+ if (dwc_qh_is_non_per(qh))
+ deactivate_non_periodic_qh(hcd, qh);
+ else
+ deactivate_periodic_qh(hcd, qh, sched_next_periodic_split);
+}
+
+/**
+ * Initializes a QTD structure.
+ */
+static void dwc_otg_hcd_qtd_init(struct dwc_qtd *qtd, struct urb *urb)
+{
+ memset(qtd, 0, sizeof(struct dwc_qtd));
+ qtd->urb = urb;
+
+ if (usb_pipecontrol(urb->pipe)) {
+ /*
+ * The only time the QTD data toggle is used is on the data
+ * phase of control transfers. This phase always starts with
+ * DATA1.
+ */
+ qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
+ qtd->control_phase = DWC_OTG_CONTROL_SETUP;
+ }
+
+ /* start split */
+ qtd->complete_split = 0;
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ qtd->isoc_split_offset = 0;
+
+ /* Store the qtd ptr in the urb to reference what QTD. */
+ urb->hcpriv = qtd;
+
+ INIT_LIST_HEAD(&qtd->qtd_list_entry);
+ return;
+}
+
+/* Allocates memory for a QTD structure. */
+static inline struct dwc_qtd *dwc_otg_hcd_qtd_alloc(gfp_t _mem_flags)
+{
+ return kmalloc(sizeof(struct dwc_qtd), _mem_flags);
+}
+
+/**
+ * This function allocates and initializes a QTD.
+ */
+struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb, gfp_t _mem_flags)
+{
+ struct dwc_qtd *qtd = dwc_otg_hcd_qtd_alloc(_mem_flags);
+
+ if (!qtd)
+ return NULL;
+
+ dwc_otg_hcd_qtd_init(qtd, urb);
+ return qtd;
+}
+
+/**
+ * This function adds a QTD to the QTD-list of a QH. It will find the correct
+ * QH to place the QTD into. If it does not find a QH, then it will create a
+ * new QH. If the QH to which the QTD is added is not currently scheduled, it
+ * is placed into the proper schedule based on its EP type.
+ *
+ */
+int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *hcd)
+{
+ struct usb_host_endpoint *ep;
+ struct dwc_qh *qh;
+ int retval = 0;
+ struct urb *urb = qtd->urb;
+
+ /*
+ * Get the QH which holds the QTD-list to insert to. Create QH if it
+ * doesn't exist.
+ */
+ ep = dwc_urb_to_endpoint(urb);
+
+ qh = (struct dwc_qh *)ep->hcpriv;
+ if (!qh) {
+ qh = dwc_otg_hcd_qh_create(hcd, urb);
+ if (!qh) {
+ retval = -1;
+ goto done;
+ }
+ ep->hcpriv = qh;
+ }
+ qtd->qtd_qh_ptr = qh;
+ retval = dwc_otg_hcd_qh_add(hcd, qh);
+ if (!retval)
+ list_add_tail(&qtd->qtd_list_entry, &qh->qtd_list);
+
+done:
+ return retval;
+}
diff --git a/drivers/usb/dwc/param.c b/drivers/usb/dwc/param.c
new file mode 100644
index 0000000..0dc73d2
--- /dev/null
+++ b/drivers/usb/dwc/param.c
@@ -0,0 +1,180 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+/*
+ * This file provides dwc_otg driver parameter and parameter checking.
+ */
+
+#include "cil.h"
+
+/*
+ * Encapsulate the module parameter settings
+ */
+struct core_params dwc_otg_module_params = {
+ .otg_cap = -1,
+ .dma_enable = -1,
+ .dma_burst_size = -1,
+ .speed = -1,
+ .host_support_fs_ls_low_power = -1,
+ .host_ls_low_power_phy_clk = -1,
+ .enable_dynamic_fifo = -1,
+ .dev_rx_fifo_size = -1,
+ .dev_nperio_tx_fifo_size = -1,
+ .dev_perio_tx_fifo_size = {-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1}, /* 15 */
+ .host_rx_fifo_size = -1,
+ .host_nperio_tx_fifo_size = -1,
+ .host_perio_tx_fifo_size = -1,
+ .max_transfer_size = -1,
+ .max_packet_count = -1,
+ .host_channels = -1,
+ .dev_endpoints = -1,
+ .phy_type = -1,
+ .phy_utmi_width = -1,
+ .phy_ulpi_ddr = -1,
+ .phy_ulpi_ext_vbus = -1,
+ .i2c_enable = -1,
+ .ulpi_fs_ls = -1,
+ .ts_dline = -1,
+ .en_multiple_tx_fifo = -1,
+ .dev_tx_fifo_size = {-1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1}, /* 15 */
+ .thr_ctl = -1,
+ .tx_thr_length = -1,
+ .rx_thr_length = -1,
+};
+
+/**
+ * Checks that parameter settings for the periodic Tx FIFO sizes are correct
+ * according to the hardware configuration. Sets the size to the hardware
+ * configuration if an incorrect size is detected.
+ */
+static int set_valid_perio_tx_fifo_sizes(struct core_if *core_if)
+{
+ ulong regs = (u32) core_if->core_global_regs;
+ u32 *param_size = &dwc_otg_module_params.dev_perio_tx_fifo_size[0];
+ u32 i, size;
+
+ for (i = 0; i < MAX_PERIO_FIFOS; i++, param_size++) {
+ size = dwc_reg_read(regs, DWC_DPTX_FSIZ_DIPTXF(i));
+ *param_size = size;
+ }
+ return 0;
+}
+
+/**
+ * Checks that parameter settings for the Tx FIFO sizes are correct according to
+ * the hardware configuration. Sets the size to the hardware configuration if
+ * an incorrect size is detected.
+ */
+static int set_valid_tx_fifo_sizes(struct core_if *core_if)
+{
+ ulong regs = (u32) core_if->core_global_regs;
+ u32 *param_size = &dwc_otg_module_params.dev_tx_fifo_size[0];
+ u32 i, size;
+
+ for (i = 0; i < MAX_TX_FIFOS; i++, param_size) {
+ size = dwc_reg_read(regs, DWC_DPTX_FSIZ_DIPTXF(i));
+ *param_size = size;
+ }
+ return 0;
+}
+
+/**
+ * This function is called during module intialization to verify that
+ * the module parameters are in a valid state.
+ */
+int check_parameters(struct core_if *core_if)
+{
+ /* Default values */
+ dwc_otg_module_params.otg_cap = dwc_param_otg_cap_default;
+ dwc_otg_module_params.dma_enable = dwc_param_dma_enable_default;
+ dwc_otg_module_params.speed = dwc_param_speed_default;
+ dwc_otg_module_params.host_support_fs_ls_low_power =
+ dwc_param_host_support_fs_ls_low_power_default;
+ dwc_otg_module_params.host_ls_low_power_phy_clk =
+ dwc_param_host_ls_low_power_phy_clk_default;
+ dwc_otg_module_params.phy_type = dwc_param_phy_type_default;
+ dwc_otg_module_params.phy_ulpi_ddr = dwc_param_phy_ulpi_ddr_default;
+ dwc_otg_module_params.phy_ulpi_ext_vbus =
+ dwc_param_phy_ulpi_ext_vbus_default;
+ dwc_otg_module_params.i2c_enable = dwc_param_i2c_enable_default;
+ dwc_otg_module_params.ulpi_fs_ls = dwc_param_ulpi_fs_ls_default;
+ dwc_otg_module_params.ts_dline = dwc_param_ts_dline_default;
+
+ dwc_otg_module_params.dma_burst_size = dwc_param_dma_burst_size_default;
+ dwc_otg_module_params.phy_utmi_width = dwc_param_phy_utmi_width_default;
+ dwc_otg_module_params.thr_ctl = dwc_param_thr_ctl_default;
+ dwc_otg_module_params.tx_thr_length = dwc_param_tx_thr_length_default;
+ dwc_otg_module_params.rx_thr_length = dwc_param_rx_thr_length_default;
+
+ /*
+ * Hardware configurations of the OTG core.
+ */
+ dwc_otg_module_params.enable_dynamic_fifo =
+ DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2);
+ dwc_otg_module_params.dev_rx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GRXFSIZ);
+ dwc_otg_module_params.dev_nperio_tx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GNPTXFSIZ) >> 16;
+
+ dwc_otg_module_params.host_rx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GRXFSIZ);
+ dwc_otg_module_params.host_nperio_tx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GNPTXFSIZ) >> 16;
+ dwc_otg_module_params.host_perio_tx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_HPTXFSIZ) >> 16;
+ dwc_otg_module_params.max_transfer_size =
+ (1 << (DWC_HWCFG3_XFERSIZE_CTR_WIDTH_RD(core_if->hwcfg3) + 11))
+ - 1;
+ dwc_otg_module_params.max_packet_count =
+ (1 << (DWC_HWCFG3_PKTSIZE_CTR_WIDTH_RD(core_if->hwcfg3) + 4))
+ - 1;
+
+ dwc_otg_module_params.host_channels =
+ DWC_HWCFG2_NO_HST_CHAN_RD(core_if->hwcfg2) + 1;
+ dwc_otg_module_params.dev_endpoints =
+ DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2);
+ dwc_otg_module_params.en_multiple_tx_fifo =
+ (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4) == 0)
+ ? 0 : 1, 0;
+ set_valid_perio_tx_fifo_sizes(core_if);
+ set_valid_tx_fifo_sizes(core_if);
+
+ return 0;
+}
+
+module_param_named(dma_enable, dwc_otg_module_params.dma_enable, bool, 0444);
+MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled");
diff --git a/drivers/usb/dwc/pcd.c b/drivers/usb/dwc/pcd.c
new file mode 100644
index 0000000..cfcb0ba
--- /dev/null
+++ b/drivers/usb/dwc/pcd.c
@@ -0,0 +1,1791 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+/*
+ * This file implements the Peripheral Controller Driver.
+ *
+ * The Peripheral Controller Driver (PCD) is responsible for
+ * translating requests from the Function Driver into the appropriate
+ * actions on the DWC_otg controller. It isolates the Function Driver
+ * from the specifics of the controller by providing an API to the
+ * Function Driver.
+ *
+ * The Peripheral Controller Driver for Linux will implement the
+ * Gadget API, so that the existing Gadget drivers can be used.
+ * (Gadget Driver is the Linux terminology for a Function Driver.)
+ *
+ * The Linux Gadget API is defined in the header file linux/usb/gadget.h. The
+ * USB EP operations API is defined in the structure usb_ep_ops and the USB
+ * Controller API is defined in the structure usb_gadget_ops
+ *
+ * An important function of the PCD is managing interrupts generated
+ * by the DWC_otg controller. The implementation of the DWC_otg device
+ * mode interrupt service routines is in dwc_otg_pcd_intr.c.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+
+#include "pcd.h"
+
+/*
+ * Static PCD pointer for use in usb_gadget_register_driver and
+ * usb_gadget_unregister_driver. Initialized in dwc_otg_pcd_init.
+ */
+static struct dwc_pcd *s_pcd;
+
+static inline int need_stop_srp_timer(struct core_if *core_if)
+{
+ if (core_if->core_params->phy_type != DWC_PHY_TYPE_PARAM_FS ||
+ !core_if->core_params->i2c_enable)
+ return core_if->srp_timer_started ? 1 : 0;
+ return 0;
+}
+
+/**
+ * Tests if the module is set to FS or if the PHY_TYPE is FS. If so, then the
+ * gadget should not report as dual-speed capable.
+ */
+static inline int check_is_dual_speed(struct core_if *core_if)
+{
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL ||
+ (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 &&
+ DWC_HWCFG2_P_2_P_RD(core_if->hwcfg2) == 1 &&
+ core_if->core_params->ulpi_fs_ls))
+ return 0;
+ return 1;
+}
+
+/**
+ * Tests if driver is OTG capable.
+ */
+static inline int check_is_otg(struct core_if *core_if)
+{
+ if (DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE ||
+ DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST ||
+ DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE ||
+ DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)
+ return 0;
+ return 1;
+}
+
+/**
+ * This function completes a request. It calls the request call back.
+ */
+void request_done(struct pcd_ep *ep, struct pcd_request *req, int status)
+{
+ unsigned stopped = ep->stopped;
+
+ list_del_init(&req->queue);
+ if (req->req.status == -EINPROGRESS)
+ req->req.status = status;
+ else
+ status = req->req.status;
+
+ if (GET_CORE_IF(ep->pcd)->dma_enable) {
+ if (req->mapped) {
+ dma_unmap_single(ep->pcd->gadget.dev.parent,
+ req->req.dma, req->req.length,
+ ep->dwc_ep.is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ req->req.dma = DMA_ADDR_INVALID;
+ req->mapped = 0;
+ } else {
+ dma_sync_single_for_cpu(ep->pcd->gadget.dev.parent,
+ req->req.dma, req->req.length,
+ ep->dwc_ep.
+ is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ }
+ }
+
+ /* don't modify queue heads during completion callback */
+ ep->stopped = 1;
+ spin_unlock(&ep->pcd->lock);
+ req->req.complete(&ep->ep, &req->req);
+ spin_lock(&ep->pcd->lock);
+
+ if (ep->pcd->request_pending > 0)
+ --ep->pcd->request_pending;
+ ep->stopped = stopped;
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Finally, when the current request is done, mark this endpoint
+ * as not active, so that new requests can be processed.
+ */
+ if (dwc_has_feature(GET_CORE_IF(ep->pcd), DWC_LIMITED_XFER))
+ ep->dwc_ep.active = 0;
+}
+
+/**
+ * This function terminates all the requsts in the EP request queue.
+ */
+void request_nuke(struct pcd_ep *ep)
+{
+ struct pcd_request *req;
+
+ ep->stopped = 1;
+
+ /* called with irqs blocked?? */
+ while (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+ request_done(ep, req, -ESHUTDOWN);
+ }
+}
+
+/*
+ * The following sections briefly describe the behavior of the Gadget
+ * API endpoint operations implemented in the DWC_otg driver
+ * software. Detailed descriptions of the generic behavior of each of
+ * these functions can be found in the Linux header file
+ * include/linux/usb_gadget.h.
+ *
+ * The Gadget API provides wrapper functions for each of the function
+ * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper
+ * function, which then calls the underlying PCD function. The
+ * following sections are named according to the wrapper
+ * functions. Within each section, the corresponding DWC_otg PCD
+ * function name is specified.
+ *
+ */
+
+/**
+ * This function assigns periodic Tx FIFO to an periodic EP in shared Tx FIFO
+ * mode
+ */
+static u32 assign_perio_tx_fifo(struct core_if *core_if)
+{
+ u32 mask = 1;
+ u32 i;
+
+ for (i = 0; i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ ++i) {
+ if (!(mask & core_if->p_tx_msk)) {
+ core_if->p_tx_msk |= mask;
+ return i + 1;
+ }
+ mask <<= 1;
+ }
+ return 0;
+}
+
+/**
+ * This function releases periodic Tx FIFO in shared Tx FIFO mode
+ */
+static void release_perio_tx_fifo(struct core_if *core_if, u32 fifo_num)
+{
+ core_if->p_tx_msk = (core_if->p_tx_msk & (1 << (fifo_num - 1)))
+ ^ core_if->p_tx_msk;
+}
+
+/**
+ * This function assigns periodic Tx FIFO to an periodic EP in shared Tx FIFO
+ * mode
+ */
+static u32 assign_tx_fifo(struct core_if *core_if)
+{
+ u32 mask = 1;
+ u32 i;
+
+ for (i = 0; i < DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); ++i) {
+ if (!(mask & core_if->tx_msk)) {
+ core_if->tx_msk |= mask;
+ return i + 1;
+ }
+ mask <<= 1;
+ }
+ return 0;
+}
+
+/**
+ * This function releases periodic Tx FIFO in shared Tx FIFO mode
+ */
+static void release_tx_fifo(struct core_if *core_if, u32 fifo_num)
+{
+ core_if->tx_msk = (core_if->tx_msk & (1 << (fifo_num - 1)))
+ ^ core_if->tx_msk;
+}
+
+/**
+ * Sets an in endpoint's tx fifo based on the hardware configuration.
+ */
+static void set_in_ep_tx_fifo(struct dwc_pcd *pcd, struct pcd_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ if (pcd->otg_dev->core_if->en_multiple_tx_fifo) {
+ ep->dwc_ep.tx_fifo_num = assign_tx_fifo(pcd->otg_dev->core_if);
+ } else {
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* If ISOC EP then assign a Periodic Tx FIFO. */
+ if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_ISOC)
+ ep->dwc_ep.tx_fifo_num =
+ assign_perio_tx_fifo(pcd->otg_dev->core_if);
+ }
+}
+
+/**
+ * This function activates an EP. The Device EP control register for
+ * the EP is configured as defined in the ep structure. Note: This function is
+ * not used for EP0.
+ */
+void dwc_otg_ep_activate(struct core_if *core_if, struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 depctl = 0;
+ ulong addr;
+ u32 daintmsk = 0;
+
+ /* Read DEPCTLn register */
+ if (ep->is_in == 1) {
+ addr = dev_if->in_ep_regs[ep->num] + DWC_DIEPCTL;
+ daintmsk = DWC_DAINTMSK_IN_EP_RW(daintmsk, ep->num);
+ } else {
+ addr = dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL;
+ daintmsk = DWC_DAINTMSK_OUT_EP_RW(daintmsk, ep->num);
+ }
+
+ /* If the EP is already active don't change the EP Control register */
+ depctl = dwc_reg_read(addr, 0);
+ if (!DWC_DEPCTL_ACT_EP_RD(depctl)) {
+ depctl = DWC_DEPCTL_MPS_RW(depctl, ep->maxpacket);
+ depctl = DWC_DEPCTL_EP_TYPE_RW(depctl, ep->type);
+ depctl = DWC_DEPCTL_TX_FIFO_NUM_RW(depctl, ep->tx_fifo_num);
+ depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1);
+ depctl = DWC_DEPCTL_ACT_EP_RW(depctl, 1);
+ dwc_reg_write(addr, 0, depctl);
+ }
+
+ /* Enable the Interrupt for this EP */
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DAINTMSK, 0, daintmsk);
+
+ ep->stall_clear_flag = 0;
+}
+
+/**
+ * This function is called by the Gadget Driver for each EP to be
+ * configured for the current configuration (SET_CONFIGURATION).
+ *
+ * This function initializes the dwc_otg_ep_t data structure, and then
+ * calls dwc_otg_ep_activate.
+ */
+static int dwc_otg_pcd_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct pcd_ep *ep;
+ struct dwc_pcd *pcd;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || !desc || ep->desc || desc->bDescriptorType !=
+ USB_DT_ENDPOINT) {
+ pr_warning("%s, bad ep or descriptor\n", __func__);
+ return -EINVAL;
+ }
+
+ if (ep == &ep->pcd->ep0) {
+ pr_warning("%s, bad ep(0)\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Check FIFO size */
+ if (!desc->wMaxPacketSize) {
+ pr_warning("%s, bad %s maxpacket\n", __func__, _ep->name);
+ return -ERANGE;
+ }
+
+ pcd = ep->pcd;
+ if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) {
+ pr_warning("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&pcd->lock, flags);
+ ep->desc = desc;
+ ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+
+ /* Activate the EP */
+ ep->stopped = 0;
+ ep->wedged = 0;
+ ep->dwc_ep.is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0;
+ ep->dwc_ep.maxpacket = ep->ep.maxpacket;
+ ep->dwc_ep.type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+
+ if (ep->dwc_ep.is_in)
+ set_in_ep_tx_fifo(pcd, ep, desc);
+
+ /* Set initial data PID. */
+ if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_BULK)
+ ep->dwc_ep.data_pid_start = 0;
+
+ dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep);
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return 0;
+}
+
+/**
+ * This function deactivates an EP. This is done by clearing the USB Active EP
+ * bit in the Device EP control register. Note: This function is not used for
+ * EP0. EP0 cannot be deactivated.
+ */
+static void dwc_otg_ep_deactivate(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ ulong addr;
+ u32 daintmsk = 0;
+
+ /* Read DEPCTLn register */
+ if (ep->is_in == 1) {
+ addr = core_if->dev_if->in_ep_regs[ep->num] + DWC_DIEPCTL;
+ daintmsk = DWC_DAINTMSK_IN_EP_RW(daintmsk, ep->num);
+ } else {
+ addr =
+ core_if->dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL;
+ daintmsk = DWC_DAINTMSK_OUT_EP_RW(daintmsk, ep->num);
+ }
+
+ depctl = DWC_DEPCTL_ACT_EP_RW(depctl, 0);
+ dwc_reg_write(addr, 0, depctl);
+
+ /* Disable the Interrupt for this EP */
+ dwc_reg_modify(core_if->dev_if->dev_global_regs, DWC_DAINTMSK,
+ daintmsk, 0);
+}
+
+/**
+ * This function is called when an EP is disabled due to disconnect or
+ * change in configuration. Any pending requests will terminate with a
+ * status of -ESHUTDOWN.
+ *
+ * This function modifies the dwc_otg_ep_t data structure for this EP,
+ * and then calls dwc_otg_ep_deactivate.
+ */
+static int dwc_otg_pcd_ep_disable(struct usb_ep *_ep)
+{
+ struct pcd_ep *ep;
+ struct core_if *core_if;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || !ep->desc)
+ return -EINVAL;
+
+ core_if = ep->pcd->otg_dev->core_if;
+
+ spin_lock_irqsave(&ep->pcd->lock, flags);
+
+ request_nuke(ep);
+ dwc_otg_ep_deactivate(core_if, &ep->dwc_ep);
+
+ ep->desc = NULL;
+ ep->stopped = 1;
+ if (ep->dwc_ep.is_in) {
+ release_perio_tx_fifo(core_if, ep->dwc_ep.tx_fifo_num);
+ release_tx_fifo(core_if, ep->dwc_ep.tx_fifo_num);
+ }
+
+ spin_unlock_irqrestore(&ep->pcd->lock, flags);
+
+ return 0;
+}
+
+/**
+ * This function allocates a request object to use with the specified
+ * endpoint.
+ */
+static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep,
+ gfp_t gfp_flags)
+{
+ struct pcd_request *req;
+
+ if (!_ep) {
+ pr_warning("%s() Invalid EP\n", __func__);
+ return NULL;
+ }
+
+ req = kzalloc(sizeof(struct pcd_request), gfp_flags);
+ if (!req) {
+ pr_warning("%s() request allocation failed\n", __func__);
+ return NULL;
+ }
+
+ req->req.dma = DMA_ADDR_INVALID;
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+/**
+ * This function frees a request object.
+ */
+static void dwc_otg_pcd_free_request(struct usb_ep *_ep,
+ struct usb_request *_req)
+{
+ struct pcd_request *req;
+
+ if (!_ep || !_req) {
+ pr_warning("%s() nvalid ep or req argument\n", __func__);
+ return;
+ }
+
+ req = container_of(_req, struct pcd_request, req);
+ kfree(req);
+}
+
+/*
+ * In dedicated Tx FIFO mode, enable the Non-Periodic Tx FIFO empty interrupt.
+ * Otherwise, enable the Tx FIFO epmty interrupt. The data will be written into
+ * the fifo by the ISR.
+ */
+static void enable_tx_fifo_empty_intr(struct core_if *c_if, struct dwc_ep *ep)
+{
+ u32 intr_mask = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong global_regs = c_if->core_global_regs;
+
+ if (!c_if->en_multiple_tx_fifo) {
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTSTS, intr_mask, 0);
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+ } else if (ep->xfer_len) {
+ /* Enable the Tx FIFO Empty Interrupt for this EP */
+ u32 fifoemptymsk = 1 << ep->num;
+ dwc_reg_modify(d_if->dev_global_regs,
+ DWC_DTKNQR4FIFOEMPTYMSK, 0, fifoemptymsk);
+ }
+}
+
+static void set_next_ep(struct device_if *dev_if, u8 num)
+{
+ u32 depctl = 0;
+
+ depctl = dwc_reg_read(dev_if->in_ep_regs[0], 0) + DWC_DIEPCTL;
+ depctl = DWC_DEPCTL_NXT_EP_RW(depctl, num);
+
+ dwc_reg_write((dev_if->in_ep_regs[0]), DWC_DIEPCTL, depctl);
+}
+
+/**
+ * This function does the setup for a data transfer for an EP and
+ * starts the transfer. For an IN transfer, the packets will be loaded into the
+ * appropriate Tx FIFO in the ISR. For OUT transfers, the packets are unloaded
+ * from the Rx FIFO in the ISR.
+ *
+ */
+void dwc_otg_ep_start_transfer(struct core_if *c_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ u32 deptsiz = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong glbl_regs = c_if->core_global_regs;
+
+ if (ep->is_in) {
+ ulong in_regs = d_if->in_ep_regs[ep->num];
+ u32 gtxstatus;
+
+ gtxstatus = dwc_reg_read(glbl_regs, DWC_GNPTXSTS);
+ if (!c_if->en_multiple_tx_fifo
+ && !DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(gtxstatus))
+ return;
+
+ depctl = dwc_reg_read(in_regs, DWC_DIEPCTL);
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ /* Zero Length Packet? */
+ if (!ep->xfer_len) {
+ deptsiz = DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, 0);
+ deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ /*
+ * Program the transfer size and packet count as
+ * follows:
+ *
+ * xfersize = N * maxpacket + short_packet
+ * pktcnt = N + (short_packet exist ? 1 : 0)
+ */
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger
+ * transfers into multiple transfers of 1024 bytes sized
+ * messages. I happens often, that transfers of 4096
+ * bytes are required (zero-gadget,
+ * file_storage-gadget).
+ */
+ if (dwc_has_feature(c_if, DWC_LIMITED_XFER)) {
+ if (ep->xfer_len > MAX_XFER_LEN) {
+ ep->bytes_pending = ep->xfer_len
+ - MAX_XFER_LEN;
+ ep->xfer_len = MAX_XFER_LEN;
+ }
+ }
+
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, ep->xfer_len);
+ deptsiz =
+ DWC_DEPTSIZ_PKT_CNT_RW(deptsiz,
+ ((ep->xfer_len - 1 +
+ ep->maxpacket) /
+ ep->maxpacket));
+ }
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr);
+ else if (ep->type != DWC_OTG_EP_TYPE_ISOC)
+ enable_tx_fifo_empty_intr(c_if, ep);
+
+ /* EP enable, IN data in FIFO */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(in_regs, DWC_DIEPCTL, depctl);
+
+ if (c_if->dma_enable)
+ set_next_ep(d_if, ep->num);
+ } else {
+ u32 out_regs = d_if->out_ep_regs[ep->num];
+
+ depctl = dwc_reg_read(out_regs, DWC_DOEPCTL);
+ deptsiz = dwc_reg_read(out_regs, DWC_DOEPTSIZ);
+
+ /*
+ * Program the transfer size and packet count as follows:
+ *
+ * pktcnt = N
+ * xfersize = N * maxpacket
+ */
+ if (!ep->xfer_len) {
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, ep->maxpacket);
+ deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz,
+ ((ep->xfer_len +
+ ep->maxpacket -
+ 1) / ep->maxpacket));
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz,
+ DWC_DEPTSIZ_PKT_CNT_RD
+ (deptsiz) * ep->maxpacket);
+ }
+ dwc_reg_write(out_regs, DWC_DOEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(out_regs, DWC_DOEPDMA, ep->dma_addr);
+
+ if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+ if (ep->even_odd_frame)
+ depctl = DWC_DEPCTL_SET_DATA1_PID_RW(depctl, 1);
+ else
+ depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1);
+ }
+
+ /* EP enable */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(out_regs, DWC_DOEPCTL, depctl);
+ }
+}
+
+/**
+ * This function does the setup for a data transfer for EP0 and starts
+ * the transfer. For an IN transfer, the packets will be loaded into
+ * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are
+ * unloaded from the Rx FIFO in the ISR.
+ */
+void dwc_otg_ep0_start_transfer(struct core_if *c_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ u32 deptsiz = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong glbl_regs = c_if->core_global_regs;
+
+ ep->total_len = ep->xfer_len;
+
+ if (ep->is_in) {
+ ulong in_regs = d_if->in_ep_regs[0];
+ u32 gtxstatus;
+
+ gtxstatus = dwc_reg_read(glbl_regs, DWC_GNPTXSTS);
+
+ if (!c_if->en_multiple_tx_fifo
+ && !DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(gtxstatus))
+ return;
+
+ depctl = dwc_reg_read(in_regs, DWC_DIEPCTL);
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ /* Zero Length Packet? */
+ if (!ep->xfer_len) {
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, 0);
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ /*
+ * Program the transfer size and packet count as
+ * follows:
+ *
+ * xfersize = N * maxpacket + short_packet
+ * pktcnt = N + (short_packet exist ? 1 : 0)
+ */
+ if (ep->xfer_len > ep->maxpacket) {
+ ep->xfer_len = ep->maxpacket;
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->
+ maxpacket);
+ } else {
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->
+ xfer_len);
+ }
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ }
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr);
+
+ /* EP enable, IN data in FIFO */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(in_regs, DWC_DIEPCTL, depctl);
+
+ if (!c_if->dma_enable)
+ enable_tx_fifo_empty_intr(c_if, ep);
+ } else {
+ ulong out_regs = d_if->out_ep_regs[ep->num];
+
+ depctl = dwc_reg_read(out_regs, DWC_DOEPCTL);
+ deptsiz = dwc_reg_read(out_regs, DWC_DOEPTSIZ);
+
+ /*
+ * Program the transfer size and packet count as follows:
+ *
+ * xfersize = N * (maxpacket + 4 - (maxpacket % 4))
+ * pktcnt = N
+ */
+ if (!ep->xfer_len) {
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->maxpacket);
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz,
+ (ep->xfer_len +
+ ep->maxpacket -
+ 1) / ep->maxpacket);
+ deptsiz =
+ DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ DWC_DEPTSIZ_PKT_CNT_RD
+ (deptsiz) * ep->maxpacket);
+ }
+ dwc_reg_write(out_regs, DWC_DOEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(out_regs, DWC_DOEPDMA, ep->dma_addr);
+
+ /* EP enable */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(out_regs, DWC_DOEPCTL, depctl);
+ }
+}
+
+/**
+ * This function is used to submit an I/O Request to an EP.
+ *
+ * - When the request completes the request's completion callback
+ * is called to return the request to the driver.
+ * - An EP, except control EPs, may have multiple requests
+ * pending.
+ * - Once submitted the request cannot be examined or modified.
+ * - Each request is turned into one or more packets.
+ * - A BULK EP can queue any amount of data; the transfer is
+ * packetized.
+ * - Zero length Packets are specified with the request 'zero'
+ * flag.
+ */
+static int dwc_otg_pcd_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ int prevented = 0;
+ struct pcd_request *req;
+ struct pcd_ep *ep;
+ struct dwc_pcd *pcd;
+ struct core_if *core_if;
+ unsigned long flags = 0;
+
+ req = container_of(_req, struct pcd_request, req);
+ if (!_req || !_req->complete || !_req->buf ||
+ !list_empty(&req->queue)) {
+ pr_warning("%s, bad params\n", __func__);
+ return -EINVAL;
+ }
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || (!ep->desc && ep->dwc_ep.num != 0)) {
+ pr_warning("%s, bad ep\n", __func__);
+ return -EINVAL;
+ }
+
+ pcd = ep->pcd;
+ if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) {
+ pr_warning("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+ core_if = pcd->otg_dev->core_if;
+
+ if (GET_CORE_IF(pcd)->dma_enable) {
+ if (_req->dma == DMA_ADDR_INVALID) {
+ _req->dma = dma_map_single(pcd->gadget.dev.parent,
+ _req->buf, _req->length,
+ ep->dwc_ep.
+ is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ req->mapped = 1;
+ } else {
+ dma_sync_single_for_device(pcd->gadget.dev.parent,
+ _req->dma, _req->length,
+ ep->dwc_ep.
+ is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ req->mapped = 0;
+ }
+ }
+
+ spin_lock_irqsave(&ep->pcd->lock, flags);
+
+ _req->status = -EINPROGRESS;
+ _req->actual = 0;
+
+ /* Start the transfer */
+ if (list_empty(&ep->queue) && !ep->stopped) {
+ /* EP0 Transfer? */
+ if (ep->dwc_ep.num == 0) {
+ switch (pcd->ep0state) {
+ case EP0_IN_DATA_PHASE:
+ break;
+ case EP0_OUT_DATA_PHASE:
+ if (pcd->request_config) {
+ /* Complete STATUS PHASE */
+ ep->dwc_ep.is_in = 1;
+ pcd->ep0state = EP0_STATUS;
+ }
+ break;
+ default:
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return -EL2HLT;
+ }
+
+ ep->dwc_ep.dma_addr = _req->dma;
+ ep->dwc_ep.start_xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_len = _req->length;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+ dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep);
+ } else {
+ /* Setup and start the Transfer */
+ ep->dwc_ep.dma_addr = _req->dma;
+ ep->dwc_ep.start_xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_len = _req->length;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+ dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep);
+ }
+ }
+
+ if (req || prevented) {
+ ++pcd->request_pending;
+ list_add_tail(&req->queue, &ep->queue);
+
+ if (ep->dwc_ep.is_in && ep->stopped && !core_if->dma_enable) {
+ /*
+ * Device IN endpoint interrupt mask register is laid
+ * out exactly the same as the device IN endpoint
+ * interrupt register.
+ */
+ u32 diepmsk = 0;
+ diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1);
+
+ dwc_reg_modify(core_if->dev_if->dev_global_regs,
+ DWC_DIEPMSK, 0, diepmsk);
+ }
+ }
+
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return 0;
+}
+
+/**
+ * This function cancels an I/O request from an EP.
+ */
+static int dwc_otg_pcd_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct pcd_request *req;
+ struct pcd_ep *ep;
+ struct dwc_pcd *pcd;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || !_req || (!ep->desc && ep->dwc_ep.num != 0)) {
+ pr_warning("%s, bad argument\n", __func__);
+ return -EINVAL;
+ }
+
+ pcd = ep->pcd;
+ if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) {
+ pr_warning("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&pcd->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(&pcd->lock, flags);
+ return -EINVAL;
+ }
+
+ if (!list_empty(&req->queue))
+ request_done(ep, req, -ECONNRESET);
+ else
+ req = NULL;
+
+ spin_unlock_irqrestore(&pcd->lock, flags);
+
+ return req ? 0 : -EOPNOTSUPP;
+}
+
+/**
+ * Set the EP STALL.
+ */
+void dwc_otg_ep_set_stall(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ ulong depctl_addr;
+
+ if (ep->is_in) {
+ depctl_addr =
+ (core_if->dev_if->in_ep_regs[ep->num]) + DWC_DIEPCTL;
+ depctl = dwc_reg_read(depctl_addr, 0);
+
+ /* set the disable and stall bits */
+ if (DWC_DEPCTL_EPENA_RD(depctl))
+ depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1);
+ depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 1);
+ dwc_reg_write(depctl_addr, 0, depctl);
+ } else {
+ depctl_addr =
+ (core_if->dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL);
+ depctl = dwc_reg_read(depctl_addr, 0);
+
+ /* set the stall bit */
+ depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 1);
+ dwc_reg_write(depctl_addr, 0, depctl);
+ }
+}
+
+/**
+ * Clear the EP STALL.
+ */
+void dwc_otg_ep_clear_stall(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ ulong depctl_addr;
+
+ if (ep->is_in == 1)
+ depctl_addr =
+ (core_if->dev_if->in_ep_regs[ep->num]) + DWC_DIEPCTL;
+ else
+ depctl_addr =
+ (core_if->dev_if->out_ep_regs[ep->num]) + DWC_DOEPCTL;
+
+ depctl = dwc_reg_read(depctl_addr, 0);
+
+ /* clear the stall bits */
+ depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 0);
+
+ /*
+ * 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->type == DWC_OTG_EP_TYPE_INTR ||
+ ep->type == DWC_OTG_EP_TYPE_BULK)
+ depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1);
+
+ dwc_reg_write(depctl_addr, 0, depctl);
+}
+
+/**
+ * usb_ep_set_halt stalls an endpoint.
+ *
+ * usb_ep_clear_halt clears an endpoint halt and resets its data
+ * toggle.
+ *
+ * Both of these functions are implemented with the same underlying
+ * function. The behavior depends on the val argument:
+ * - 0 means clear_halt.
+ * - 1 means set_halt,
+ * - 2 means clear stall lock flag.
+ * - 3 means set stall lock flag.
+ */
+static int dwc_otg_pcd_ep_set_halt_wedge(struct usb_ep *_ep,
+ int val, int wedged)
+{
+ int retval = 0;
+ unsigned long flags;
+ struct pcd_ep *ep;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || (!ep->desc && ep != &ep->pcd->ep0) ||
+ ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
+ pr_warning("%s, bad ep\n", __func__);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->pcd->lock, flags);
+
+ if (ep->dwc_ep.is_in && !list_empty(&ep->queue)) {
+ pr_warning("%s() %s XFer In process\n", __func__, _ep->name);
+ retval = -EAGAIN;
+ } else if (val == 0) {
+ ep->wedged = 0;
+ dwc_otg_ep_clear_stall(ep->pcd->otg_dev->core_if, &ep->dwc_ep);
+ } else if (val == 1) {
+ if (ep->dwc_ep.num == 0)
+ ep->pcd->ep0state = EP0_STALL;
+ if (wedged)
+ ep->wedged = 1;
+
+ ep->stopped = 1;
+ dwc_otg_ep_set_stall(ep->pcd->otg_dev->core_if, &ep->dwc_ep);
+ } else if (val == 2) {
+ ep->dwc_ep.stall_clear_flag = 0;
+ } else if (val == 3) {
+ ep->dwc_ep.stall_clear_flag = 1;
+ }
+
+ spin_unlock_irqrestore(&ep->pcd->lock, flags);
+ return retval;
+}
+static int dwc_otg_pcd_ep_set_halt(struct usb_ep *_ep, int val)
+{
+ return dwc_otg_pcd_ep_set_halt_wedge(_ep, val, 0);
+}
+static int dwc_otg_pcd_ep_set_wedge(struct usb_ep *_ep)
+{
+ return dwc_otg_pcd_ep_set_halt_wedge(_ep, 1, 1);
+}
+
+static struct usb_ep_ops dwc_otg_pcd_ep_ops = {
+ .enable = dwc_otg_pcd_ep_enable,
+ .disable = dwc_otg_pcd_ep_disable,
+ .alloc_request = dwc_otg_pcd_alloc_request,
+ .free_request = dwc_otg_pcd_free_request,
+ .queue = dwc_otg_pcd_ep_queue,
+ .dequeue = dwc_otg_pcd_ep_dequeue,
+ .set_halt = dwc_otg_pcd_ep_set_halt,
+ .set_wedge = dwc_otg_pcd_ep_set_wedge,
+ .fifo_status = NULL,
+ .fifo_flush = NULL,
+};
+
+/**
+ * Gets the current USB frame number from the DTS register. This is the frame
+ * number from the last SOF packet.
+ */
+static u32 dwc_otg_get_frame_number(struct core_if *core_if)
+{
+ u32 dsts;
+
+ dsts = dwc_reg_read(core_if->dev_if->dev_global_regs, DWC_DSTS);
+ return DWC_DSTS_SOFFN_RD(dsts);
+}
+
+/**
+ * The following gadget operations will be implemented in the DWC_otg
+ * PCD. Functions in the API that are not described below are not
+ * implemented.
+ *
+ * The Gadget API provides wrapper functions for each of the function
+ * pointers defined in usb_gadget_ops. The Gadget Driver calls the
+ * wrapper function, which then calls the underlying PCD function. The
+ * following sections are named according to the wrapper functions
+ * (except for ioctl, which doesn't have a wrapper function). Within
+ * each section, the corresponding DWC_otg PCD function name is
+ * specified.
+ *
+ */
+
+/**
+ *Gets the USB Frame number of the last SOF.
+ */
+static int dwc_otg_pcd_get_frame(struct usb_gadget *_gadget)
+{
+ if (!_gadget) {
+ return -ENODEV;
+ } else {
+ struct dwc_pcd *pcd;
+
+ pcd = container_of(_gadget, struct dwc_pcd, gadget);
+ dwc_otg_get_frame_number(GET_CORE_IF(pcd));
+ }
+
+ return 0;
+}
+
+/**
+ * This function is called when the SRP timer expires. The SRP should complete
+ * within 6 seconds.
+ */
+static void srp_timeout(unsigned long data)
+{
+ u32 gotgctl;
+ struct dwc_pcd *pcd = (struct dwc_pcd *)data;
+ struct core_if *core_if = pcd->otg_dev->core_if;
+ ulong addr = otg_ctl_reg(pcd);
+
+ gotgctl = dwc_reg_read(addr, 0);
+ core_if->srp_timer_started = 0;
+
+ if (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS &&
+ core_if->core_params->i2c_enable) {
+ pr_info("SRP Timeout\n");
+
+ if (core_if->srp_success && (gotgctl &
+ DWC_GCTL_BSESSION_VALID)) {
+ if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup)
+ core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->
+ p);
+
+ /* Clear Session Request */
+ gotgctl = 0;
+ gotgctl |= DWC_GCTL_SES_REQ;
+ dwc_reg_modify(addr, 0, gotgctl, 0);
+
+ core_if->srp_success = 0;
+ } else {
+ pr_err("Device not connected/responding\n");
+ gotgctl &= ~DWC_GCTL_SES_REQ;
+ dwc_reg_write(addr, 0, gotgctl);
+ }
+ } else if (gotgctl & DWC_GCTL_SES_REQ) {
+ pr_info("SRP Timeout\n");
+ pr_err("Device not connected/responding\n");
+
+ gotgctl &= ~DWC_GCTL_SES_REQ;
+ dwc_reg_write(addr, 0, gotgctl);
+ } else {
+ pr_info(" SRP GOTGCTL=%0x\n", gotgctl);
+ }
+}
+
+/**
+ * Start the SRP timer to detect when the SRP does not complete within
+ * 6 seconds.
+ */
+static void dwc_otg_pcd_start_srp_timer(struct dwc_pcd *pcd)
+{
+ struct timer_list *srp_timer = &pcd->srp_timer;
+
+ GET_CORE_IF(pcd)->srp_timer_started = 1;
+ init_timer(srp_timer);
+ srp_timer->function = srp_timeout;
+ srp_timer->data = (unsigned long)pcd;
+ srp_timer->expires = jiffies + (HZ * 6);
+
+ add_timer(srp_timer);
+}
+
+static void dwc_otg_pcd_initiate_srp(struct dwc_pcd *pcd)
+{
+ u32 mem;
+ u32 val;
+ ulong addr = otg_ctl_reg(pcd);
+
+ val = dwc_reg_read(addr, 0);
+ if (val & DWC_GCTL_SES_REQ) {
+ pr_err("Session Request Already active!\n");
+ return;
+ }
+
+ pr_notice("Session Request Initated\n");
+ mem = dwc_reg_read(addr, 0);
+ mem |= DWC_GCTL_SES_REQ;
+ dwc_reg_write(addr, 0, mem);
+
+ /* Start the SRP timer */
+ dwc_otg_pcd_start_srp_timer(pcd);
+ return;
+}
+
+static void dwc_otg_pcd_remote_wakeup(struct dwc_pcd *pcd, int set)
+{
+ u32 dctl = 0;
+ ulong addr = dev_ctl_reg(pcd);
+
+ if (dwc_otg_is_device_mode(GET_CORE_IF(pcd))) {
+ if (pcd->remote_wakeup_enable) {
+ if (set) {
+ dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1);
+ dwc_reg_modify(addr, 0, 0, dctl);
+ msleep(20);
+ dwc_reg_modify(addr, 0, dctl, 0);
+ }
+ }
+ }
+}
+
+/**
+ * Initiates Session Request Protocol (SRP) to wakeup the host if no
+ * session is in progress. If a session is already in progress, but
+ * the device is suspended, remote wakeup signaling is started.
+ *
+ */
+static int dwc_otg_pcd_wakeup(struct usb_gadget *_gadget)
+{
+ unsigned long flags;
+ struct dwc_pcd *pcd;
+ u32 dsts;
+ u32 gotgctl;
+
+ if (!_gadget)
+ return -ENODEV;
+ else
+ pcd = container_of(_gadget, struct dwc_pcd, gadget);
+
+ spin_lock_irqsave(&pcd->lock, flags);
+
+ /*
+ * This function starts the Protocol if no session is in progress. If
+ * a session is already in progress, but the device is suspended,
+ * remote wakeup signaling is started.
+ */
+
+ /* Check if valid session */
+ gotgctl = dwc_reg_read(otg_ctl_reg(pcd), 0);
+ if (gotgctl & DWC_GCTL_BSESSION_VALID) {
+ /* Check if suspend state */
+ dsts = dwc_reg_read(dev_sts_reg(pcd), 0);
+ if (DWC_DSTS_SUSP_STS_RD(dsts))
+ dwc_otg_pcd_remote_wakeup(pcd, 1);
+ } else {
+ dwc_otg_pcd_initiate_srp(pcd);
+ }
+
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return 0;
+}
+
+static int dwc_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA)
+{
+ struct dwc_pcd *pcd = container_of(gadget, struct dwc_pcd, gadget);
+
+ if (pcd->otg_dev->core_if->xceiv)
+ return -EOPNOTSUPP;
+
+ return otg_set_power(pcd->otg_dev->core_if->xceiv, mA);
+
+}
+static int dwc_gadget_start(struct usb_gadget_driver *driver,
+ int (*bind) (struct usb_gadget *));
+static int dwc_gadget_stop(struct usb_gadget_driver *driver);
+
+static const struct usb_gadget_ops dwc_otg_pcd_ops = {
+ .get_frame = dwc_otg_pcd_get_frame,
+ .wakeup = dwc_otg_pcd_wakeup,
+ .start = dwc_gadget_start,
+ .stop = dwc_gadget_stop,
+ .vbus_draw = dwc_gadget_vbus_draw,
+};
+
+/**
+ * This function updates the otg values in the gadget structure.
+ */
+void dwc_otg_pcd_update_otg(struct dwc_pcd *pcd, const unsigned reset)
+{
+ if (!pcd->gadget.is_otg)
+ return;
+
+ if (reset) {
+ pcd->b_hnp_enable = 0;
+ pcd->a_hnp_support = 0;
+ pcd->a_alt_hnp_support = 0;
+ }
+
+ pcd->gadget.b_hnp_enable = pcd->b_hnp_enable;
+ pcd->gadget.a_hnp_support = pcd->a_hnp_support;
+ pcd->gadget.a_alt_hnp_support = pcd->a_alt_hnp_support;
+}
+
+/**
+ * This function is the top level PCD interrupt handler.
+ */
+static irqreturn_t dwc_otg_pcd_irq(int _irq, void *dev)
+{
+ struct dwc_pcd *pcd = dev;
+ int retval;
+
+ retval = dwc_otg_pcd_handle_intr(pcd);
+ return IRQ_RETVAL(retval);
+}
+
+/**
+ * PCD Callback function for initializing the PCD when switching to
+ * device mode.
+ */
+static int dwc_otg_pcd_start_cb(void *_p)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)_p;
+
+ /* Initialize the Core for Device mode. */
+ if (dwc_otg_is_device_mode(GET_CORE_IF(pcd)))
+ dwc_otg_core_dev_init(GET_CORE_IF(pcd));
+
+ return 1;
+}
+
+/**
+ * PCD Callback function for stopping the PCD when switching to Host
+ * mode.
+ */
+static int dwc_otg_pcd_stop_cb(void *_p)
+{
+ dwc_otg_pcd_stop((struct dwc_pcd *)_p);
+ return 1;
+}
+
+/**
+ * PCD Callback function for notifying the PCD when resuming from
+ * suspend.
+ *
+ * @param _p void pointer to the <code>struct dwc_pcd</code>
+ */
+static int dwc_otg_pcd_suspend_cb(void *_p)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)_p;
+
+ if (pcd->driver && pcd->driver->suspend) {
+ spin_unlock(&pcd->lock);
+ pcd->driver->suspend(&pcd->gadget);
+ spin_lock(&pcd->lock);
+ }
+ return 1;
+}
+
+/**
+ * PCD Callback function for notifying the PCD when resuming from
+ * suspend.
+ */
+static int dwc_otg_pcd_resume_cb(void *_p)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)_p;
+ struct core_if *core_if = pcd->otg_dev->core_if;
+
+ if (pcd->driver && pcd->driver->resume) {
+ spin_unlock(&pcd->lock);
+ pcd->driver->resume(&pcd->gadget);
+ spin_lock(&pcd->lock);
+ }
+
+ /* Maybe stop the SRP timeout timer. */
+ if (need_stop_srp_timer(core_if)) {
+ core_if->srp_timer_started = 0;
+ del_timer_sync(&pcd->srp_timer);
+ }
+ return 1;
+}
+
+/**
+ * PCD Callback structure for handling mode switching.
+ */
+static struct cil_callbacks pcd_callbacks = {
+ .start = dwc_otg_pcd_start_cb,
+ .stop = dwc_otg_pcd_stop_cb,
+ .suspend = dwc_otg_pcd_suspend_cb,
+ .resume_wakeup = dwc_otg_pcd_resume_cb,
+ .p = NULL, /* Set at registration */
+};
+
+/**
+ * Tasklet
+ *
+ */
+static void start_xfer_tasklet_func(unsigned long data)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)data;
+ u32 diepctl = 0;
+ int num = pcd->otg_dev->core_if->dev_if->num_in_eps;
+ u32 i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pcd->lock, flags);
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0);
+
+ if (pcd->ep0.queue_sof) {
+ pcd->ep0.queue_sof = 0;
+ dwc_start_next_request(&pcd->ep0);
+ }
+
+ for (i = 0; i < num; i++) {
+ u32 diepctl = 0;
+
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, i), 0);
+ if (pcd->in_ep[i].queue_sof) {
+ pcd->in_ep[i].queue_sof = 0;
+ dwc_start_next_request(&pcd->in_ep[i]);
+ }
+ }
+ spin_unlock_irqrestore(&pcd->lock, flags);
+}
+
+static struct tasklet_struct start_xfer_tasklet = {
+ .next = NULL,
+ .state = 0,
+ .count = ATOMIC_INIT(0),
+ .func = start_xfer_tasklet_func,
+ .data = 0,
+};
+
+/**
+ * This function initialized the pcd Dp structures to there default
+ * state.
+ */
+static void dwc_otg_pcd_reinit(struct dwc_pcd *pcd)
+{
+ static const char *names[] = {
+ "ep0", "ep1in", "ep2in", "ep3in", "ep4in", "ep5in",
+ "ep6in", "ep7in", "ep8in", "ep9in", "ep10in", "ep11in",
+ "ep12in", "ep13in", "ep14in", "ep15in", "ep1out", "ep2out",
+ "ep3out", "ep4out", "ep5out", "ep6out", "ep7out", "ep8out",
+ "ep9out", "ep10out", "ep11out", "ep12out", "ep13out",
+ "ep14out", "ep15out"
+ };
+ u32 i;
+ int in_ep_cntr, out_ep_cntr;
+ u32 hwcfg1;
+ u32 num_in_eps = (GET_CORE_IF(pcd))->dev_if->num_in_eps;
+ u32 num_out_eps = (GET_CORE_IF(pcd))->dev_if->num_out_eps;
+ struct pcd_ep *ep;
+
+ INIT_LIST_HEAD(&pcd->gadget.ep_list);
+ pcd->gadget.ep0 = &pcd->ep0.ep;
+ pcd->gadget.speed = USB_SPEED_UNKNOWN;
+ INIT_LIST_HEAD(&pcd->gadget.ep0->ep_list);
+
+ /* Initialize the EP0 structure. */
+ ep = &pcd->ep0;
+
+ /* Init EP structure */
+ ep->desc = NULL;
+ ep->pcd = pcd;
+ ep->stopped = 1;
+
+ /* Init DWC ep structure */
+ ep->dwc_ep.num = 0;
+ ep->dwc_ep.active = 0;
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* Control until ep is actvated */
+ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+ ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+ ep->dwc_ep.dma_addr = 0;
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = 0;
+ ep->queue_sof = 0;
+
+ /* Init the usb_ep structure. */
+ ep->ep.name = names[0];
+ ep->ep.ops = &dwc_otg_pcd_ep_ops;
+
+ ep->ep.maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list);
+ INIT_LIST_HEAD(&ep->queue);
+
+ /* Initialize the EP structures. */
+ in_ep_cntr = 0;
+ hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1 >> 3;
+
+ for (i = 1; in_ep_cntr < num_in_eps; i++) {
+ if (!(hwcfg1 & 0x1)) {
+ struct pcd_ep *ep = &pcd->in_ep[in_ep_cntr];
+
+ in_ep_cntr++;
+ /* Init EP structure */
+ ep->desc = NULL;
+ ep->pcd = pcd;
+ ep->stopped = 1;
+
+ /* Init DWC ep structure */
+ ep->dwc_ep.is_in = 1;
+ ep->dwc_ep.num = i;
+ ep->dwc_ep.active = 0;
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* Control until ep is actvated */
+ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+ ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+ ep->dwc_ep.dma_addr = 0;
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = 0;
+ ep->queue_sof = 0;
+
+ ep->ep.name = names[i];
+ ep->ep.ops = &dwc_otg_pcd_ep_ops;
+
+ ep->ep.maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list);
+ INIT_LIST_HEAD(&ep->queue);
+ }
+ hwcfg1 >>= 2;
+ }
+
+ out_ep_cntr = 0;
+ hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1 >> 2;
+ for (i = 1; out_ep_cntr < num_out_eps; i++) {
+ if (!(hwcfg1 & 0x1)) {
+ struct pcd_ep *ep = &pcd->out_ep[out_ep_cntr];
+
+ out_ep_cntr++;
+ /* Init EP structure */
+ ep->desc = NULL;
+ ep->pcd = pcd;
+ ep->stopped = 1;
+
+ /* Init DWC ep structure */
+ ep->dwc_ep.is_in = 0;
+ ep->dwc_ep.num = i;
+ ep->dwc_ep.active = 0;
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* Control until ep is actvated */
+ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+ ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+ ep->dwc_ep.dma_addr = 0;
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = 0;
+ ep->queue_sof = 0;
+
+ ep->ep.name = names[15 + i];
+ ep->ep.ops = &dwc_otg_pcd_ep_ops;
+
+ ep->ep.maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list);
+ INIT_LIST_HEAD(&ep->queue);
+ }
+ hwcfg1 >>= 2;
+ }
+
+ /* remove ep0 from the list. There is a ep0 pointer. */
+ list_del_init(&pcd->ep0.ep.ep_list);
+
+ pcd->ep0state = EP0_DISCONNECT;
+ pcd->ep0.ep.maxpacket = MAX_EP0_SIZE;
+ pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE;
+ pcd->ep0.dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+}
+
+/**
+ * This function releases the Gadget device.
+ * required by device_unregister().
+ */
+static void dwc_otg_pcd_gadget_release(struct device *dev)
+{
+ pr_info("%s(%p)\n", __func__, dev);
+}
+
+/**
+ * Allocates the buffers for the setup packets when the PCD portion of the
+ * driver is first initialized.
+ */
+static int init_pkt_buffs(struct device *dev, struct dwc_pcd *pcd)
+{
+ if (pcd->otg_dev->core_if->dma_enable) {
+ pcd->dwc_pool = dma_pool_create("dwc_otg_pcd", dev,
+ sizeof(*pcd->setup_pkt) * 5, 32,
+ 0);
+ if (!pcd->dwc_pool)
+ return -ENOMEM;
+ pcd->setup_pkt = dma_pool_alloc(pcd->dwc_pool, GFP_KERNEL,
+ &pcd->setup_pkt_dma_handle);
+ if (!pcd->setup_pkt)
+ goto error;
+ pcd->status_buf = dma_pool_alloc(pcd->dwc_pool, GFP_KERNEL,
+ &pcd->status_buf_dma_handle);
+ if (!pcd->status_buf)
+ goto error1;
+ } else {
+ pcd->setup_pkt = kmalloc(sizeof(*pcd->setup_pkt) * 5,
+ GFP_KERNEL);
+ if (!pcd->setup_pkt)
+ return -ENOMEM;
+ pcd->status_buf = kmalloc(sizeof(u16), GFP_KERNEL);
+ if (!pcd->status_buf) {
+ kfree(pcd->setup_pkt);
+ return -ENOMEM;
+ }
+ }
+ return 0;
+
+error1:
+ dma_pool_free(pcd->dwc_pool, pcd->setup_pkt, pcd->setup_pkt_dma_handle);
+error:
+ dma_pool_destroy(pcd->dwc_pool);
+ return -ENOMEM;
+}
+
+/**
+ * This function initializes the PCD portion of the driver.
+ */
+int dwc_otg_pcd_init(struct device *dev)
+{
+ static const char pcd_name[] = "dwc_otg_pcd";
+ struct dwc_pcd *pcd;
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(dev);
+ struct core_if *core_if = otg_dev->core_if;
+ int retval;
+
+ /* Allocate PCD structure */
+ pcd = kzalloc(sizeof(*pcd), GFP_KERNEL);
+ if (!pcd) {
+ retval = -ENOMEM;
+ goto err;
+ }
+
+ spin_lock_init(&pcd->lock);
+
+ otg_dev->pcd = pcd;
+ s_pcd = pcd;
+ pcd->gadget.name = pcd_name;
+
+ dev_set_name(&pcd->gadget.dev, "gadget");
+ pcd->otg_dev = otg_dev;
+ pcd->gadget.dev.parent = dev;
+ pcd->gadget.dev.release = dwc_otg_pcd_gadget_release;
+ pcd->gadget.ops = &dwc_otg_pcd_ops;
+
+ if (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4))
+ pr_info("Dedicated Tx FIFOs mode\n");
+ else
+ pr_info("Shared Tx FIFO mode\n");
+
+ pcd->gadget.is_dualspeed = check_is_dual_speed(core_if);
+ pcd->gadget.is_otg = check_is_otg(core_if);
+
+ /* Register the gadget device */
+ retval = device_register(&pcd->gadget.dev);
+ if (retval)
+ goto unreg_device;
+
+
+ /* hook up the gadget*/
+ retval = usb_add_gadget_udc(dev, &pcd->gadget);
+ if (retval)
+ goto unreg_device;
+
+ /* Initialized the Core for Device mode. */
+ if (dwc_otg_is_device_mode(core_if))
+ dwc_otg_core_dev_init(core_if);
+
+ /* Initialize EP structures */
+ dwc_otg_pcd_reinit(pcd);
+
+ /* Register the PCD Callbacks. */
+ dwc_otg_cil_register_pcd_callbacks(core_if, &pcd_callbacks, pcd);
+
+ /* Setup interupt handler */
+ retval = request_irq(otg_dev->irq, dwc_otg_pcd_irq, IRQF_SHARED,
+ pcd->gadget.name, pcd);
+ if (retval) {
+ pr_err("request of irq%d failed\n", otg_dev->irq);
+ retval = -EBUSY;
+ goto err_cleanup;
+ }
+
+ /* Initialize the DMA buffer for SETUP packets */
+ retval = init_pkt_buffs(dev, pcd);
+ if (retval)
+ goto err_cleanup;
+
+ /* Initialize tasklet */
+ start_xfer_tasklet.data = (unsigned long)pcd;
+ pcd->start_xfer_tasklet = &start_xfer_tasklet;
+ return 0;
+
+err_cleanup:
+ kfree(pcd);
+ otg_dev->pcd = NULL;
+ s_pcd = NULL;
+
+unreg_device:
+ device_unregister(&pcd->gadget.dev);
+
+err:
+ return retval;
+}
+
+/**
+ * Cleanup the PCD.
+ */
+void __devexit dwc_otg_pcd_remove(struct device *dev)
+{
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(dev);
+ struct dwc_pcd *pcd = otg_dev->pcd;
+
+ /* Free the IRQ */
+ free_irq(otg_dev->irq, pcd);
+
+ /* start with the driver above us */
+ if (pcd->driver) {
+ /* should have been done already by driver model core */
+ pr_warning("driver '%s' is still registered\n",
+ pcd->driver->driver.name);
+ usb_gadget_unregister_driver(pcd->driver);
+ }
+ if (pcd->start_xfer_tasklet)
+ tasklet_kill(pcd->start_xfer_tasklet);
+ tasklet_kill(&pcd->test_mode_tasklet);
+
+ device_unregister(&pcd->gadget.dev);
+ if (GET_CORE_IF(pcd)->dma_enable) {
+ dma_pool_free(pcd->dwc_pool, pcd->setup_pkt,
+ pcd->setup_pkt_dma_handle);
+ dma_pool_free(pcd->dwc_pool, pcd->status_buf,
+ pcd->status_buf_dma_handle);
+ dma_pool_destroy(pcd->dwc_pool);
+ } else {
+ kfree(pcd->setup_pkt);
+ kfree(pcd->status_buf);
+ }
+ kfree(pcd);
+ otg_dev->pcd = NULL;
+}
+
+/**
+ * This function registers a gadget driver with the PCD.
+ *
+ * When a driver is successfully registered, it will receive control
+ * requests including set_configuration(), which enables non-control
+ * requests. then usb traffic follows until a disconnect is reported.
+ * then a host may connect again, or the driver might get unbound.
+ */
+static int dwc_gadget_start(struct usb_gadget_driver *driver,
+ int (*bind) (struct usb_gadget *))
+{
+ int retval;
+
+ if (!driver || driver->speed == USB_SPEED_UNKNOWN || !bind ||
+ !driver->unbind || !driver->disconnect || !driver->setup)
+ return -EINVAL;
+
+ if (s_pcd == NULL)
+ return -ENODEV;
+
+ if (s_pcd->driver != NULL)
+ return -EBUSY;
+
+ /* hook up the driver */
+ s_pcd->driver = driver;
+ s_pcd->gadget.dev.driver = &driver->driver;
+
+ retval = bind(&s_pcd->gadget);
+ if (retval) {
+ struct core_if *core_if;
+
+ pr_err("bind to driver %s --> error %d\n",
+ driver->driver.name, retval);
+ core_if = s_pcd->otg_dev->core_if;
+ otg_set_peripheral(core_if->xceiv, &s_pcd->gadget);
+ s_pcd->driver = NULL;
+ s_pcd->gadget.dev.driver = NULL;
+ return retval;
+ }
+ return 0;
+}
+
+/**
+ * This function unregisters a gadget driver
+ */
+static int dwc_gadget_stop(struct usb_gadget_driver *driver)
+{
+ struct core_if *core_if;
+
+ if (!s_pcd)
+ return -ENODEV;
+ if (!driver || driver != s_pcd->driver)
+ return -EINVAL;
+
+ core_if = s_pcd->otg_dev->core_if;
+ core_if->xceiv->state = OTG_STATE_UNDEFINED;
+ otg_set_peripheral(core_if->xceiv, NULL);
+
+ driver->unbind(&s_pcd->gadget);
+ s_pcd->driver = NULL;
+
+ return 0;
+}
diff --git a/drivers/usb/dwc/pcd.h b/drivers/usb/dwc/pcd.h
new file mode 100644
index 0000000..0000446
--- /dev/null
+++ b/drivers/usb/dwc/pcd.h
@@ -0,0 +1,139 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#if !defined(__DWC_PCD_H__)
+#define __DWC_PCD_H__
+
+#include "driver.h"
+
+/*
+ * This file contains the structures, constants, and interfaces for
+ * the Perpherial Contoller Driver (PCD).
+ *
+ * The Peripheral Controller Driver (PCD) for Linux will implement the
+ * Gadget API, so that the existing Gadget drivers can be used. For
+ * the Mass Storage Function driver the File-backed USB Storage Gadget
+ * (FBS) driver will be used. The FBS driver supports the
+ * Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only
+ * transports.
+ *
+ */
+
+/* Invalid DMA Address */
+#define DMA_ADDR_INVALID (~(dma_addr_t) 0)
+/* Maxpacket size for EP0 */
+#define MAX_EP0_SIZE 64
+/* Maxpacket size for any EP */
+#define MAX_PACKET_SIZE 1024
+
+/*
+ * Get the pointer to the core_if from the pcd pointer.
+ */
+#define GET_CORE_IF(_pcd) (_pcd->otg_dev->core_if)
+
+/*
+ * DWC_otg request structure.
+ * This structure is a list of requests.
+ */
+struct pcd_request {
+ struct usb_request req; /* USB Request. */
+ struct list_head queue; /* queue of these requests. */
+ unsigned mapped:1;
+};
+
+static inline ulong in_ep_int_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->in_ep_regs[i] + DWC_DIEPINT;
+}
+static inline ulong out_ep_int_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->out_ep_regs[i] + DWC_DOEPINT;
+}
+static inline ulong in_ep_ctl_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->in_ep_regs[i] + DWC_DIEPCTL;
+}
+
+static inline ulong out_ep_ctl_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->out_ep_regs[i] + DWC_DOEPCTL;
+}
+
+static inline ulong dev_ctl_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->dev_if->dev_global_regs +
+ DWC_DCTL;
+}
+
+static inline ulong dev_diepmsk_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->dev_if->dev_global_regs +
+ DWC_DIEPMSK;
+}
+
+static inline ulong dev_sts_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->dev_if->dev_global_regs +
+ DWC_DSTS;
+}
+
+static inline ulong otg_ctl_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->core_global_regs + DWC_GOTGCTL;
+}
+
+extern int dwc_otg_pcd_init(struct device *dev);
+
+/*
+ * The following functions support managing the DWC_otg controller in device
+ * mode.
+ */
+extern void dwc_otg_ep_activate(struct core_if *core_if, struct dwc_ep *ep);
+extern void dwc_otg_ep_start_transfer(struct core_if *_if, struct dwc_ep *ep);
+extern void dwc_otg_ep_set_stall(struct core_if *core_if, struct dwc_ep *ep);
+extern void dwc_otg_ep_clear_stall(struct core_if *core_if, struct dwc_ep *ep);
+extern void dwc_otg_pcd_remove(struct device *dev);
+extern int dwc_otg_pcd_handle_intr(struct dwc_pcd *pcd);
+extern void dwc_otg_pcd_stop(struct dwc_pcd *pcd);
+extern void request_nuke(struct pcd_ep *ep);
+extern void dwc_otg_pcd_update_otg(struct dwc_pcd *pcd, const unsigned reset);
+extern void dwc_otg_ep0_start_transfer(struct core_if *_if, struct dwc_ep *ep);
+
+extern void request_done(struct pcd_ep *ep, struct pcd_request *req,
+ int _status);
+
+extern void dwc_start_next_request(struct pcd_ep *ep);
+#endif
diff --git a/drivers/usb/dwc/pcd_intr.c b/drivers/usb/dwc/pcd_intr.c
new file mode 100644
index 0000000..ea0ce78
--- /dev/null
+++ b/drivers/usb/dwc/pcd_intr.c
@@ -0,0 +1,2312 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#include "driver.h"
+#include "pcd.h"
+
+/**
+ * This function returns pointer to in ep struct with number num
+ */
+static struct pcd_ep *get_in_ep(struct dwc_pcd *pcd, u32 num)
+{
+ if (num == 0) {
+ return &pcd->ep0;
+ } else {
+ u32 i;
+ int num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps;
+
+ for (i = 0; i < num_in_eps; ++i) {
+ if (pcd->in_ep[i].dwc_ep.num == num)
+ return &pcd->in_ep[i];
+ }
+ }
+ return NULL;
+}
+
+/**
+ * This function returns pointer to out ep struct with number num
+ */
+static struct pcd_ep *get_out_ep(struct dwc_pcd *pcd, u32 num)
+{
+ if (num == 0) {
+ return &pcd->ep0;
+ } else {
+ u32 i;
+ int num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps;
+
+ for (i = 0; i < num_out_eps; ++i) {
+ if (pcd->out_ep[i].dwc_ep.num == num)
+ return &pcd->out_ep[i];
+ }
+ }
+ return NULL;
+}
+
+/**
+ * This functions gets a pointer to an EP from the wIndex address
+ * value of the control request.
+ */
+static struct pcd_ep *get_ep_by_addr(struct dwc_pcd *pcd, u16 index)
+{
+ struct pcd_ep *ep;
+
+ if (!(index & USB_ENDPOINT_NUMBER_MASK))
+ return &pcd->ep0;
+
+ list_for_each_entry(ep, &pcd->gadget.ep_list, ep.ep_list) {
+ u8 bEndpointAddress;
+
+ if (!ep->desc)
+ continue;
+
+ bEndpointAddress = ep->desc->bEndpointAddress;
+ if ((index ^ bEndpointAddress) & USB_DIR_IN)
+ continue;
+
+ if ((index & 0x0f) == (bEndpointAddress & 0x0f))
+ return ep;
+ }
+ return NULL;
+}
+
+/**
+ * This function checks the EP request queue, if the queue is not
+ * empty the next request is started.
+ */
+void dwc_start_next_request(struct pcd_ep *ep)
+{
+ if (!list_empty(&ep->queue)) {
+ struct pcd_request *req;
+
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+
+ /* Setup and start the Transfer */
+ ep->dwc_ep.start_xfer_buff = req->req.buf;
+ ep->dwc_ep.xfer_buff = req->req.buf;
+ ep->dwc_ep.xfer_len = req->req.length;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.dma_addr = req->req.dma;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * When a new transfer will be started, mark this
+ * endpoint as active. This way it will be blocked
+ * for further transfers, until the current transfer
+ * is finished.
+ */
+ if (dwc_has_feature(GET_CORE_IF(ep->pcd), DWC_LIMITED_XFER))
+ ep->dwc_ep.active = 1;
+
+ dwc_otg_ep_start_transfer(GET_CORE_IF(ep->pcd), &ep->dwc_ep);
+ }
+}
+
+/**
+ * This function handles the SOF Interrupts. At this time the SOF
+ * Interrupt is disabled.
+ */
+static int dwc_otg_pcd_handle_sof_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 gintsts;
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTMSK_STRT_OF_FRM;
+ dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This function reads the 8 bytes of the setup packet from the Rx FIFO into the
+ * destination buffer. It is called from the Rx Status Queue Level (RxStsQLvl)
+ * interrupt routine when a SETUP packet has been received in Slave mode.
+ */
+static void dwc_otg_read_setup_packet(struct core_if *core_if, u32 * dest)
+{
+ dest[0] = dwc_read_fifo32(core_if->data_fifo[0]);
+ dest[1] = dwc_read_fifo32(core_if->data_fifo[0]);
+}
+
+/**
+ * This function handles the Rx Status Queue Level Interrupt, which
+ * indicates that there is a least one packet in the Rx FIFO. The
+ * packets are moved from the FIFO to memory, where they will be
+ * processed when the Endpoint Interrupt Register indicates Transfer
+ * Complete or SETUP Phase Done.
+ *
+ * Repeat the following until the Rx Status Queue is empty:
+ * -# Read the Receive Status Pop Register (GRXSTSP) to get Packet
+ * info
+ * -# If Receive FIFO is empty then skip to step Clear the interrupt
+ * and exit
+ * -# If SETUP Packet call dwc_otg_read_setup_packet to copy the
+ * SETUP data to the buffer
+ * -# If OUT Data Packet call dwc_otg_read_packet to copy the data
+ * to the destination buffer
+ */
+static int dwc_otg_pcd_handle_rx_status_q_level_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong global_regs = core_if->core_global_regs;
+ u32 gintmask = 0;
+ u32 grxsts;
+ struct pcd_ep *ep;
+ u32 gintsts;
+
+ /* Disable the Rx Status Queue Level interrupt */
+ gintmask |= DWC_INTMSK_RXFIFO_NOT_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, gintmask, 0);
+
+ /* Get the Status from the top of the FIFO */
+ grxsts = dwc_reg_read(global_regs, DWC_GRXSTSP);
+
+ /* Get pointer to EP structure */
+ ep = get_out_ep(pcd, DWC_DM_RXSTS_CHAN_NUM_RD(grxsts));
+
+ switch (DWC_DM_RXSTS_PKT_STS_RD(grxsts)) {
+ case DWC_DSTS_GOUT_NAK:
+ break;
+ case DWC_STS_DATA_UPDT:
+ if ((grxsts & DWC_DM_RXSTS_BYTE_CNT) && ep->dwc_ep.xfer_buff) {
+ dwc_otg_read_packet(core_if, ep->dwc_ep.xfer_buff,
+ DWC_DM_RXSTS_BYTE_CNT_RD(grxsts));
+ ep->dwc_ep.xfer_count +=
+ DWC_DM_RXSTS_BYTE_CNT_RD(grxsts);
+ ep->dwc_ep.xfer_buff +=
+ DWC_DM_RXSTS_BYTE_CNT_RD(grxsts);
+ }
+ break;
+ case DWC_STS_XFER_COMP:
+ break;
+ case DWC_DSTS_SETUP_COMP:
+ break;
+ case DWC_DSTS_SETUP_UPDT:
+ dwc_otg_read_setup_packet(core_if, pcd->setup_pkt->d32);
+ ep->dwc_ep.xfer_count += DWC_DM_RXSTS_BYTE_CNT_RD(grxsts);
+ break;
+ default:
+ pr_err("RX_STS_Q Interrupt: Unknown status %d\n",
+ DWC_HM_RXSTS_PKT_STS_RD(grxsts));
+ break;
+ }
+
+ /* Enable the Rx Status Queue Level interrupt */
+ dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmask);
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_RXFIFO_NOT_EMPT;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+
+ return 1;
+}
+
+/**
+ * This function examines the Device IN Token Learning Queue to
+ * determine the EP number of the last IN token received. This
+ * implementation is for the Mass Storage device where there are only
+ * 2 IN EPs (Control-IN and BULK-IN).
+ *
+ * The EP numbers for the first six IN Tokens are in DTKNQR1 and there
+ * are 8 EP Numbers in each of the other possible DTKNQ Registers.
+ */
+static int get_ep_of_last_in_token(struct core_if *core_if)
+{
+ ulong regs = core_if->dev_if->dev_global_regs;
+ const u32 TOKEN_Q_DEPTH =
+ DWC_HWCFG2_DEV_TKN_Q_DEPTH_RD(core_if->hwcfg2);
+ /* Number of Token Queue Registers */
+ const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8;
+ u32 dtknqr1 = 0;
+ u32 in_tkn_epnums[4];
+ int ndx;
+ u32 i;
+ u32 addr = regs + DWC_DTKNQR1;
+ int epnum = 0;
+
+ /* Read the DTKNQ Registers */
+ for (i = 0; i <= DTKNQ_REG_CNT; i++) {
+ in_tkn_epnums[i] = dwc_reg_read(addr, 0);
+
+ if (addr == (regs + DWC_DVBUSDIS))
+ addr = regs + DWC_DTKNQR3_DTHRCTL;
+ else
+ ++addr;
+ }
+
+ /* Copy the DTKNQR1 data to the bit field. */
+ dtknqr1 = in_tkn_epnums[0];
+
+ /* Get the EP numbers */
+ in_tkn_epnums[0] = DWC_DTKNQR1_EP_TKN_NO_RD(dtknqr1);
+ ndx = DWC_DTKNQR1_INT_TKN_Q_WR_PTR_RD(dtknqr1) - 1;
+
+ if (ndx == -1) {
+ /*
+ * Calculate the max queue position.
+ */
+ int cnt = TOKEN_Q_DEPTH;
+
+ if (TOKEN_Q_DEPTH <= 6)
+ cnt = TOKEN_Q_DEPTH - 1;
+ else if (TOKEN_Q_DEPTH <= 14)
+ cnt = TOKEN_Q_DEPTH - 7;
+ else if (TOKEN_Q_DEPTH <= 22)
+ cnt = TOKEN_Q_DEPTH - 15;
+ else
+ cnt = TOKEN_Q_DEPTH - 23;
+
+ epnum = (in_tkn_epnums[DTKNQ_REG_CNT - 1] >> (cnt * 4)) & 0xF;
+ } else {
+ if (ndx <= 5) {
+ epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF;
+ } else if (ndx <= 13) {
+ ndx -= 6;
+ epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF;
+ } else if (ndx <= 21) {
+ ndx -= 14;
+ epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF;
+ } else if (ndx <= 29) {
+ ndx -= 22;
+ epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF;
+ }
+ }
+
+ return epnum;
+}
+
+static inline int count_dwords(struct pcd_ep *ep, u32 len)
+{
+ if (len > ep->dwc_ep.maxpacket)
+ len = ep->dwc_ep.maxpacket;
+ return (len + 3) / 4;
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the EP. For
+ * non-periodic EPs the non-periodic Tx FIFO is written. For periodic EPs the
+ * periodic Tx FIFO associated with the EP is written with all packets for the
+ * next micro-frame.
+ *
+ * The buffer is padded to DWORD on a per packet basis in
+ * slave/dma mode if the MPS is not DWORD aligned. The last packet, if
+ * short, is also padded to a multiple of DWORD.
+ *
+ * ep->xfer_buff always starts DWORD aligned in memory and is a
+ * multiple of DWORD in length
+ *
+ * ep->xfer_len can be any number of bytes
+ *
+ * ep->xfer_count is a multiple of ep->maxpacket until the last packet
+ *
+ * FIFO access is DWORD
+ */
+static void dwc_otg_ep_write_packet(struct core_if *core_if, struct dwc_ep *ep,
+ int dma)
+{
+ u32 i;
+ u32 byte_count;
+ u32 dword_count;
+ u32 fifo;
+ u32 *data_buff = (u32 *) ep->xfer_buff;
+
+ if (ep->xfer_count >= ep->xfer_len)
+ return;
+
+ /* Find the byte length of the packet either short packet or MPS */
+ if ((ep->xfer_len - ep->xfer_count) < ep->maxpacket)
+ byte_count = ep->xfer_len - ep->xfer_count;
+ else
+ byte_count = ep->maxpacket;
+
+ /*
+ * Find the DWORD length, padded by extra bytes as neccessary if MPS
+ * is not a multiple of DWORD
+ */
+ dword_count = (byte_count + 3) / 4;
+
+ fifo = core_if->data_fifo[ep->num];
+
+ if (!dma)
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(fifo, *data_buff);
+
+ ep->xfer_count += byte_count;
+ ep->xfer_buff += byte_count;
+ ep->dma_addr += byte_count;
+}
+
+/**
+ * This interrupt occurs when the non-periodic Tx FIFO is half-empty.
+ * The active request is checked for the next packet to be loaded into
+ * the non-periodic Tx FIFO.
+ */
+static int dwc_otg_pcd_handle_np_tx_fifo_empty_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong global_regs = core_if->core_global_regs;
+ u32 txstatus = 0;
+ u32 gintsts = 0;
+ int epnum;
+ struct pcd_ep *ep;
+ u32 len;
+ int dwords;
+
+ /* Get the epnum from the IN Token Learning Queue. */
+ epnum = get_ep_of_last_in_token(core_if);
+ ep = get_in_ep(pcd, epnum);
+
+ txstatus = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+
+ /*
+ * While there is space in the queue, space in the FIFO, and data to
+ * tranfer, write packets to the Tx FIFO
+ */
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ while ((DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(txstatus) > 0) &&
+ (DWC_GNPTXSTS_NPTXFSPCAVAIL_RD(txstatus) > dwords) &&
+ ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) {
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * When a new transfer will be started, mark this
+ * endpoint as active. This way it will be blocked
+ * for further transfers, until the current transfer
+ * is finished.
+ */
+ if (dwc_has_feature(core_if, DWC_LIMITED_XFER))
+ ep->dwc_ep.active = 1;
+
+ dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0);
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ txstatus = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+ }
+
+ /* Clear nptxfempty interrupt */
+ gintsts |= DWC_INTMSK_RXFIFO_NOT_EMPT;
+ dwc_reg_write(global_regs, DWC_GINTSTS, gintsts);
+
+ /* Re-enable tx-fifo empty interrupt, if packets are stil pending */
+ if (len)
+ dwc_reg_modify(global_regs, DWC_GINTSTS, 0, gintsts);
+ return 1;
+}
+
+/**
+ * This function is called when dedicated Tx FIFO Empty interrupt occurs.
+ * The active request is checked for the next packet to be loaded into
+ * apropriate Tx FIFO.
+ */
+static int write_empty_tx_fifo(struct dwc_pcd *pcd, u32 epnum)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong regs;
+ u32 txstatus = 0;
+ struct pcd_ep *ep;
+ u32 len;
+ int dwords;
+ u32 diepint = 0;
+
+ ep = get_in_ep(pcd, epnum);
+ regs = core_if->dev_if->in_ep_regs[epnum];
+ txstatus = dwc_reg_read(regs, DWC_DTXFSTS);
+
+ /*
+ * While there is space in the queue, space in the FIFO and data to
+ * tranfer, write packets to the Tx FIFO
+ */
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ while (DWC_DTXFSTS_TXFSSPC_AVAI_RD(txstatus) > dwords
+ && ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len
+ && ep->dwc_ep.xfer_len != 0) {
+ dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0);
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ txstatus = dwc_reg_read(regs, DWC_DTXFSTS);
+ }
+ /* Clear emptyintr */
+ diepint = DWC_DIEPINT_TXFIFO_EMPTY_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, epnum), 0, diepint);
+ return 1;
+}
+
+/**
+ * This function is called when the Device is disconnected. It stops any active
+ * requests and informs the Gadget driver of the disconnect.
+ */
+void dwc_otg_pcd_stop(struct dwc_pcd *pcd)
+{
+ int i, num_in_eps, num_out_eps;
+ struct pcd_ep *ep;
+ u32 intr_mask = 0;
+ ulong global_regs = GET_CORE_IF(pcd)->core_global_regs;
+
+ num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps;
+ num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps;
+
+ /* Don't disconnect drivers more than once */
+ if (pcd->ep0state == EP0_DISCONNECT)
+ return;
+ pcd->ep0state = EP0_DISCONNECT;
+
+ /* Reset the OTG state. */
+ dwc_otg_pcd_update_otg(pcd, 1);
+
+ /* Disable the NP Tx Fifo Empty Interrupt. */
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0);
+
+ /* Flush the FIFOs */
+ dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), 0);
+ dwc_otg_flush_rx_fifo(GET_CORE_IF(pcd));
+
+ /* Prevent new request submissions, kill any outstanding requests */
+ ep = &pcd->ep0;
+ request_nuke(ep);
+
+ /* Prevent new request submissions, kill any outstanding requests */
+ for (i = 0; i < num_in_eps; i++)
+ request_nuke((struct pcd_ep *)&pcd->in_ep[i]);
+
+ /* Prevent new request submissions, kill any outstanding requests */
+ for (i = 0; i < num_out_eps; i++)
+ request_nuke((struct pcd_ep *)&pcd->out_ep[i]);
+
+ /* Report disconnect; the driver is already quiesced */
+ if (pcd->driver && pcd->driver->disconnect) {
+ spin_unlock(&pcd->lock);
+ pcd->driver->disconnect(&pcd->gadget);
+ spin_lock(&pcd->lock);
+ }
+}
+
+/**
+ * This interrupt indicates that ...
+ */
+static int dwc_otg_pcd_handle_i2c_intr(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts;
+
+ pr_info("Interrupt handler not implemented for i2cintr\n");
+
+ /* Turn off and clean the interrupt */
+ intr_mask |= DWC_INTMSK_I2C_INTR;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_I2C_INTR;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+
+ return 1;
+}
+
+/**
+ * This interrupt indicates that ...
+ */
+static int dwc_otg_pcd_handle_early_suspend_intr(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts;
+
+ pr_info("Early Suspend Detected\n");
+
+ /* Turn off and clean the interrupt */
+ intr_mask |= DWC_INTMSK_EARLY_SUSP;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_EARLY_SUSP;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+
+ return 1;
+}
+
+/**
+ * This function configures EPO to receive SETUP packets.
+ *
+ * Program the following fields in the endpoint specific registers for Control
+ * OUT EP 0, in order to receive a setup packet:
+ *
+ * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back setup packets)
+ *
+ * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back to back setup
+ * packets)
+ *
+ * In DMA mode, DOEPDMA0 Register with a memory address to store any setup
+ * packets received
+ */
+static void ep0_out_start(struct core_if *core_if, struct dwc_pcd *pcd)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 doeptsize0 = 0;
+
+ doeptsize0 = DWC_DEPTSIZ0_SUPCNT_RW(doeptsize0, 3);
+ doeptsize0 = DWC_DEPTSIZ0_PKT_CNT_RW(doeptsize0, 1);
+ doeptsize0 = DWC_DEPTSIZ0_XFER_SIZ_RW(doeptsize0, 8 * 3);
+ dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPTSIZ, doeptsize0);
+
+ if (core_if->dma_enable) {
+ u32 doepctl = 0;
+
+ dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPDMA,
+ pcd->setup_pkt_dma_handle);
+ doepctl = DWC_DEPCTL_EPENA_RW(doepctl, 1);
+ doepctl = DWC_DEPCTL_ACT_EP_RW(doepctl, 1);
+ dwc_reg_write(out_ep_ctl_reg(pcd, 0), 0, doepctl);
+ }
+}
+
+/**
+ * This interrupt occurs when a USB Reset is detected. When the USB Reset
+ * Interrupt occurs the device state is set to DEFAULT and the EP0 state is set
+ * to IDLE.
+ *
+ * Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1)
+ *
+ * Unmask the following interrupt bits:
+ * - DAINTMSK.INEP0 = 1 (Control 0 IN endpoint)
+ * - DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint)
+ * - DOEPMSK.SETUP = 1
+ * - DOEPMSK.XferCompl = 1
+ * - DIEPMSK.XferCompl = 1
+ * - DIEPMSK.TimeOut = 1
+ *
+ * Program the following fields in the endpoint specific registers for Control
+ * OUT EP 0, in order to receive a setup packet
+ * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back setup packets)
+ * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back to back setup
+ * packets)
+ *
+ * - In DMA mode, DOEPDMA0 Register with a memory address to store any setup
+ * packets received
+ *
+ * At this point, all the required initialization, except for enabling
+ * the control 0 OUT endpoint is done, for receiving SETUP packets.
+ *
+ * Note that the bits in the Device IN endpoint mask register (diepmsk) are laid
+ * out exactly the same as the Device IN endpoint interrupt register (diepint.)
+ * Likewise for Device OUT endpoint mask / interrupt registers (doepmsk /
+ * doepint.)
+ */
+static int dwc_otg_pcd_handle_usb_reset_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ u32 doepctl = 0;
+ u32 daintmsk = 0;
+ u32 doepmsk = 0;
+ u32 diepmsk = 0;
+ u32 dcfg = 0;
+ u32 resetctl = 0;
+ u32 dctl = 0;
+ u32 i;
+ u32 gintsts = 0;
+
+ pr_info("USB RESET\n");
+
+ /* reset the HNP settings */
+ dwc_otg_pcd_update_otg(pcd, 1);
+
+ /* Clear the Remote Wakeup Signalling */
+ dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1);
+ dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, 0);
+
+ /* Set NAK for all OUT EPs */
+ doepctl = DWC_DEPCTL_SET_NAK_RW(doepctl, 1);
+ for (i = 0; i <= dev_if->num_out_eps; i++)
+ dwc_reg_write(out_ep_ctl_reg(pcd, i), 0, doepctl);
+
+ /* Flush the NP Tx FIFO */
+ dwc_otg_flush_tx_fifo(core_if, 0);
+
+ /* Flush the Learning Queue */
+ resetctl |= DWC_RSTCTL_TKN_QUE_FLUSH;
+ dwc_reg_write(core_if->core_global_regs, DWC_GRSTCTL, resetctl);
+
+ daintmsk |= DWC_DAINT_INEP00;
+ daintmsk |= DWC_DAINT_OUTEP00;
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DAINTMSK, daintmsk);
+
+ doepmsk = DWC_DOEPMSK_SETUP_DONE_RW(doepmsk, 1);
+ doepmsk = DWC_DOEPMSK_AHB_ERROR_RW(doepmsk, 1);
+ doepmsk = DWC_DOEPMSK_EP_DISA_RW(doepmsk, 1);
+ doepmsk = DWC_DOEPMSK_TX_COMPL_RW(doepmsk, 1);
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DOEPMSK, doepmsk);
+
+ diepmsk = DWC_DIEPMSK_TX_CMPL_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_TOUT_COND_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_EP_DISA_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_AHB_ERROR_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1);
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DIEPMSK, diepmsk);
+
+ /* Reset Device Address */
+ dcfg = dwc_reg_read(dev_if->dev_global_regs, DWC_DCFG);
+ dcfg = DWC_DCFG_DEV_ADDR_WR(dcfg, 0);
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DCFG, dcfg);
+
+ /* setup EP0 to receive SETUP packets */
+ ep0_out_start(core_if, pcd);
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_USB_RST;
+ dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts);
+
+ return 1;
+}
+
+/**
+ * Get the device speed from the device status register and convert it
+ * to USB speed constant.
+ */
+static int get_device_speed(struct dwc_pcd *pcd)
+{
+ u32 dsts = 0;
+ enum usb_device_speed speed = USB_SPEED_UNKNOWN;
+
+ dsts = dwc_reg_read(dev_sts_reg(pcd), 0);
+
+ switch (DWC_DSTS_ENUM_SPEED_RD(dsts)) {
+ case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ:
+ speed = USB_SPEED_HIGH;
+ break;
+ case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ:
+ case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ:
+ speed = USB_SPEED_FULL;
+ break;
+ case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ:
+ speed = USB_SPEED_LOW;
+ break;
+ }
+ return speed;
+}
+
+/**
+ * This function enables EP0 OUT to receive SETUP packets and configures EP0
+ * IN for transmitting packets. It is normally called when the "Enumeration
+ * Done" interrupt occurs.
+ */
+static void dwc_otg_ep0_activate(struct core_if *core_if, struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 dsts;
+ u32 diepctl = 0;
+ u32 doepctl = 0;
+ u32 dctl = 0;
+
+ /* Read the Device Status and Endpoint 0 Control registers */
+ dsts = dwc_reg_read(dev_if->dev_global_regs, DWC_DSTS);
+ diepctl = dwc_reg_read(dev_if->in_ep_regs[0], DWC_DIEPCTL);
+ doepctl = dwc_reg_read(dev_if->out_ep_regs[0], DWC_DOEPCTL);
+
+ /* Set the MPS of the IN EP based on the enumeration speed */
+ switch (DWC_DSTS_ENUM_SPEED_RD(dsts)) {
+ case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ:
+ case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ:
+ case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ:
+ diepctl = DWC_DEPCTL_MPS_RW(diepctl, DWC_DEP0CTL_MPS_64);
+ break;
+ case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ:
+ diepctl = DWC_DEPCTL_MPS_RW(diepctl, DWC_DEP0CTL_MPS_8);
+ break;
+ }
+ dwc_reg_write(dev_if->in_ep_regs[0], DWC_DIEPCTL, diepctl);
+
+ /* Enable OUT EP for receive */
+ doepctl = DWC_DEPCTL_EPENA_RW(doepctl, 1);
+ dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPCTL, doepctl);
+
+ dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1);
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DCTL, dctl, dctl);
+}
+
+/**
+ * Read the device status register and set the device speed in the
+ * data structure.
+ * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate.
+ */
+static int dwc_otg_pcd_handle_enum_done_intr(struct dwc_pcd *pcd)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+ u32 gintsts;
+ u32 gusbcfg;
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong global_regs = core_if->core_global_regs;
+ u32 gsnpsid = global_regs + DWC_GSNPSID;
+ u8 utmi16b, utmi8b;
+
+ if (gsnpsid >= (u32) 0x4f54260a) {
+ utmi16b = 5;
+ utmi8b = 9;
+ } else {
+ utmi16b = 4;
+ utmi8b = 8;
+ }
+ dwc_otg_ep0_activate(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+ pcd->ep0state = EP0_IDLE;
+ ep0->stopped = 0;
+ pcd->gadget.speed = get_device_speed(pcd);
+
+ gusbcfg = dwc_reg_read(global_regs, DWC_GUSBCFG);
+
+ /* Set USB turnaround time based on device speed and PHY interface. */
+ if (pcd->gadget.speed == USB_SPEED_HIGH) {
+ switch (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2)) {
+ case DWC_HWCFG2_HS_PHY_TYPE_ULPI:
+ gusbcfg =
+ (gusbcfg & (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(9);
+ break;
+ case DWC_HWCFG2_HS_PHY_TYPE_UTMI:
+ if (DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD(core_if->hwcfg4)
+ == 0)
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi8b);
+ else if (DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD
+ (core_if->hwcfg4) == 1)
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi16b);
+ else if (core_if->core_params->phy_utmi_width == 8)
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi8b);
+ else
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi16b);
+ break;
+ case DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI:
+ if (gusbcfg & DWC_USBCFG_ULPI_UTMI_SEL) {
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(9);
+ } else {
+ if (core_if->core_params->phy_utmi_width == 16)
+ gusbcfg =
+ (gusbcfg &
+ (~
+ ((u32) DWC_USBCFG_TRN_TIME(0xf))))
+ | DWC_USBCFG_TRN_TIME(utmi16b);
+ else
+ gusbcfg =
+ (gusbcfg &
+ (~
+ ((u32) DWC_USBCFG_TRN_TIME(0xf))))
+ | DWC_USBCFG_TRN_TIME(utmi8b);
+ }
+ break;
+ }
+ } else {
+ /* Full or low speed */
+ gusbcfg = (gusbcfg & (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(9);
+ }
+ dwc_reg_write(global_regs, DWC_GUSBCFG, gusbcfg);
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_ENUM_DONE;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+
+ return 1;
+}
+
+/**
+ * This interrupt indicates that the ISO OUT Packet was dropped due to
+ * Rx FIFO full or Rx Status Queue Full. If this interrupt occurs
+ * read all the data from the Rx FIFO.
+ */
+static int dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts;
+
+ pr_info("Interrupt Handler not implemented for ISOC Out " "Dropped\n");
+
+ /* Turn off and clear the interrupt */
+ intr_mask |= DWC_INTMSK_ISYNC_OUTPKT_DRP;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_ISYNC_OUTPKT_DRP;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+
+ return 1;
+}
+
+/**
+ * This interrupt indicates the end of the portion of the micro-frame
+ * for periodic transactions. If there is a periodic transaction for
+ * the next frame, load the packets into the EP periodic Tx FIFO.
+ */
+static int dwc_otg_pcd_handle_end_periodic_frame_intr(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts;
+
+ pr_info("Interrupt handler not implemented for End of "
+ "Periodic Portion of Micro-Frame Interrupt");
+
+ /* Turn off and clear the interrupt */
+ intr_mask |= DWC_INTMSK_END_OF_PFRM;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_END_OF_PFRM;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+
+ return 1;
+}
+
+/**
+ * This interrupt indicates that EP of the packet on the top of the
+ * non-periodic Tx FIFO does not match EP of the IN Token received.
+ *
+ * The "Device IN Token Queue" Registers are read to determine the
+ * order the IN Tokens have been received. The non-periodic Tx FIFO is flushed,
+ * so it can be reloaded in the order seen in the IN Token Queue.
+ */
+static int dwc_otg_pcd_handle_ep_mismatch_intr(struct core_if *core_if)
+{
+ u32 intr_mask = 0;
+ u32 gintsts;
+
+ pr_info("Interrupt handler not implemented for End Point "
+ "Mismatch\n");
+
+ /* Turn off and clear the interrupt */
+ intr_mask |= DWC_INTMSK_ENDP_MIS_MTCH;
+ dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_ENDP_MIS_MTCH;
+ dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This funcion stalls EP0.
+ */
+static void ep0_do_stall(struct dwc_pcd *pcd, const int val)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+ struct usb_ctrlrequest *ctrl = &pcd->setup_pkt->req;
+
+ pr_warning("req %02x.%02x protocol STALL; err %d\n",
+ ctrl->bRequestType, ctrl->bRequest, val);
+
+ ep0->dwc_ep.is_in = 1;
+ dwc_otg_ep_set_stall(pcd->otg_dev->core_if, &ep0->dwc_ep);
+
+ pcd->ep0.stopped = 1;
+ pcd->ep0state = EP0_IDLE;
+ ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * This functions delegates the setup command to the gadget driver.
+ */
+static void do_gadget_setup(struct dwc_pcd *pcd, struct usb_ctrlrequest *ctrl)
+{
+ if (pcd->driver && pcd->driver->setup) {
+ int ret;
+
+ spin_unlock(&pcd->lock);
+ ret = pcd->driver->setup(&pcd->gadget, ctrl);
+ spin_lock(&pcd->lock);
+
+ if (ret < 0)
+ ep0_do_stall(pcd, ret);
+
+ /** This is a g_file_storage gadget driver specific
+ * workaround: a DELAYED_STATUS result from the fsg_setup
+ * routine will result in the gadget queueing a EP0 IN status
+ * phase for a two-stage control transfer.
+ *
+ * Exactly the same as a SET_CONFIGURATION/SET_INTERFACE except
+ * that this is a class specific request. Need a generic way to
+ * know when the gadget driver will queue the status phase.
+ *
+ * Can we assume when we call the gadget driver setup() function
+ * that it will always queue and require the following flag?
+ * Need to look into this.
+ */
+ if (ret == 256 + 999)
+ pcd->request_config = 1;
+ }
+}
+
+/**
+ * This function starts the Zero-Length Packet for the IN status phase
+ * of a 2 stage control transfer.
+ */
+static void do_setup_in_status_phase(struct dwc_pcd *pcd)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+
+ if (pcd->ep0state == EP0_STALL)
+ return;
+
+ pcd->ep0state = EP0_STATUS;
+
+ ep0->dwc_ep.xfer_len = 0;
+ ep0->dwc_ep.xfer_count = 0;
+ ep0->dwc_ep.is_in = 1;
+ ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle;
+ dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+ /* Prepare for more SETUP Packets */
+ ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * This function starts the Zero-Length Packet for the OUT status phase
+ * of a 2 stage control transfer.
+ */
+static void do_setup_out_status_phase(struct dwc_pcd *pcd)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+
+ if (pcd->ep0state == EP0_STALL)
+ return;
+ pcd->ep0state = EP0_STATUS;
+
+ ep0->dwc_ep.xfer_len = 0;
+ ep0->dwc_ep.xfer_count = 0;
+ ep0->dwc_ep.is_in = 0;
+ ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle;
+ dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+ /* Prepare for more SETUP Packets */
+ ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * Clear the EP halt (STALL) and if pending requests start the
+ * transfer.
+ */
+static void pcd_clear_halt(struct dwc_pcd *pcd, struct pcd_ep *ep)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+
+ if (!ep->dwc_ep.stall_clear_flag)
+ dwc_otg_ep_clear_stall(core_if, &ep->dwc_ep);
+
+ /* Reactive the EP */
+ dwc_otg_ep_activate(core_if, &ep->dwc_ep);
+
+ if (ep->stopped) {
+ ep->stopped = 0;
+ /* If there is a request in the EP queue start it */
+
+ /*
+ * dwc_start_next_request(), outside of interpt contxt at some
+ * time after the current time, after a clear-halt setup packet.
+ * Still need to implement ep mismatch in the future if a gadget
+ * ever uses more than one endpoint at once
+ */
+ if (core_if->dma_enable) {
+ ep->queue_sof = 1;
+ tasklet_schedule(pcd->start_xfer_tasklet);
+ } else {
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * To re-enable this endpoint it's important to
+ * set this next_ep number. Otherwise the endpoint
+ * will not get active again after stalling.
+ */
+ if (dwc_has_feature(core_if, DWC_LIMITED_XFER))
+ dwc_start_next_request(ep);
+ }
+ }
+
+ /* Start Control Status Phase */
+ do_setup_in_status_phase(pcd);
+}
+
+/**
+ * This function is called when the SET_FEATURE TEST_MODE Setup packet is sent
+ * from the host. The Device Control register is written with the Test Mode
+ * bits set to the specified Test Mode. This is done as a tasklet so that the
+ * "Status" phase of the control transfer completes before transmitting the TEST
+ * packets.
+ *
+ */
+static void do_test_mode(unsigned long data)
+{
+ u32 dctl = 0;
+ struct dwc_pcd *pcd = (struct dwc_pcd *)data;
+ int test_mode = pcd->test_mode;
+
+ dctl = dwc_reg_read(dev_ctl_reg(pcd), 0);
+ switch (test_mode) {
+ case 1: /* TEST_J */
+ dctl = DWC_DCTL_TST_CTL(dctl, 1);
+ break;
+ case 2: /* TEST_K */
+ dctl = DWC_DCTL_TST_CTL(dctl, 2);
+ break;
+ case 3: /* TEST_SE0_NAK */
+ dctl = DWC_DCTL_TST_CTL(dctl, 3);
+ break;
+ case 4: /* TEST_PACKET */
+ dctl = DWC_DCTL_TST_CTL(dctl, 4);
+ break;
+ case 5: /* TEST_FORCE_ENABLE */
+ dctl = DWC_DCTL_TST_CTL(dctl, 5);
+ break;
+ }
+ dwc_reg_write(dev_ctl_reg(pcd), 0, dctl);
+}
+
+/**
+ * This function process the SET_FEATURE Setup Commands.
+ */
+static void do_set_feature(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong regs = core_if->core_global_regs;
+ struct usb_ctrlrequest ctrl = pcd->setup_pkt->req;
+ int otg_cap = core_if->core_params->otg_cap;
+ u32 gotgctl = 0;
+
+ switch (ctrl.bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (__le16_to_cpu(ctrl.wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ pcd->remote_wakeup_enable = 1;
+ break;
+ case USB_DEVICE_TEST_MODE:
+ /*
+ * Setup the Test Mode tasklet to do the Test
+ * Packet generation after the SETUP Status
+ * phase has completed.
+ */
+
+ pcd->test_mode_tasklet.next = NULL;
+ pcd->test_mode_tasklet.state = 0;
+ atomic_set(&pcd->test_mode_tasklet.count, 0);
+
+ pcd->test_mode_tasklet.func = do_test_mode;
+ pcd->test_mode_tasklet.data = (unsigned long)pcd;
+ pcd->test_mode = __le16_to_cpu(ctrl.wIndex) >> 8;
+ tasklet_schedule(&pcd->test_mode_tasklet);
+
+ break;
+ case USB_DEVICE_B_HNP_ENABLE:
+ /* dev may initiate HNP */
+ if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+ pcd->b_hnp_enable = 1;
+ dwc_otg_pcd_update_otg(pcd, 0);
+ /*
+ * gotgctl.devhnpen cleared by a
+ * USB Reset?
+ */
+ gotgctl |= DWC_GCTL_DEV_HNP_ENA;
+ gotgctl |= DWC_GCTL_HNP_REQ;
+ dwc_reg_write(regs, DWC_GOTGCTL, gotgctl);
+ } else {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ }
+ break;
+ case USB_DEVICE_A_HNP_SUPPORT:
+ /* RH port supports HNP */
+ if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+ pcd->a_hnp_support = 1;
+ dwc_otg_pcd_update_otg(pcd, 0);
+ } else {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ }
+ break;
+ case USB_DEVICE_A_ALT_HNP_SUPPORT:
+ /* other RH port does */
+ if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+ pcd->a_alt_hnp_support = 1;
+ dwc_otg_pcd_update_otg(pcd, 0);
+ } else {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ }
+ break;
+ }
+ do_setup_in_status_phase(pcd);
+ break;
+ case USB_RECIP_INTERFACE:
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ case USB_RECIP_ENDPOINT:
+ if (__le16_to_cpu(ctrl.wValue) == USB_ENDPOINT_HALT) {
+ struct pcd_ep *ep;
+
+ ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex));
+
+ if (ep == NULL) {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ return;
+ }
+
+ ep->stopped = 1;
+ dwc_otg_ep_set_stall(core_if, &ep->dwc_ep);
+ }
+ do_setup_in_status_phase(pcd);
+ break;
+ }
+}
+
+/**
+ * This function process the CLEAR_FEATURE Setup Commands.
+ */
+static void do_clear_feature(struct dwc_pcd *pcd)
+{
+ struct usb_ctrlrequest ctrl = pcd->setup_pkt->req;
+ struct pcd_ep *ep;
+
+ switch (ctrl.bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (__le16_to_cpu(ctrl.wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ pcd->remote_wakeup_enable = 0;
+ break;
+ case USB_DEVICE_TEST_MODE:
+ /* Add CLEAR_FEATURE for TEST modes. */
+ break;
+ }
+ do_setup_in_status_phase(pcd);
+ break;
+ case USB_RECIP_ENDPOINT:
+ ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex));
+ if (ep == NULL) {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ return;
+ }
+
+ pcd_clear_halt(pcd, ep);
+ break;
+ }
+}
+
+/**
+ * This function processes SETUP commands. In Linux, the USB Command processing
+ * is done in two places - the first being the PCD and the second in the Gadget
+ * Driver (for example, the File-Backed Storage Gadget Driver).
+ *
+ * GET_STATUS: Command is processed as defined in chapter 9 of the USB 2.0
+ * Specification chapter 9
+ *
+ * CLEAR_FEATURE: The Device and Endpoint requests are the ENDPOINT_HALT feature
+ * is procesed, all others the interface requests are ignored.
+ *
+ * SET_FEATURE: The Device and Endpoint requests are processed by the PCD.
+ * Interface requests are passed to the Gadget Driver.
+ *
+ * SET_ADDRESS: PCD, Program the DCFG reg, with device address received
+ *
+ * GET_DESCRIPTOR: Gadget Driver, Return the requested descriptor
+ *
+ * SET_DESCRIPTOR: Gadget Driver, Optional - not implemented by any of the
+ * existing Gadget Drivers.
+ *
+ * SET_CONFIGURATION: Gadget Driver, Disable all EPs and enable EPs for new
+ * configuration.
+ *
+ * GET_CONFIGURATION: Gadget Driver, Return the current configuration
+ *
+ * SET_INTERFACE: Gadget Driver, Disable all EPs and enable EPs for new
+ * configuration.
+ *
+ * GET_INTERFACE: Gadget Driver, Return the current interface.
+ *
+ * SYNC_FRAME: Display debug message.
+ *
+ * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are
+ * processed by pcd_setup. Calling the Function Driver's setup function from
+ * pcd_setup processes the gadget SETUP commands.
+ */
+static void pcd_setup(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ struct usb_ctrlrequest ctrl = pcd->setup_pkt->req;
+ struct pcd_ep *ep;
+ struct pcd_ep *ep0 = &pcd->ep0;
+ u16 *status = pcd->status_buf;
+ u32 doeptsize0 = 0;
+
+ doeptsize0 = dwc_reg_read(dev_if->out_ep_regs[0], DWC_DOEPTSIZ);
+
+ /* handle > 1 setup packet , assert error for now */
+ if (core_if->dma_enable && (DWC_DEPTSIZ0_SUPCNT_RD(doeptsize0) < 2))
+ pr_err("\n\n CANNOT handle > 1 setup packet in "
+ "DMA mode\n\n");
+
+ /* Clean up the request queue */
+ request_nuke(ep0);
+ ep0->stopped = 0;
+
+ if (ctrl.bRequestType & USB_DIR_IN) {
+ ep0->dwc_ep.is_in = 1;
+ pcd->ep0state = EP0_IN_DATA_PHASE;
+ } else {
+ ep0->dwc_ep.is_in = 0;
+ pcd->ep0state = EP0_OUT_DATA_PHASE;
+ }
+
+ if ((ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) {
+ /*
+ * Handle non-standard (class/vendor) requests in the gadget
+ * driver
+ */
+ do_gadget_setup(pcd, &ctrl);
+ return;
+ }
+
+ switch (ctrl.bRequest) {
+ case USB_REQ_GET_STATUS:
+ switch (ctrl.bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ *status = 0x1; /* Self powered */
+ *status |= pcd->remote_wakeup_enable << 1;
+ break;
+ case USB_RECIP_INTERFACE:
+ *status = 0;
+ break;
+ case USB_RECIP_ENDPOINT:
+ ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex));
+ if (ep == NULL || __le16_to_cpu(ctrl.wLength) > 2) {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ return;
+ }
+ *status = ep->stopped;
+ break;
+ }
+
+ *status = __cpu_to_le16(*status);
+
+ pcd->ep0_pending = 1;
+ ep0->dwc_ep.start_xfer_buff = (u8 *) status;
+ ep0->dwc_ep.xfer_buff = (u8 *) status;
+ ep0->dwc_ep.dma_addr = pcd->status_buf_dma_handle;
+ ep0->dwc_ep.xfer_len = 2;
+ ep0->dwc_ep.xfer_count = 0;
+ ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len;
+ dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ do_clear_feature(pcd);
+ break;
+ case USB_REQ_SET_FEATURE:
+ do_set_feature(pcd);
+ break;
+ case USB_REQ_SET_ADDRESS:
+ if (ctrl.bRequestType == USB_RECIP_DEVICE) {
+ u32 dcfg = 0;
+
+ dcfg = DWC_DCFG_DEV_ADDR_WR(dcfg,
+ __le16_to_cpu(ctrl.wValue));
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DCFG,
+ 0, dcfg);
+ do_setup_in_status_phase(pcd);
+ return;
+ }
+ break;
+ case USB_REQ_SET_INTERFACE:
+ case USB_REQ_SET_CONFIGURATION:
+ pcd->request_config = 1; /* Configuration changed */
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ case USB_REQ_SYNCH_FRAME:
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ default:
+ /* Call the Gadget Driver's setup functions */
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ }
+}
+
+/**
+ * This function completes the ep0 control transfer.
+ */
+static int ep0_complete_request(struct pcd_ep *ep)
+{
+ struct core_if *core_if = GET_CORE_IF(ep->pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ ulong in_regs = dev_if->in_ep_regs[ep->dwc_ep.num];
+ u32 deptsiz = 0;
+ struct pcd_request *req;
+ int is_last = 0;
+ struct dwc_pcd *pcd = ep->pcd;
+
+ if (pcd->ep0_pending && list_empty(&ep->queue)) {
+ if (ep->dwc_ep.is_in)
+ do_setup_out_status_phase(pcd);
+ else
+ do_setup_in_status_phase(pcd);
+
+ pcd->ep0_pending = 0;
+ pcd->ep0state = EP0_STATUS;
+ return 1;
+ }
+
+ if (list_empty(&ep->queue))
+ return 0;
+
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+
+ if (pcd->ep0state == EP0_STATUS) {
+ is_last = 1;
+ } else if (ep->dwc_ep.is_in) {
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ if (DWC_DEPTSIZ0_XFER_SIZ_RD(deptsiz) == 0) {
+ req->req.actual = ep->dwc_ep.xfer_count;
+ do_setup_out_status_phase(pcd);
+ }
+ } else {
+ /* This is ep0-OUT */
+ req->req.actual = ep->dwc_ep.xfer_count;
+ do_setup_in_status_phase(pcd);
+ }
+
+ /* Complete the request */
+ if (is_last) {
+ request_done(ep, req, 0);
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * This function completes the request for the EP. If there are additional
+ * requests for the EP in the queue they will be started.
+ */
+static void complete_ep(struct pcd_ep *ep)
+{
+ struct core_if *core_if = GET_CORE_IF(ep->pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ ulong in_ep_regs = dev_if->in_ep_regs[ep->dwc_ep.num];
+ u32 deptsiz = 0;
+ struct pcd_request *req = NULL;
+ int is_last = 0;
+
+ /* Get any pending requests */
+ if (!list_empty(&ep->queue))
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+
+ if (ep->dwc_ep.is_in) {
+ deptsiz = dwc_reg_read(in_ep_regs, DWC_DIEPTSIZ);
+
+ if (core_if->dma_enable && !DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz))
+ ep->dwc_ep.xfer_count = ep->dwc_ep.xfer_len;
+
+ if (DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz) == 0 &&
+ DWC_DEPTSIZ_PKT_CNT_RD(deptsiz) == 0 &&
+ ep->dwc_ep.xfer_count == ep->dwc_ep.xfer_len)
+ is_last = 1;
+ else
+ pr_warning("Incomplete transfer (%s-%s "
+ "[siz=%d pkt=%d])\n", ep->ep.name,
+ ep->dwc_ep.is_in ? "IN" : "OUT",
+ DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz),
+ DWC_DEPTSIZ_PKT_CNT_RD(deptsiz));
+ } else {
+ ulong out_ep_regs = dev_if->out_ep_regs[ep->dwc_ep.num];
+
+ deptsiz = dwc_reg_read(out_ep_regs, DWC_DOEPTSIZ);
+ is_last = 1;
+ }
+
+ /* Complete the request */
+ if (is_last) {
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger transfers
+ * into multiple transfers of 1024 bytes sized messages.
+ * I happens often, that transfers of 4096 bytes are
+ * required (zero-gadget, file_storage-gadget).
+ */
+ if ((dwc_has_feature(core_if, DWC_LIMITED_XFER)) &&
+ ep->dwc_ep.bytes_pending) {
+ ulong in_regs =
+ core_if->dev_if->in_ep_regs[ep->dwc_ep.num];
+ u32 intr_mask = 0;
+
+ ep->dwc_ep.xfer_len = ep->dwc_ep.bytes_pending;
+ if (ep->dwc_ep.xfer_len > MAX_XFER_LEN) {
+ ep->dwc_ep.bytes_pending = ep->dwc_ep.xfer_len -
+ MAX_XFER_LEN;
+ ep->dwc_ep.xfer_len = MAX_XFER_LEN;
+ } else {
+ ep->dwc_ep.bytes_pending = 0;
+ }
+
+ /*
+ * Restart the current transfer with the next "chunk"
+ * of data.
+ */
+ ep->dwc_ep.xfer_count = 0;
+
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz,
+ ep->dwc_ep.xfer_len);
+ deptsiz =
+ DWC_DEPTSIZ_PKT_CNT_RW(deptsiz,
+ ((ep->dwc_ep.xfer_len - 1 +
+ ep->dwc_ep.maxpacket) /
+ ep->dwc_ep.maxpacket));
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ intr_mask |= DWC_INTSTS_NP_TXFIFO_EMPT;
+ dwc_reg_modify((core_if->core_global_regs),
+ DWC_GINTSTS, intr_mask, 0);
+ dwc_reg_modify((core_if->core_global_regs),
+ DWC_GINTMSK, intr_mask, intr_mask);
+
+ /*
+ * Just return here if message was not completely
+ * transferred.
+ */
+ return;
+ }
+ if (core_if->dma_enable)
+ req->req.actual = ep->dwc_ep.xfer_len -
+ DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz);
+ else
+ req->req.actual = ep->dwc_ep.xfer_count;
+
+ request_done(ep, req, 0);
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+
+ /* If there is a request in the queue start it. */
+ dwc_start_next_request(ep);
+ }
+}
+
+/**
+ * This function continues control IN transfers started by
+ * dwc_otg_ep0_start_transfer, when the transfer does not fit in a
+ * single packet. NOTE: The DIEPCTL0/DOEPCTL0 registers only have one
+ * bit for the packet count.
+ */
+static void dwc_otg_ep0_continue_transfer(struct core_if *c_if,
+ struct dwc_ep *ep)
+{
+ if (ep->is_in) {
+ u32 depctl = 0;
+ u32 deptsiz = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong in_regs = d_if->in_ep_regs[0];
+ u32 tx_status = 0;
+ ulong glbl_regs = c_if->core_global_regs;
+
+ tx_status = dwc_reg_read(glbl_regs, DWC_GNPTXSTS);
+
+ depctl = dwc_reg_read(in_regs, DWC_DIEPCTL);
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ /*
+ * Program the transfer size and packet count as follows:
+ * xfersize = N * maxpacket + short_packet
+ * pktcnt = N + (short_packet exist ? 1 : 0)
+ */
+ if (ep->total_len - ep->xfer_count > ep->maxpacket)
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->maxpacket);
+ else
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ (ep->total_len -
+ ep->xfer_count));
+
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ ep->xfer_len += DWC_DEPTSIZ0_XFER_SIZ_RD(deptsiz);
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ /* Write the DMA register */
+ if (DWC_HWCFG2_ARCH_RD(c_if->hwcfg2) == DWC_INT_DMA_ARCH)
+ dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr);
+
+ /* EP enable, IN data in FIFO */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(in_regs, DWC_DIEPCTL, depctl);
+
+ /*
+ * Enable the Non-Periodic Tx FIFO empty interrupt, the
+ * data will be written into the fifo by the ISR.
+ */
+ if (!c_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ /* First clear it from GINTSTS */
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_write(glbl_regs, DWC_GINTSTS, intr_mask);
+
+ /* To avoid spurious NPTxFEmp intr */
+ dwc_reg_modify(glbl_regs, DWC_GINTMSK, intr_mask,
+ intr_mask);
+ }
+ }
+}
+
+/**
+ * This function handles EP0 Control transfers.
+ *
+ * The state of the control tranfers are tracked in ep0state
+ */
+static void handle_ep0(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct pcd_ep *ep0 = &pcd->ep0;
+
+ switch (pcd->ep0state) {
+ case EP0_DISCONNECT:
+ break;
+ case EP0_IDLE:
+ pcd->request_config = 0;
+ pcd_setup(pcd);
+ break;
+ case EP0_IN_DATA_PHASE:
+ if (core_if->dma_enable)
+ /*
+ * For EP0 we can only program 1 packet at a time so we
+ * need to do the calculations after each complete.
+ * Call write_packet to make the calculations, as in
+ * slave mode, and use those values to determine if we
+ * can complete.
+ */
+ dwc_otg_ep_write_packet(core_if, &ep0->dwc_ep, 1);
+ else
+ dwc_otg_ep_write_packet(core_if, &ep0->dwc_ep, 0);
+
+ if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len)
+ dwc_otg_ep0_continue_transfer(core_if, &ep0->dwc_ep);
+ else
+ ep0_complete_request(ep0);
+ break;
+ case EP0_OUT_DATA_PHASE:
+ ep0_complete_request(ep0);
+ break;
+ case EP0_STATUS:
+ ep0_complete_request(ep0);
+ pcd->ep0state = EP0_IDLE;
+ ep0->stopped = 1;
+ ep0->dwc_ep.is_in = 0; /* OUT for next SETUP */
+
+ /* Prepare for more SETUP Packets */
+ if (core_if->dma_enable) {
+ ep0_out_start(core_if, pcd);
+ } else {
+ int i;
+ u32 diepctl = 0;
+
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0);
+ if (pcd->ep0.queue_sof) {
+ pcd->ep0.queue_sof = 0;
+ dwc_start_next_request(&pcd->ep0);
+ }
+
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0);
+ if (pcd->ep0.queue_sof) {
+ pcd->ep0.queue_sof = 0;
+ dwc_start_next_request(&pcd->ep0);
+ }
+
+ for (i = 0; i < core_if->dev_if->num_in_eps; i++) {
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, i),
+ 0);
+
+ if (pcd->in_ep[i].queue_sof) {
+ pcd->in_ep[i].queue_sof = 0;
+ dwc_start_next_request(&pcd->in_ep[i]);
+ }
+ }
+ }
+ break;
+ case EP0_STALL:
+ pr_err("EP0 STALLed, should not get here handle_ep0()\n");
+ break;
+ }
+}
+
+/**
+ * Restart transfer
+ */
+static void restart_transfer(struct dwc_pcd *pcd, const u32 ep_num)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ u32 dieptsiz = 0;
+ struct pcd_ep *ep;
+
+ dieptsiz = dwc_reg_read(dev_if->in_ep_regs[ep_num], DWC_DIEPTSIZ);
+ ep = get_in_ep(pcd, ep_num);
+
+ /*
+ * If pktcnt is not 0, and xfersize is 0, and there is a buffer,
+ * resend the last packet.
+ */
+ if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) &&
+ !DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz) && ep->dwc_ep.start_xfer_buff) {
+ if (ep->dwc_ep.xfer_len <= ep->dwc_ep.maxpacket) {
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff;
+ } else {
+ ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket;
+
+ /* convert packet size to dwords. */
+ ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket;
+ }
+ ep->stopped = 0;
+
+ if (!ep_num)
+ dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep);
+ else
+ dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep);
+ }
+}
+
+/**
+ * Handle the IN EP Transfer Complete interrupt.
+ *
+ * If dedicated fifos are enabled, then the Tx FIFO empty interrupt for the EP
+ * is disabled. Otherwise the NP Tx FIFO empty interrupt is disabled.
+ */
+static void handle_in_ep_xfr_complete_intr(struct dwc_pcd *pcd,
+ struct pcd_ep *ep, u32 num)
+{
+ struct core_if *c_if = GET_CORE_IF(pcd);
+ struct device_if *d_if = c_if->dev_if;
+ struct dwc_ep *dwc_ep = &ep->dwc_ep;
+ u32 diepint = 0;
+
+ if (c_if->en_multiple_tx_fifo) {
+ u32 fifoemptymsk = 0x1 << dwc_ep->num;
+ dwc_reg_modify(d_if->dev_global_regs,
+ DWC_DTKNQR4FIFOEMPTYMSK, fifoemptymsk, 0);
+ } else {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify((c_if->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+ }
+
+ /* Clear the interrupt, then complete the transfer */
+ diepint = DWC_DIEPINT_TX_CMPL_RW(diepint, 1);
+ dwc_reg_write(d_if->in_ep_regs[num], DWC_DIEPINT, diepint);
+
+ if (!num)
+ handle_ep0(pcd);
+ else
+ complete_ep(ep);
+}
+
+/**
+ * Handle the IN EP disable interrupt.
+ */
+static void handle_in_ep_disable_intr(struct dwc_pcd *pcd, const u32 ep_num)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ u32 dieptsiz = 0;
+ u32 dctl = 0;
+ struct pcd_ep *ep;
+ struct dwc_ep *dwc_ep;
+ u32 diepint = 0;
+
+ ep = get_in_ep(pcd, ep_num);
+ dwc_ep = &ep->dwc_ep;
+
+ dieptsiz = dwc_reg_read(dev_if->in_ep_regs[ep_num], DWC_DIEPTSIZ);
+
+ if (ep->stopped) {
+ /* Flush the Tx FIFO */
+ dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num);
+
+ /* Clear the Global IN NP NAK */
+ dctl = 0;
+ dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1);
+ dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, 0);
+
+ if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) ||
+ DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz))
+ restart_transfer(pcd, ep_num);
+ } else {
+ if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) ||
+ DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz))
+ restart_transfer(pcd, ep_num);
+ }
+ /* Clear epdisabled */
+ diepint = DWC_DIEPINT_EP_DISA_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, ep_num), 0, diepint);
+
+}
+
+/**
+ * Handler for the IN EP timeout handshake interrupt.
+ */
+static void handle_in_ep_timeout_intr(struct dwc_pcd *pcd, const u32 ep_num)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct pcd_ep *ep;
+ u32 dctl = 0;
+ u32 intr_mask = 0;
+ u32 diepint = 0;
+
+ ep = get_in_ep(pcd, ep_num);
+
+ /* Disable the NP Tx Fifo Empty Interrrupt */
+ if (!core_if->dma_enable) {
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+ }
+
+ /* Non-periodic EP */
+ /* Enable the Global IN NAK Effective Interrupt */
+ intr_mask |= DWC_INTMSK_GLBL_IN_NAK;
+ dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK, 0,
+ intr_mask);
+
+ /* Set Global IN NAK */
+ dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1);
+ dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, dctl);
+ ep->stopped = 1;
+
+ /* Clear timeout */
+ diepint = DWC_DIEPINT_TOUT_COND_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, ep_num), 0, diepint);
+}
+
+/**
+ * Handles the IN Token received with TxF Empty interrupt.
+ *
+ * For the 405EZ, only start the next transfer, when currently no other transfer
+ * is active on this endpoint.
+ *
+ * Note that the bits in the Device IN endpoint mask register are laid out
+ * exactly the same as the Device IN endpoint interrupt register.
+ */
+static void handle_in_ep_tx_fifo_empty_intr(struct dwc_pcd *pcd,
+ struct pcd_ep *ep, u32 num)
+{
+ u32 diepint = 0;
+
+ if (!ep->stopped && num) {
+ u32 diepmsk = 0;
+
+ diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1);
+ dwc_reg_modify(dev_diepmsk_reg(pcd), 0, diepmsk, 0);
+
+ if (dwc_has_feature(GET_CORE_IF(pcd), DWC_LIMITED_XFER)) {
+ if (!ep->dwc_ep.active)
+ dwc_start_next_request(ep);
+ } else {
+ dwc_start_next_request(ep);
+ }
+ }
+ /* Clear intktxfemp */
+ diepint = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, num), 0, diepint);
+}
+
+static void handle_in_ep_nak_effective_intr(struct dwc_pcd *pcd,
+ struct pcd_ep *ep, u32 num)
+{
+ u32 diepctl = 0;
+ u32 diepint = 0;
+
+ /* Periodic EP */
+ if (ep->disabling) {
+ diepctl = 0;
+ diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1);
+ diepctl = DWC_DEPCTL_DPID_RW(diepctl, 1);
+ dwc_reg_modify(in_ep_ctl_reg(pcd, num), 0, diepctl, diepctl);
+ }
+ /* Clear inepnakeff */
+ diepint = DWC_DIEPINT_IN_EP_NAK_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, num), 0, diepint);
+
+}
+
+/**
+ * This function returns the Device IN EP Interrupt register
+ */
+static inline u32 dwc_otg_read_diep_intr(struct core_if *core_if,
+ struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 v, msk, emp;
+
+ msk = dwc_reg_read(dev_if->dev_global_regs, DWC_DIEPMSK);
+ emp =
+ dwc_reg_read(dev_if->dev_global_regs, DWC_DTKNQR4FIFOEMPTYMSK);
+ msk |= ((emp >> ep->num) & 0x1) << 7;
+ v = dwc_reg_read(dev_if->in_ep_regs[ep->num], DWC_DIEPINT) & msk;
+ return v;
+}
+
+/**
+ * This function reads the Device All Endpoints Interrupt register and
+ * returns the IN endpoint interrupt bits.
+ */
+static inline u32 dwc_otg_read_dev_all_in_ep_intr(struct core_if *_if)
+{
+ u32 v;
+
+ v = dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINT) &
+ dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINTMSK);
+ return v & 0xffff;
+}
+
+/**
+ * This interrupt indicates that an IN EP has a pending Interrupt.
+ * The sequence for handling the IN EP interrupt is shown below:
+ *
+ * - Read the Device All Endpoint Interrupt register
+ * - Repeat the following for each IN EP interrupt bit set (from LSB to MSB).
+ *
+ * - Read the Device Endpoint Interrupt (DIEPINTn) register
+ * - If "Transfer Complete" call the request complete function
+ * - If "Endpoint Disabled" complete the EP disable procedure.
+ * - If "AHB Error Interrupt" log error
+ * - If "Time-out Handshake" log error
+ * - If "IN Token Received when TxFIFO Empty" write packet to Tx FIFO.
+ * - If "IN Token EP Mismatch" (disable, this is handled by EP Mismatch
+ * Interrupt)
+ */
+static int dwc_otg_pcd_handle_in_ep_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 diepint = 0;
+ u32 ep_intr;
+ u32 epnum = 0;
+ struct pcd_ep *ep;
+ struct dwc_ep *dwc_ep;
+
+ /* Read in the device interrupt bits */
+ ep_intr = dwc_otg_read_dev_all_in_ep_intr(core_if);
+
+ /* Service the Device IN interrupts for each endpoint */
+ while (ep_intr) {
+ if (ep_intr & 0x1) {
+ u32 c_diepint;
+
+ /* Get EP pointer */
+ ep = get_in_ep(pcd, epnum);
+ dwc_ep = &ep->dwc_ep;
+
+ diepint = dwc_otg_read_diep_intr(core_if, dwc_ep);
+
+ /* Transfer complete */
+ if (DWC_DIEPINT_TX_CMPL_RD(diepint))
+ handle_in_ep_xfr_complete_intr(pcd, ep, epnum);
+
+ /* Endpoint disable */
+ if (DWC_DIEPINT_EP_DISA_RD(diepint))
+ handle_in_ep_disable_intr(pcd, epnum);
+
+ /* AHB Error */
+ if (DWC_DIEPINT_AHB_ERROR_RD(diepint)) {
+ /* Clear ahberr */
+ c_diepint = 0;
+ c_diepint =
+ DWC_DIEPINT_AHB_ERROR_RW(c_diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, epnum), 0,
+ c_diepint);
+ }
+
+ /* TimeOUT Handshake (non-ISOC IN EPs) */
+ if (DWC_DIEPINT_TOUT_COND_RD(diepint))
+ handle_in_ep_timeout_intr(pcd, epnum);
+
+ /* IN Token received with TxF Empty */
+ if (DWC_DIEPINT_IN_TKN_TX_EMPTY_RD(diepint))
+ handle_in_ep_tx_fifo_empty_intr(pcd, ep, epnum);
+
+ /* IN Token Received with EP mismatch */
+ if (DWC_DIEPINT_IN_TKN_EP_MISS_RD(diepint)) {
+ /* Clear intknepmis */
+ c_diepint = 0;
+ c_diepint =
+ DWC_DIEPINT_IN_TKN_EP_MISS_RW(c_diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, epnum), 0,
+ c_diepint);
+ }
+
+ /* IN Endpoint NAK Effective */
+ if (DWC_DIEPINT_IN_EP_NAK_RD(diepint))
+ handle_in_ep_nak_effective_intr(pcd, ep, epnum);
+
+ /* IN EP Tx FIFO Empty Intr */
+ if (DWC_DIEPINT_TXFIFO_EMPTY_RD(diepint))
+ write_empty_tx_fifo(pcd, epnum);
+ }
+ epnum++;
+ ep_intr >>= 1;
+ }
+ return 1;
+}
+
+/**
+ * This function reads the Device All Endpoints Interrupt register and
+ * returns the OUT endpoint interrupt bits.
+ */
+static inline u32 dwc_otg_read_dev_all_out_ep_intr(struct core_if *_if)
+{
+ u32 v;
+
+ v = dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINT) &
+ dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINTMSK);
+ return (v & 0xffff0000) >> 16;
+}
+
+/**
+ * This function returns the Device OUT EP Interrupt register
+ */
+static inline u32 dwc_otg_read_doep_intr(struct core_if *core_if,
+ struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 v;
+
+ v = dwc_reg_read(dev_if->out_ep_regs[ep->num], DWC_DOEPINT) &
+ dwc_reg_read(dev_if->dev_global_regs, DWC_DOEPMSK);
+ return v;
+}
+
+/**
+ * This interrupt indicates that an OUT EP has a pending Interrupt.
+ * The sequence for handling the OUT EP interrupt is shown below:
+ *
+ * - Read the Device All Endpoint Interrupt register.
+ * - Repeat the following for each OUT EP interrupt bit set (from LSB to MSB).
+ *
+ * - Read the Device Endpoint Interrupt (DOEPINTn) register
+ * - If "Transfer Complete" call the request complete function
+ * - If "Endpoint Disabled" complete the EP disable procedure.
+ * - If "AHB Error Interrupt" log error
+ * - If "Setup Phase Done" process Setup Packet (See Standard USB Command
+ * Processing)
+ */
+static int dwc_otg_pcd_handle_out_ep_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 ep_intr;
+ u32 doepint = 0;
+ u32 epnum = 0;
+ struct dwc_ep *dwc_ep;
+
+ /* Read in the device interrupt bits */
+ ep_intr = dwc_otg_read_dev_all_out_ep_intr(core_if);
+ while (ep_intr) {
+ if (ep_intr & 0x1) {
+ u32 c_doepint = 0;
+
+ dwc_ep = &((get_out_ep(pcd, epnum))->dwc_ep);
+ doepint = dwc_otg_read_doep_intr(core_if, dwc_ep);
+
+ /* Transfer complete */
+ if (DWC_DOEPINT_TX_COMPL_RD(doepint)) {
+ /* Clear xfercompl */
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_TX_COMPL_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ if (epnum == 0)
+ handle_ep0(pcd);
+ else
+ complete_ep(get_out_ep(pcd, epnum));
+ }
+
+ /* Endpoint disable */
+ if (DWC_DOEPINT_EP_DISA_RD(doepint)) {
+ /* Clear epdisabled */
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_EP_DISA_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ }
+
+ /* AHB Error */
+ if (DWC_DOEPINT_AHB_ERROR_RD(doepint)) {
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_AHB_ERROR_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ }
+
+ /* Setup Phase Done (control EPs) */
+ if (DWC_DOEPINT_SETUP_DONE_RD(doepint)) {
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_SETUP_DONE_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ handle_ep0(pcd);
+ }
+ }
+ epnum++;
+ ep_intr >>= 1;
+ }
+ return 1;
+}
+
+/**
+ * Incomplete ISO IN Transfer Interrupt. This interrupt indicates one of the
+ * following conditions occurred while transmitting an ISOC transaction.
+ *
+ * - Corrupted IN Token for ISOC EP.
+ * - Packet not complete in FIFO.
+ *
+ * The follow actions should be taken:
+ * - Determine the EP
+ * - Set incomplete flag in dwc_ep structure
+ * - Disable EP. When "Endpoint Disabled" interrupt is received Flush FIFO
+ */
+static int dwc_otg_pcd_handle_incomplete_isoc_in_intr(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts = 0;
+
+ pr_info("Interrupt handler not implemented for IN ISOC "
+ "Incomplete\n");
+
+ /* Turn off and clear the interrupt */
+ intr_mask |= DWC_INTMSK_INCMP_IN_ATX;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts |= DWC_INTSTS_INCMP_IN_ATX;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+ return 1;
+}
+
+/**
+ * Incomplete ISO OUT Transfer Interrupt. This interrupt indicates that the
+ * core has dropped an ISO OUT packet. The following conditions can be the
+ * cause:
+ *
+ * - FIFO Full, the entire packet would not fit in the FIFO.
+ * - CRC Error
+ * - Corrupted Token
+ *
+ * The follow actions should be taken:
+ * - Determine the EP
+ * - Set incomplete flag in dwc_ep structure
+ * - Read any data from the FIFO
+ * - Disable EP. When "Endpoint Disabled" interrupt is received re-enable EP.
+ */
+static int dwc_otg_pcd_handle_incomplete_isoc_out_intr(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts = 0;
+
+ pr_info("Interrupt handler not implemented for OUT ISOC "
+ "Incomplete\n");
+
+ /* Turn off and clear the interrupt */
+ intr_mask |= DWC_INTMSK_INCMP_OUT_PTX;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ gintsts |= DWC_INTSTS_INCMP_OUT_PTX;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+ return 1;
+}
+
+/**
+ * This function handles the Global IN NAK Effective interrupt.
+ */
+static int dwc_otg_pcd_handle_in_nak_effective(struct dwc_pcd *pcd)
+{
+ struct device_if *dev_if = GET_CORE_IF(pcd)->dev_if;
+ u32 diepctl = 0;
+ u32 diepctl_rd = 0;
+ u32 intr_mask = 0;
+ u32 gintsts = 0;
+ u32 i;
+
+ /* Disable all active IN EPs */
+ diepctl = DWC_DEPCTL_DPID_RW(diepctl, 1);
+ diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1);
+ for (i = 0; i <= dev_if->num_in_eps; i++) {
+ diepctl_rd = dwc_reg_read(in_ep_ctl_reg(pcd, i), 0);
+ if (DWC_DEPCTL_EPENA_RD(diepctl_rd))
+ dwc_reg_write(in_ep_ctl_reg(pcd, i), 0, diepctl);
+ }
+
+ /* Disable the Global IN NAK Effective Interrupt */
+ intr_mask |= DWC_INTMSK_GLBL_IN_NAK;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ /* Clear interrupt */
+ gintsts |= DWC_INTSTS_GLBL_IN_NAK;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+ return 1;
+}
+
+/**
+ * This function handles the Global OUT NAK Effective interrupt.
+ */
+static int dwc_otg_pcd_handle_out_nak_effective(struct dwc_pcd *pcd)
+{
+ u32 intr_mask = 0;
+ u32 gintsts = 0;
+
+ pr_info("Interrupt handler not implemented for Global IN "
+ "NAK Effective\n");
+
+ /* Turn off and clear the interrupt */
+ intr_mask |= DWC_INTMSK_GLBL_OUT_NAK;
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+
+ /* Clear goutnakeff */
+ gintsts |= DWC_INTSTS_GLBL_OUT_NAK;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+ return 1;
+}
+
+/**
+ * PCD interrupt handler.
+ *
+ * The PCD handles the device interrupts. Many conditions can cause a
+ * device interrupt. When an interrupt occurs, the device interrupt
+ * service routine determines the cause of the interrupt and
+ * dispatches handling to the appropriate function. These interrupt
+ * handling functions are described below.
+ *
+ * All interrupt registers are processed from LSB to MSB.
+ *
+ */
+int dwc_otg_pcd_handle_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 gintr_status;
+ int ret = 0;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ spin_lock(&pcd->lock);
+
+ gintr_status = dwc_otg_read_core_intr(core_if);
+ if (!gintr_status) {
+ spin_unlock(&pcd->lock);
+ return 0;
+ }
+
+ if (gintr_status & DWC_INTSTS_STRT_OF_FRM)
+ ret |= dwc_otg_pcd_handle_sof_intr(pcd);
+ if (gintr_status & DWC_INTSTS_RXFIFO_NOT_EMPT)
+ ret |= dwc_otg_pcd_handle_rx_status_q_level_intr(pcd);
+ if (gintr_status & DWC_INTSTS_NP_TXFIFO_EMPT)
+ ret |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr(pcd);
+ if (gintr_status & DWC_INTSTS_GLBL_IN_NAK)
+ ret |= dwc_otg_pcd_handle_in_nak_effective(pcd);
+ if (gintr_status & DWC_INTSTS_GLBL_OUT_NAK)
+ ret |= dwc_otg_pcd_handle_out_nak_effective(pcd);
+ if (gintr_status & DWC_INTSTS_I2C_INTR)
+ ret |= dwc_otg_pcd_handle_i2c_intr(pcd);
+ if (gintr_status & DWC_INTSTS_EARLY_SUSP)
+ ret |= dwc_otg_pcd_handle_early_suspend_intr(pcd);
+ if (gintr_status & DWC_INTSTS_USB_RST)
+ ret |= dwc_otg_pcd_handle_usb_reset_intr(pcd);
+ if (gintr_status & DWC_INTSTS_ENUM_DONE)
+ ret |= dwc_otg_pcd_handle_enum_done_intr(pcd);
+ if (gintr_status & DWC_INTSTS_ISYNC_OUTPKT_DRP)
+ ret |=
+ dwc_otg_pcd_handle_isoc_out_packet_dropped_intr
+ (pcd);
+ if (gintr_status & DWC_INTSTS_END_OF_PFRM)
+ ret |= dwc_otg_pcd_handle_end_periodic_frame_intr(pcd);
+ if (gintr_status & DWC_INTSTS_ENDP_MIS_MTCH)
+ ret |= dwc_otg_pcd_handle_ep_mismatch_intr(core_if);
+ if (gintr_status & DWC_INTSTS_IN_ENDP)
+ ret |= dwc_otg_pcd_handle_in_ep_intr(pcd);
+ if (gintr_status & DWC_INTSTS_OUT_ENDP)
+ ret |= dwc_otg_pcd_handle_out_ep_intr(pcd);
+ if (gintr_status & DWC_INTSTS_INCMP_IN_ATX)
+ ret |= dwc_otg_pcd_handle_incomplete_isoc_in_intr(pcd);
+ if (gintr_status & DWC_INTSTS_INCMP_OUT_PTX)
+ ret |= dwc_otg_pcd_handle_incomplete_isoc_out_intr(pcd);
+
+ spin_unlock(&pcd->lock);
+ }
+ return ret;
+}
diff --git a/drivers/usb/dwc/regs.h b/drivers/usb/dwc/regs.h
new file mode 100644
index 0000000..d1748f1
--- /dev/null
+++ b/drivers/usb/dwc/regs.h
@@ -0,0 +1,1326 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * 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 version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ *
+ * Revamped register difinitions by Tirumala R Marri(tmarri@apm.com)
+ *
+ * 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 SYNOPSYS, INC. 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.
+ *
+ */
+
+#ifndef __DWC_OTG_REGS_H__
+#define __DWC_OTG_REGS_H__
+
+#include <linux/types.h>
+/*Bit fields in the Device EP Transfer Size Register is 11 bits */
+#undef DWC_LIMITED_XFER_SIZE
+/*
+ * This file contains the Macro defintions for accessing the DWC_otg core
+ * registers.
+ *
+ * The application interfaces with the HS OTG core by reading from and
+ * writing to the Control and Status Register (CSR) space through the
+ * AHB Slave interface. These registers are 32 bits wide, and the
+ * addresses are 32-bit-block aligned.
+ * CSRs are classified as follows:
+ * - Core Global Registers
+ * - Device Mode Registers
+ * - Device Global Registers
+ * - Device Endpoint Specific Registers
+ * - Host Mode Registers
+ * - Host Global Registers
+ * - Host Port CSRs
+ * - Host Channel Specific Registers
+ *
+ * Only the Core Global registers can be accessed in both Device and
+ * Host modes. When the HS OTG core is operating in one mode, either
+ * Device or Host, the application must not access registers from the
+ * other mode. When the core switches from one mode to another, the
+ * registers in the new mode of operation must be reprogrammed as they
+ * would be after a power-on reset.
+ */
+
+/*
+ * DWC_otg Core registers. The core_global_regs structure defines the
+ * size and relative field offsets for the Core Global registers.
+ */
+#define DWC_GOTGCTL 0x000
+#define DWC_GOTGINT 0x004
+#define DWC_GAHBCFG 0x008
+#define DWC_GUSBCFG 0x00C
+#define DWC_GRSTCTL 0x010
+#define DWC_GINTSTS 0x014
+#define DWC_GINTMSK 0x018
+#define DWC_GRXSTSR 0x01C
+#define DWC_GRXSTSP 0x020
+#define DWC_GRXFSIZ 0x024
+#define DWC_GNPTXFSIZ 0x028
+#define DWC_GNPTXSTS 0x02C
+#define DWC_GI2CCTL 0x030
+#define DWC_VDCTL 0x034
+#define DWC_GGPIO 0x038
+#define DWC_GUID 0x03C
+#define DWC_GSNPSID 0x040
+#define DWC_GHWCFG1 0x044
+#define DWC_GHWCFG2 0x048
+#define DWC_GHWCFG3 0x04c
+#define DWC_GHWCFG4 0x050
+#define DWC_HPTXFSIZ 0x100
+#define DWC_DPTX_FSIZ_DIPTXF(x) (0x104 + x * 4) /* 15 <= x > 1 */
+
+#define DWC_GLBINTRMASK 0x0001
+#define DWC_DMAENABLE 0x0020
+#define DWC_NPTXEMPTYLVL_EMPTY 0x0080
+#define DWC_NPTXEMPTYLVL_HALFEMPTY 0x0000
+#define DWC_PTXEMPTYLVL_EMPTY 0x0100
+#define DWC_PTXEMPTYLVL_HALFEMPTY 0x0000
+
+#define DWC_SLAVE_ONLY_ARCH 0
+#define DWC_EXT_DMA_ARCH 1
+#define DWC_INT_DMA_ARCH 2
+
+#define DWC_MODE_HNP_SRP_CAPABLE 0
+#define DWC_MODE_SRP_ONLY_CAPABLE 1
+#define DWC_MODE_NO_HNP_SRP_CAPABLE 2
+#define DWC_MODE_SRP_CAPABLE_DEVICE 3
+#define DWC_MODE_NO_SRP_CAPABLE_DEVICE 4
+#define DWC_MODE_SRP_CAPABLE_HOST 5
+#define DWC_MODE_NO_SRP_CAPABLE_HOST 6
+
+/*
+ * These Macros represents the bit fields of the Core OTG Controland Status
+ * Register (GOTGCTL). Set the bits using the bit fields then write the u32
+ * value to the register.
+ */
+#define DWC_GCTL_BSESSION_VALID (1 << 19)
+#define DWC_GCTL_CSESSION_VALID (1 << 18)
+#define DWC_GCTL_DEBOUNCE (1 << 17)
+#define DWC_GCTL_CONN_ID_STATUS (1 << 16)
+#define DWC_GCTL_DEV_HNP_ENA (1 << 11)
+#define DWC_GCTL_HOST_HNP_ENA (1 << 10)
+#define DWC_GCTL_HNP_REQ (1 << 9)
+#define DWC_GCTL_HOST_NEG_SUCCES (1 << 8)
+#define DWC_GCTL_SES_REQ (1 << 1)
+#define DWC_GCTL_SES_REQ_SUCCESS (1 << 0)
+
+#define DWC_GCTL_BSESSION_VALID_RD(reg) (((reg) & (0x001 << 19)) >> 19)
+#define DWC_GCTL_CSESSION_VALID_RD(reg) (((reg) & (0x001 << 18)) >> 18)
+#define DWC_GCTL_DEBOUNCE_RD(reg) (((reg) & (0x001 << 17)) >> 17)
+#define DWC_GCTL_CONN_ID_STATUS_RD(reg) (((reg) & (0x001 << 16)) >> 16)
+#define DWC_GCTL_DEV_HNP_ENA_RD(reg) (((reg) & (0x001 << 11)) >> 11)
+#define DWC_GCTL_HOST_HNP_ENA_RD(reg) (((reg) & (0x001 << 10)) >> 10)
+#define DWC_GCTL_HNP_REQ_RD(reg) (((reg) & (0x001 << 9)) >> 9)
+#define DWC_GCTL_HOST_NEG_SUCCES_RD(reg) (((reg) & (0x001 << 8)) >> 8)
+#define DWC_GCTL_SES_REQ_RD(reg) (((reg) & (0x001 << 1)) >> 1)
+#define DWC_GCTL_SES_REQ_SUCCESS_RD(reg) (((reg) & (0x001 << 0)) >> 0)
+
+#define DWC_GCTL_BSESSION_VALID_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 19))) | ((x) << 19))
+#define DWC_GCTL_CSESSION_VALID_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 18))) | ((x) << 18))
+#define DWC_GCTL_DEBOUNCE_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 17))) | ((x) << 17))
+#define DWC_GCTL_CONN_ID_STATUS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 16))) | ((x) << 16))
+#define DWC_GCTL_DEV_HNP_ENA_RW (reg, x) \
+ (((reg) & (~((u32)0x01 << 11))) | ((x) << 11))
+#define DWC_GCTL_HOST_HNP_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 10))) | ((x) << 10))
+#define DWC_GCTL_HNP_REQ_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 9))) | ((x) << 9))
+#define DWC_GCTL_HOST_NEG_SUCCES_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_GCTL_SES_REQ_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_GCTL_SES_REQ_SUCCESS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+/*
+ * These Macros represents the bit fields of the Core OTG Interrupt Register
+ * (GOTGINT). Set/clear the bits using the bit fields then write the u32
+ * value to the register.
+ */
+#define DWC_GINT_DEBDONE (1 << 19)
+#define DWC_GINT_DEVTOUT (1 << 18)
+#define DWC_GINT_HST_NEGDET (1 << 17)
+#define DWC_GINT_HST_NEGSUC (1 << 9)
+#define DWC_GINT_SES_REQSUC (1 << 8)
+#define DWC_GINT_SES_ENDDET (1 << 2)
+
+/*
+ * These Macros represents the bit fields of the Core AHB Configuration Register
+ * (GAHBCFG). Set/clear the bits using the bit fields then write the u32 value
+ * to the register.
+ */
+#define DWC_AHBCFG_FIFO_EMPTY (1 << 8)
+#define DWC_AHBCFG_NPFIFO_EMPTY (1 << 7)
+#define DWC_AHBCFG_DMA_ENA (1 << 5)
+#define DWC_AHBCFG_BURST_LEN(x) (x << 1)
+#define DWC_AHBCFG_GLBL_INT_MASK (1 << 0)
+
+#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1
+#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0
+#define DWC_GAHBCFG_DMAENABLE 1
+#define DWC_GAHBCFG_INT_DMA_BURST_SINGLE 0
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR 1
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR4 3
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR8 5
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR16 7
+
+/*
+
+ * (GUSBCFG). Set the bits using the bit fields then write the u32 value to the
+ * register.
+ */
+#define DWC_USBCFG_CORR_PKT (1 << 31)
+#define DWC_USBCFG_FRC_DEV_MODE (1 << 30)
+#define DWC_USBCFG_FRC_HST_MODE (1 << 29)
+#define DWC_USBCFG_TERM_SEL_DL_PULSE (1 << 22)
+#define DWC_USBCFG_ULPI_INTVBUS_INDICATOR (1 << 21)
+#define DWC_USBCFG_ULPI_EXT_VBUS_DRV (1 << 20)
+#define DWC_USBCFG_ULPI_CLK_SUS_M (1 << 19)
+#define DWC_USBCFG_ULPI_AUTO_RES (1 << 18)
+#define DWC_USBCFG_ULPI_FSLS (1 << 17)
+#define DWC_USBCFG_OTGUTMIFSSEL (1 << 16)
+#define DWC_USBCFG_PHYLPWRCLKSEL (1 << 15)
+#define DWC_USBCFG_NPTXFRWNDEN (1 << 14)
+#define DWC_USBCFG_TRN_TIME(x) (x << 10)
+#define DWC_USBCFG_HNP_CAP (1 << 9)
+#define DWC_USBCFG_SRP_CAP (1 << 8)
+#define DWC_USBCFG_DDRSEL (1 << 7)
+#define DWC_USBCFG_USB_2_11 (1 << 6)
+#define DWC_USBCFG_FSINTF (1 << 5)
+#define DWC_USBCFG_ULPI_UTMI_SEL (1 << 4)
+#define DWC_USBCFG_PHYIF (1 << 3)
+#define DWC_USBCFG_TOUT_CAL(x) (x << 0)
+
+/*
+ * These Macros represents the bit fields of the Core Reset Register (GRSTCTL).
+ * Set/clear the bits using the bit fields then write the u32 value to the
+ * register.
+ */
+#define DWC_RSTCTL_AHB_IDLE (1 << 31)
+#define DWC_RSTCTL_DMA_REQ (1 << 30)
+#define DWC_RSTCTL_TX_FIFO_NUM(reg, x) \
+ (((reg) & (~((u32)0x1f << 6))) | ((x) << 6))
+#define DWC_RSTCTL_TX_FIFO_FLUSH (1 << 5)
+#define DWC_RSTCTL_RX_FIFO_FLUSH (1 << 4)
+#define DWC_RSTCTL_TKN_QUE_FLUSH (1 << 3)
+#define DWC_RSTCTL_HSTFRM_CNTR_RST (1 << 2)
+#define DWC_RSTCTL_HCLK_SFT_RST (1 << 1)
+#define DWC_RSTCTL_SFT_RST (1 << 1)
+#define DWC_GRSTCTL_TXFNUM_ALL 0x10
+
+/*
+ * These Macros represents the bit fields of the Core Interrupt Mask Register
+ * (GINTMSK). Set/clear the bits using the bit fields then write the u32 value
+ * to the register.
+ */
+#define DWC_INTMSK_WKP (1 << 31)
+#define DWC_INTMSK_NEW_SES_DET (1 << 30)
+#define DWC_INTMSK_SES_DISCON_DET (1 << 29)
+#define DWC_INTMSK_CON_ID_STS_CHG (1 << 28)
+#define DWC_INTMSK_P_TXFIFO_EMPTY (1 << 26)
+#define DWC_INTMSK_HST_CHAN (1 << 25)
+#define DWC_INTMSK_HST_PORT (1 << 24)
+#define DWC_INTMSK_DATA_FETCH_SUS (1 << 23)
+#define DWC_INTMSK_INCMP_PTX (1 << 22)
+#define DWC_INTMSK_INCMP_OUT_PTX (1 << 21)
+#define DWC_INTMSK_INCMP_IN_ATX (1 << 20)
+#define DWC_INTMSK_OUT_ENDP (1 << 19)
+#define DWC_INTMSK_IN_ENDP (1 << 18)
+#define DWC_INTMSK_ENDP_MIS_MTCH (1 << 17)
+#define DWC_INTMSK_END_OF_PFRM (1 << 15)
+#define DWC_INTMSK_ISYNC_OUTPKT_DRP (1 << 14)
+#define DWC_INTMSK_ENUM_DONE (1 << 13)
+#define DWC_INTMSK_USB_RST (1 << 12)
+#define DWC_INTMSK_USB_SUSP (1 << 11)
+#define DWC_INTMSK_EARLY_SUSP (1 << 10)
+#define DWC_INTMSK_I2C_INTR (1 << 9)
+#define DWC_INTMSK_GLBL_OUT_NAK (1 << 7)
+#define DWC_INTMSK_GLBL_IN_NAK (1 << 6)
+#define DWC_INTMSK_NP_TXFIFO_EMPT (1 << 5)
+#define DWC_INTMSK_RXFIFO_NOT_EMPT (1 << 4)
+#define DWC_INTMSK_STRT_OF_FRM (1 << 3)
+#define DWC_INTMSK_OTG (1 << 2)
+#define DWC_INTMSK_MODE_MISMTC (1 << 1)
+/*
+ * These Macros represents the bit fields of the Core Interrupt Register
+ * (GINTSTS). Set/clear the bits using the bit fields then write the u32 value
+ * to the register.
+ */
+#define DWC_INTSTS_WKP (1 << 31)
+#define DWC_INTSTS_NEW_SES_DET (1 << 30)
+#define DWC_INTSTS_SES_DISCON_DET (1 << 29)
+#define DWC_INTSTS_CON_ID_STS_CHG (1 << 28)
+#define DWC_INTSTS_P_TXFIFO_EMPTY (1 << 26)
+#define DWC_INTSTS_HST_CHAN (1 << 25)
+#define DWC_INTSTS_HST_PORT (1 << 24)
+#define DWC_INTSTS_DATA_FETCH_SUS (1 << 23)
+#define DWC_INTSTS_INCMP_PTX (1 << 22)
+#define DWC_INTSTS_INCMP_OUT_PTX (1 << 21)
+#define DWC_INTSTS_INCMP_IN_ATX (1 << 20)
+#define DWC_INTSTS_OUT_ENDP (1 << 19)
+#define DWC_INTSTS_IN_ENDP (1 << 18)
+#define DWC_INTSTS_ENDP_MIS_MTCH (1 << 17)
+#define DWC_INTSTS_END_OF_PFRM (1 << 15)
+#define DWC_INTSTS_ISYNC_OUTPKT_DRP (1 << 14)
+#define DWC_INTSTS_ENUM_DONE (1 << 13)
+#define DWC_INTSTS_USB_RST (1 << 12)
+#define DWC_INTSTS_USB_SUSP (1 << 11)
+#define DWC_INTSTS_EARLY_SUSP (1 << 10)
+#define DWC_INTSTS_I2C_INTR (1 << 9)
+#define DWC_INTSTS_GLBL_OUT_NAK (1 << 7)
+#define DWC_INTSTS_GLBL_IN_NAK (1 << 6)
+#define DWC_INTSTS_NP_TXFIFO_EMPT (1 << 5)
+#define DWC_INTSTS_RXFIFO_NOT_EMPT (1 << 4)
+#define DWC_INTSTS_STRT_OF_FRM (1 << 3)
+#define DWC_INTSTS_OTG (1 << 2)
+#define DWC_INTSTS_MODE_MISMTC (1 << 1)
+#define DWC_INTSTS_CURR_MODE (1 << 0)
+#define DWC_SOF_INTR_MASK 0x0008
+#define DWC_HOST_MODE 1
+
+/*
+ * These Macros represents the bit fields in the Device Receive Status Read and
+ * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the u32
+ * element then read out the bits using the bit elements.
+ */
+#define DWC_DM_RXSTS_PKT_STS (0x01f << 17)
+#define DWC_DM_RXSTS_PKT_DPID (0x003 << 15)
+#define DWC_DM_RXSTS_BYTE_CNT (0x7ff << 4)
+#define DWC_DM_RXSTS_CHAN_NUM (0x00f << 0)
+
+#define DWC_DM_RXSTS_PKT_STS_RD(reg) (((reg) & (0x00f << 17)) >> 17)
+#define DWC_DM_RXSTS_PKT_DPID_RD(reg) (((reg) & (0x003 << 15)) >> 15)
+#define DWC_DM_RXSTS_BYTE_CNT_RD(reg) (((reg) & (0x7ff << 04)) >> 04)
+#define DWC_DM_RXSTS_CHAN_NUM_RD(reg) ((reg) & 0x00f)
+
+#define DWC_STS_DATA_UPDT 0x2 /* OUT Data Packet */
+#define DWC_STS_XFER_COMP 0x3 /* OUT Data Transfer Complete */
+#define DWC_DSTS_GOUT_NAK 0x1 /* Global OUT NAK */
+#define DWC_DSTS_SETUP_COMP 0x4 /* Setup Phase Complete */
+#define DWC_DSTS_SETUP_UPDT 0x6 /* SETUP Packet */
+
+/*
+ * These Macros represents the bit fields in the Host Receive Status Read and
+ * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the u32
+ * element then read out the bits using the bit elements.
+ */
+#define DWC_HM_RXSTS_FRM_NUM (0x00f << 21)
+#define DWC_HM_RXSTS_PKT_STS (0x01f << 17)
+#define DWC_HM_RXSTS_PKT_DPID (0x003 << 15)
+#define DWC_HM_RXSTS_BYTE_CNT (0x7ff << 4)
+#define DWC_HM_RXSTS_CHAN_NUM (0x00f << 0)
+
+#define DWC_HM_RXSTS_PKT_STS_RD(reg) (((reg) & (0x00f << 17)) >> 17)
+#define DWC_HM_RXSTS_PKT_DPID_RD(reg) (((reg) & (0x003 << 15)) >> 15)
+#define DWC_HM_RXSTS_BYTE_CNT_RD(reg) (((reg) & (0x7ff << 04)) >> 04)
+#define DWC_HM_RXSTS_CHAN_NUM_RD(reg) ((reg) & 0x00f)
+
+#define DWC_GRXSTS_PKTSTS_IN 0x2
+#define DWC_GRXSTS_PKTSTS_IN_XFER_COMP 0x3
+#define DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR 0x5
+#define DWC_GRXSTS_PKTSTS_CH_HALTED 0x7
+
+/*
+ * These Macros represents the bit fields in the FIFO Size Registers (HPTXFSIZ,
+ * GNPTXFSIZ, DPTXFSIZn). Read the register into the u32 element then
+ * read out the bits using the bit elements.
+ */
+#define DWC_RX_FIFO_DEPTH_RD(reg) (((reg) & ((u32)0xffff << 16)) >> 16)
+#define DWC_RX_FIFO_DEPTH_WR(reg, x) \
+ (((reg) & (~((u32)0xffff << 16))) | ((x) << 16))
+#define DWC_RX_FIFO_START_ADDR_RD(reg) ((reg) & 0xffff)
+#define DWC_RX_FIFO_START_ADDR_WR(reg, x) \
+ (((reg) & (~((u32)0xffff))) | (x))
+
+/*
+ * These Macros represents the bit fields in the Non-Periodic Tx FIFO/Queue
+ * Status Register (GNPTXSTS). Read the register into the u32 element then read
+ * out the bits using the bit elements.
+ */
+#define DWC_GNPTXSTS_NPTXQTOP_CHNEP_RD(x) (((x) & (0x3f << 26)) >> 26)
+#define DWC_GNPTXSTS_NPTXQTOP_TKN_RD(x) (((x) & (0x03 << 24)) >> 24)
+#define DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(x) (((x) & (0xff << 16)) >> 16)
+#define DWC_GNPTXSTS_NPTXFSPCAVAIL_RD(x) (0xffff & (x))
+
+/*
+ * These Macros represents the bit fields in the Transmit FIFO Status Register
+ * (DTXFSTS). Read the register into the u32 element then read out the bits
+ * using the bit elements.
+ */
+#define DWC_DTXFSTS_TXFSSPC_AVAI_RD(x) ((x) & 0xffff)
+
+/*
+ * These Macros represents the bit fields in the I2C Control Register (I2CCTL).
+ * Read the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_I2CCTL_BSYDNE (1 << 31)
+#define DWC_I2CCTL_RW (1 << 30)
+#define DWC_I2CCTL_I2CDEVADDR(x) ((x) << 27)
+#define DWC_I2CCTL_I2CSUSCTL (1 << 25)
+#define DWC_I2CCTL_ACK (1 << 24)
+#define DWC_I2CCTL_I2CEN (1 << 23)
+#define DWC_I2CCTL_ADDR (1 << 22)
+#define DWC_I2CCTL_REGADDR(x) ((x) << 14)
+#define DWC_I2CCTL_RWDATA(x) ((x) << 6)
+
+/*
+ * These Macros represents the bit fields in the User HW Config1 Register. Read
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG1_EPDIR15(x) ((x) << 30)
+#define DWC_HWCFG1_EPDIR14(x) ((x) << 28)
+#define DWC_HWCFG1_EPDIR13(x) ((x) << 26)
+#define DWC_HWCFG1_EPDIR12(x) ((x) << 24)
+#define DWC_HWCFG1_EPDIR11(x) ((x) << 22)
+#define DWC_HWCFG1_EPDIR10(x) ((x) << 20)
+#define DWC_HWCFG1_EPDIR9(x) ((x) << 18)
+#define DWC_HWCFG1_EPDIR8(x) ((x) << 16)
+#define DWC_HWCFG1_EPDIR7(x) ((x) << 14)
+#define DWC_HWCFG1_EPDIR6(x) ((x) << 13)
+#define DWC_HWCFG1_EPDIR5(x) ((x) << 10)
+#define DWC_HWCFG1_EPDIR4(x) ((x) << 8)
+#define DWC_HWCFG1_EPDIR3(x) ((x) << 6)
+#define DWC_HWCFG1_EPDIR2(x) ((x) << 4)
+#define DWC_HWCFG1_EPDIR1(x) ((x) << 2)
+#define DWC_HWCFG1_EPDIR0(x) ((x) << 0)
+
+/*
+ * These Macros represents the bit fields in the User HW Config2 Register. Read
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG2_DEV_TKN_Q_DEPTH_RD(x) (((x) & (0x1F << 26)) >> 26)
+#define DWC_HWCFG2_HOST_PERIO_Q_DEPTH_RD(x) (((x) & (0x3 << 24)) >> 24)
+#define DWC_HWCFG2_NP_TX_Q_DEPTH_RD(x) (((x) & (0x3 << 22)) >> 22)
+#define DWC_HWCFG2_RX_STS_Q_DEPTH_RD(x) (((x) & (0x3 << 20)) >> 20)
+#define DWC_HWCFG2_DYN_FIFO_RD(x) (((x) & (0x1 << 19)) >> 19)
+#define DWC_HWCFG2_PERIO_EP_SUPP_RD(x) (((x) & (0x1 << 18)) >> 18)
+#define DWC_HWCFG2_NO_HST_CHAN_RD(x) (((x) & (0xf << 14)) >> 14)
+#define DWC_HWCFG2_NO_DEV_EP_RD(x) (((x) & (0xf << 10)) >> 10)
+#define DWC_HWCFG2_FS_PHY_TYPE_RD(x) (((x) & (0x3 << 8)) >> 8)
+#define DWC_HWCFG2_HS_PHY_TYPE_RD(x) (((x) & (0x3 << 6)) >> 6)
+#define DWC_HWCFG2_P_2_P_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HWCFG2_ARCH_RD(x) (((x) & (0x3 << 3)) >> 3)
+#define DWC_HWCFG2_OP_MODE_RD(x) ((x) & 0x7)
+
+#define DWC_HWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0
+#define DWC_HWCFG2_HS_PHY_TYPE_UTMI 1
+#define DWC_HWCFG2_HS_PHY_TYPE_ULPI 2
+#define DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI 3
+#define DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG 0
+#define DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG 1
+#define DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG 2
+#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3
+#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4
+#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST 5
+#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6
+
+/*
+ * These Macros represents the bit fields in the User HW Config3 Register. ead
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG3_DFIFO_DEPTH_RD(x) (((x) & (0xffff << 16)) >> 16)
+#define DWC_HWCFG3_AHB_PHY_CLK_SYNC_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HWCFG3_SYNC_RST_TYPE_RD(x) (((x) & (0x1 << 11)) >> 11)
+#define DWC_HWCFG3_OPT_FEATURES_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HWCFG3_VEND_CTRL_IF_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HWCFG3_I2C_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HWCFG3_OTG_FUNC_RD(x) (((x) & (0x1 << 07)) >> 07)
+#define DWC_HWCFG3_PKTSIZE_CTR_WIDTH_RD(x) (((x) & (0x7 << 04)) >> 04)
+#define DWC_HWCFG3_XFERSIZE_CTR_WIDTH_RD(x) ((x) & 0xf)
+
+/*
+ * These Macros represents the bit fields in the User HW Config4 Register. Read
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG4_NUM_IN_EPS_RD(x) (((x) & (0xF << 26)) >> 26)
+#define DWC_HWCFG4_DED_FIFO_ENA_RD(x) (((x) & (0x1 << 25)) >> 25)
+#define DWC_HWCFG4_SES_END_FILT_EN_RD(x) (((x) & (0x1 << 24)) >> 24)
+#define DWC_HWCFG4_BVALID_FILT_EN_RD(x) (((x) & (0x1 << 23)) >> 23)
+#define DWC_HWCFG4_AVALID_FILT_EN_RD(x) (((x) & (0x1 << 22)) >> 22)
+#define DWC_HWCFG4_VBUS_VALID_FILT_EN_RD(x) (((x) & (0x1 << 21)) >> 21)
+#define DWC_HWCFG4_IDDIG_FILT_EN_RD(x) (((x) & (0x1 << 20)) >> 20)
+#define DWC_HWCFG4_NUM_DEV_MODE_CTRL_EP_RD(x) (((x) & (0xF << 16)) >> 16)
+#define DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD(x) (((x) & (0x3 << 14)) >> 14)
+#define DWC_HWCFG4_MIN_AHB_FREQ_RD(x) (((x) & (0x1 << 05)) >> 05)
+#define DWC_HWCFG4_POWER_OPT_RD(x) (((x) & (0x1 << 04)) >> 04)
+#define DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(x) ((x) & 0xf)
+
+/*
+ * Device Global Registers. Offsets 800h-BFFh
+ *
+ * The following structures define the size and relative field offsets for the
+ * Device Mode Registers.
+ *
+ * These registers are visible only in Device mode and must not be accessed in
+ * Host mode, as the results are unknown.
+ */
+#define DWC_DCFG 0x000
+#define DWC_DCTL 0x004
+#define DWC_DSTS 0x008
+#define DWC_DIEPMSK 0x010
+#define DWC_DOEPMSK 0x014
+#define DWC_DAINT 0x018
+#define DWC_DAINTMSK 0x01C
+#define DWC_DTKNQR1 0x020
+#define DWC_DTKNQR2 0x024
+#define DWC_DVBUSDIS 0x028
+#define DWC_DVBUSPULSE 0x02C
+#define DWC_DTKNQR3_DTHRCTL 0x030
+#define DWC_DTKNQR4FIFOEMPTYMSK 0x034
+
+/*
+ * These Macros represents the bit fields in the Device Configuration
+ * Register. Read the register into the u32 member then
+ * set/clear the bits using the bit elements. Write the
+ * u32 member to the dcfg register.
+*/
+#define DWC_DCFG_IN_EP_MISMATCH_CNT_RD(x) (((x) & (0x1f << 18)) >> 18)
+#define DWC_DCFG_P_FRM_INTRVL_RD(x) (((x) & (0x03 << 11)) >> 11)
+#define DWC_DCFG_DEV_ADDR_RD(x) (((x) & (0x3f << 04)) >> 04)
+#define DWC_DCFG_NGL_STS_OUT_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_DCFG_DEV_SPEED_RD(x) ((x) & 0x3)
+
+#define DWC_DCFG_IN_EP_MISMATCH_CNT_WR(reg, x) \
+ (((reg) & (~((u32)0x1f << 18))) | ((x) << 18))
+#define DWC_DCFG_P_FRM_INTRVL_WR(reg, x) \
+ (((reg) & (~((u32)0x03 << 11))) | ((x) << 11))
+#define DWC_DCFG_DEV_ADDR_WR(reg, x) \
+ (((reg) & (~((u32)0x3f << 04))) | ((x) << 04))
+#define DWC_DCFG_NGL_STS_OUT_WR(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_DCFG_DEV_SPEED_WR(reg, x) \
+ (((reg) & (~(u32)0x3)) | (x))
+
+#define DWC_DCFG_FRAME_INTERVAL_80 0
+#define DWC_DCFG_FRAME_INTERVAL_85 1
+#define DWC_DCFG_FRAME_INTERVAL_90 2
+#define DWC_DCFG_FRAME_INTERVAL_95 3
+
+/*
+ * These Macros represents the bit fields in the Device Control Register. Read
+ * the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DCTL_PWR_ON_PROG_DONE_RD(x) (((x) & (1 << 11)) >> 11)
+
+#define DWC_DCTL_PWR_ON_PROG_DONE_WR(reg, x) \
+ (((reg) & (~((u32)0x01 << 11))) | ((x) << 11))
+#define DWC_DCTL_CLR_GLBL_OUT_NAK_WR(reg, x) \
+ (((reg) & (~((u32)0x01 << 10))) | ((x) << 10))
+#define DWC_DCTL_SET_GLBL_OUT_NAL(reg, x) \
+ (((reg) & (~((u32)0x01 << 9))) | ((x) << 9))
+#define DWC_DCTL_CLR_CLBL_NP_IN_NAK(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DCTL_SET_GLBL_NP_IN_NAK(reg, x) \
+ (((reg) & (~((u32)0x01 << 07))) | ((x) << 07))
+#define DWC_DCTL_TST_CTL(reg, x) \
+ (((reg) & (~((u32)0x07 << 04))) | ((x) << 04))
+#define DWC_DCTL_GLBL_OUT_NAK_STS(reg, x) \
+ (((reg) & (~((u32)0x01 << 03))) | ((x) << 03))
+#define DWC_DCTL_GLBL_NP_IN_NAK(reg, x) \
+ (((reg) & (~((u32)0x01 << 02))) | ((x) << 02))
+#define DWC_DCTL_SFT_DISCONNECT(reg, x) \
+ (((reg) & (~((u32)0x01 << 01))) | ((x) << 01))
+#define DEC_DCTL_REMOTE_WAKEUP_SIG(reg, x) \
+ (((reg) & (~((u32)0x01 << 00))) | ((x) << 00))
+
+/*
+ * These Macros represents the bit fields in the Dev Status Register. Read the
+ * register into the u32 member then set/clear the bits using the bit elements.
+ */
+#define DWC_DSTS_SOFFN_RD(x) (((x) & (0x3fff << 8)) >> 8)
+#define DWC_DSTS_ERRTICERR_RD(x) (((x) & (0x0001 << 3)) >> 3)
+#define DWC_DSTS_ENUM_SPEED_RD(x) (((x) & (0x0003 << 1)) >> 1)
+#define DWC_DSTS_SUSP_STS_RD(x) ((x) & 1)
+
+#define DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ 0
+#define DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ 1
+#define DWC_DSTS_ENUMSPD_LS_PHY_6MHZ 2
+#define DWC_DSTS_ENUMSPD_FS_PHY_48MHZ 3
+
+/*
+ * These Macros represents the bit fields in the Device IN EP Interrupt Register
+ * and the Device IN EP Common Mask Register.
+ *
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DIEPINT_TXFIFO_UNDERN_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_DIEPINT_TXFIFO_EMPTY_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_DIEPINT_IN_EP_NAK_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_DIEPINT_IN_TKN_EP_MISS_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_DIEPINT_IN_TKN_TX_EMPTY_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_DIEPINT_TOUT_COND_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_DIEPINT_AHB_ERROR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_DIEPINT_EP_DISA_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_DIEPINT_TX_CMPL_RD(x) ((x) & 0x1)
+
+#define DWC_DIEPINT_TXFIFO_UNDERN_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DIEPINT_TXFIFO_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 7))) | ((x) << 7))
+#define DWC_DIEPINT_IN_EP_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 6))) | ((x) << 6))
+#define DWC_DIEPINT_IN_TKN_EP_MISS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 5))) | ((x) << 5))
+#define DWC_DIEPINT_IN_TKN_TX_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 4))) | ((x) << 4))
+#define DWC_DIEPINT_TOUT_COND_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 3))) | ((x) << 3))
+#define DWC_DIEPINT_AHB_ERROR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 2))) | ((x) << 2))
+#define DWC_DIEPINT_EP_DISA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_DIEPINT_TX_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+
+#define DWC_DIEPMSK_TXFIFO_UNDERN_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DIEPMSK_TXFIFO_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 7))) | ((x) << 7))
+#define DWC_DIEPMSK_IN_EP_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 6))) | ((x) << 6))
+#define DWC_DIEPMSK_IN_TKN_EP_MISS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 5))) | ((x) << 5))
+#define DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 4))) | ((x) << 4))
+#define DWC_DIEPMSK_TOUT_COND_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 3))) | ((x) << 3))
+#define DWC_DIEPMSK_AHB_ERROR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 2))) | ((x) << 2))
+#define DWC_DIEPMSK_EP_DISA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_DIEPMSK_TX_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Device OUT EP Itr Register
+ * and Device OUT EP Common Interrupt Mask Register.
+ *
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DOEPINT_OUTPKT_ERR_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_DOEPINT_B2B_PKTS_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_DOEPINT_OUT_TKN_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_DOEPINT_SETUP_DONE_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_DOEPINT_AHB_ERROR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_DOEPINT_EP_DISA_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_DOEPINT_TX_COMPL_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_DOEPMSK_OUTPKT_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DOEPMSK_B2B_PKTS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 6))) | ((x) << 6))
+#define DWC_DOEPMSK_OUT_TKN_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 4))) | ((x) << 4))
+#define DWC_DOEPMSK_SETUP_DONE_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 3))) | ((x) << 3))
+#define DWC_DOEPMSK_AHB_ERROR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 2))) | ((x) << 2))
+#define DWC_DOEPMSK_EP_DISA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_DOEPMSK_TX_COMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Device All EP Intr and Mask
+ * Registers. Read the register into the u32 member then set/clear the bits
+ * using the bit elements.
+ */
+#define DWC_DAINT_OUT_EP_RD(reg, ep) \
+ (((reg) & (1 << (ep + 16))) >> (ep + 16))
+#define DWC_DAINTMSK_OUT_EP_RW(reg, ep) \
+ (((reg) & (~(u32)(1 << (ep + 16)))) | (1 << (ep + 16)))
+#define DWC_DAINT_IN_EP_RD(reg, ep) (((reg) & (1 << ep)) >> ep)
+#define DWC_DAINTMSK_IN_EP_RW(reg, ep) \
+ (((reg) & (~(u32)(1 << ep))) | (1 << ep))
+#define DWC_DAINT_OUTEP15 (1 << 31)
+#define DWC_DAINT_OUTEP14 (1 << 30)
+#define DWC_DAINT_OUTEP13 (1 << 29)
+#define DWC_DAINT_OUTEP12 (1 << 28)
+#define DWC_DAINT_OUTEP11 (1 << 27)
+#define DWC_DAINT_OUTEP10 (1 << 26)
+#define DWC_DAINT_OUTEP09 (1 << 25)
+#define DWC_DAINT_OUTEP08 (1 << 24)
+#define DWC_DAINT_OUTEP07 (1 << 23)
+#define DWC_DAINT_OUTEP06 (1 << 22)
+#define DWC_DAINT_OUTEP05 (1 << 21)
+#define DWC_DAINT_OUTEP04 (1 << 20)
+#define DWC_DAINT_OUTEP03 (1 << 19)
+#define DWC_DAINT_OUTEP02 (1 << 18)
+#define DWC_DAINT_OUTEP01 (1 << 17)
+#define DWC_DAINT_OUTEP00 (1 << 16)
+#define DWC_DAINT_INEP15 (1 << 15)
+#define DWC_DAINT_INEP14 (1 << 14)
+#define DWC_DAINT_INEP13 (1 << 13)
+#define DWC_DAINT_INEP12 (1 << 12)
+#define DWC_DAINT_INEP11 (1 << 11)
+#define DWC_DAINT_INEP10 (1 << 10)
+#define DWC_DAINT_INEP09 (1 << 9)
+#define DWC_DAINT_INEP08 (1 << 8)
+#define DWC_DAINT_INEP07 (1 << 07)
+#define DWC_DAINT_INEP06 (1 << 06)
+#define DWC_DAINT_INEP05 (1 << 05)
+#define DWC_DAINT_INEP04 (1 << 04)
+#define DWC_DAINT_INEP03 (1 << 03)
+#define DWC_DAINT_INEP02 (1 << 02)
+#define DWC_DAINT_INEP01 (1 << 01)
+#define DWC_DAINT_INEP00 (1 << 00)
+
+/*
+ * These Macros represents the bit fields in the Device IN Token Queue Read
+ * Registers. Read the register into the u32 member. READ-ONLY Register
+ */
+#define DWC_DTKNQR1_EP_TKN_NO_RD(x) (((x) & (0xffffff << 8)) >> 8)
+#define DWC_DTKNQR1_WRAP_BIT_RD(x) (((x) & (1 << 7)) >> 7)
+#define DWC_DTKNQR1_INT_TKN_Q_WR_PTR_RD(x) ((x) & 0x1f)
+
+/*
+ * These Macros represents Threshold control Register. Read and wr the register
+ * into the u32 member. READ-WRITABLE Register
+ */
+#define DWC_DTHCTRL_RX_ARB_PARK_EN_RD(x) (((x) & (0x001 << 27)) >> 27)
+#define DWC_DTHCTRL_RX_THR_LEN_RD(x) (((x) & (0x1ff << 17)) >> 17)
+#define DWC_DTHCTRL_RX_THR_EN_RD(x) (((x) & (0x001 << 16)) >> 16)
+#define DWC_DTHCTRL_TX_THR_LEN_RD(x) (((x) & (0x1ff << 02)) >> 02)
+#define DWC_DTHCTRL_ISO_THR_EN(x) (((x) & (0x001 << 01)) >> 01)
+#define DWC_DTHCTRL_NON_ISO_THR_ENA_RD(x) (((x) & (0x001 << 00)) >> 00)
+
+#define DWC_DTHCTRL_RX_ARB_PARK_EN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 27))) | ((x) << 27))
+#define DWC_DTHCTRL_RX_THR_LEN_RW(reg, x) \
+ (((reg) & (~((u32)0x1ff << 17))) | ((x) << 17))
+#define DWC_DTHCTRL_RX_THR_EN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 16))) | ((x) << 16))
+#define DWC_DTHCTRL_TX_THR_LEN_RW(reg, x) \
+ (((reg) & (~((u32)0x1ff << 02))) | ((x) << 02))
+#define DWC_DTHCTRL_ISO_THR_EN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 01))) | ((x) << 01))
+#define DWC_DTHCTRL_NON_ISO_THR_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 00))) | ((x) << 00))
+
+/*
+ * Device Logical IN Endpoint-Specific Registers. Offsets 900h-AFCh
+ *
+ * There will be one set of endpoint registers per logical endpoint implemented.
+ *
+ * These registers are visible only in Device mode and must not be accessed in
+ * Host mode, as the results are unknown.
+ */
+#define DWC_DIEPCTL 0x00
+#define DWC_DIEPINT 0x08
+#define DWC_DIEPTSIZ 0x10
+#define DWC_DIEPDMA 0x14
+#define DWC_DTXFSTS 0x18
+
+/*
+ * Device Logical OUT Endpoint-Specific Registers. Offsets: B00h-CFCh
+ *
+ * There will be one set of endpoint registers per logical endpoint implemented.
+ *
+ * These registers are visible only in Device mode and must not be accessed in
+ * Host mode, as the results are unknown.
+ */
+#define DWC_DOEPCTL 0x00
+#define DWC_DOEPFN 0x04
+#define DWC_DOEPINT 0x08
+#define DWC_DOEPTSIZ 0x10
+#define DWC_DOEPDMA 0x14
+
+/*
+ * These Macros represents the bit fields in the Device EP Ctrl Register. Read
+ * the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DEP0CTL_MPS_64 0
+#define DWC_DEP0CTL_MPS_32 1
+#define DWC_DEP0CTL_MPS_16 2
+#define DWC_DEP0CTL_MPS_8 3
+
+#define DWC_DEPCTL_EPENA_RD(x) (((x) & (0x1 << 31)) >> 31)
+#define DWC_DEPCTL_EPDIS_RD(x) (((x) & (0x1 << 30)) >> 30)
+#define DWC_DEPCTL_SET_DATA1_PID_RD(x) (((x) & (0x1 << 29)) >> 29)
+#define DWC_DEPCTL_SET_DATA0_PID_RD(x) (((x) & (0x1 << 28)) >> 28)
+#define DWC_DEPCTL_SET_NAK_RD(x) (((x) & (0x1 << 27)) >> 27)
+#define DWC_DEPCTL_CLR_NAK_RD(x) (((x) & (0x1 << 26)) >> 26)
+#define DWC_DEPCTL_TX_FIFO_NUM_RD(x) (((x) & (0xf << 22)) >> 22)
+#define DWC_DEPCTL_STALL_HNDSHK _RD(x) (((x) & (0x1 << 21)) >> 21)
+#define DWC_DEPCTL_SNP_MODE_RD(x) (((x) & (0x1 << 20)) >> 20)
+#define DWC_DEPCTL_EP_TYPE_RD(x) (((x) & (0x3 << 18)) >> 18)
+#define DWC_DEPCTL_NKASTS_RD(x) (((x) & (0x1 << 17)) >> 17)
+#define DWC_DEPCTL_DPID _RD(x) (((x) & (0x1 << 16)) >> 16)
+#define DWC_DEPCTL_ACT_EP_RD(x) (((x) & (0x1 << 15)) >> 15)
+#define DWC_DEPCTL_NXT_EP_RD(x) (((x) & (0xf << 11)) >> 11)
+#define DWC_DEPCTL_MPS_RD(x) (((x) & (0x7ff << 00)) >> 00)
+
+#define DWC_DEPCTL_EPENA_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 31))) | ((x) << 31))
+#define DWC_DEPCTL_EPDIS_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 30))) | ((x) << 30))
+#define DWC_DEPCTL_SET_DATA1_PID_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 29))) | ((x) << 29))
+#define DWC_DEPCTL_SET_DATA0_PID_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 28))) | ((x) << 28))
+#define DWC_DEPCTL_SET_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 27))) | ((x) << 27))
+#define DWC_DEPCTL_CLR_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 26))) | ((x) << 26))
+#define DWC_DEPCTL_TX_FIFO_NUM_RW(reg, x) \
+ (((reg) & (~((u32)0x00f << 22))) | ((x) << 22))
+#define DWC_DEPCTL_STALL_HNDSHK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 21))) | ((x) << 21))
+#define DWC_DEPCTL_SNP_MODE_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 20))) | ((x) << 20))
+#define DWC_DEPCTL_EP_TYPE_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 18))) | ((x) << 18))
+#define DWC_DEPCTL_NKASTS_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 17))) | ((x) << 17))
+#define DWC_DEPCTL_DPID_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 16))) | ((x) << 16))
+#define DWC_DEPCTL_ACT_EP_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 15))) | ((x) << 15))
+#define DWC_DEPCTL_NXT_EP_RW(reg, x) \
+ (((reg) & (~((u32)0x00f << 11))) | ((x) << 11))
+#define DWC_DEPCTL_MPS_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 00))) | ((x) << 00))
+
+/*
+ * These Macros represents the bit fields in the Device EP Txfer Size Register.
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#if defined(DWC_LIMITED_XFER_SIZE)
+#define DWC_DEPTSIZ_MCOUNT_RD(x) (((x) & (0x003 << 29)) >> 29)
+#define DWC_DEPTSIZ_PKT_CNT_RD(x) (((x) & (0x01f << 19)) >> 19)
+#define DWC_DEPTSIZ_XFER_SIZ_RD(x) (((x) & (0x7ff << 00)) >> 00)
+#define DWC_DEPTSIZ_MCOUNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 29))) | ((x) << 29))
+#define DWC_DEPTSIZ_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x01f << 19))) | ((x) << 19))
+#define DWC_DEPTSIZ_XFER_SIZ_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 00))) | ((x) << 00))
+#else
+#define DWC_DEPTSIZ_MCOUNT_RD(x) \
+ (((x) & (0x003 << 29)) >> 29)
+#define DWC_DEPTSIZ_PKT_CNT_RD(x) \
+ (((x) & (0x3ff << 19)) >> 19)
+#define DWC_DEPTSIZ_XFER_SIZ_RD(x) \
+ (((x) & (0x7ffff << 00)) >> 00)
+#define DWC_DEPTSIZ_MCOUNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 29))) | ((x) << 29))
+#define DWC_DEPTSIZ_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 19))) | ((x) << 19))
+#define DWC_DEPTSIZ_XFER_SIZ_RW(reg, x) \
+ (((reg) & (~((u32)0x7ffff << 00))) | ((x) << 00))
+#endif
+
+/*
+ * These Macros represents the bit fields in the Device EP 0 Transfer Size
+ * Register. Read the register into the u32 member then set/clear the bits
+ * using the bit elements.
+ */
+#define DWC_DEPTSIZ0_SUPCNT_RD(x) (((x) & (0x003 << 29)) >> 29)
+#define DWC_DEPTSIZ0_PKT_CNT_RD(x) (((x) & (0x001 << 19)) >> 19)
+#define DWC_DEPTSIZ0_XFER_SIZ_RD(x) (((x) & (0x07f << 00)) >> 00)
+#define DWC_DEPTSIZ0_SUPCNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 29))) | ((x) << 29))
+#define DWC_DEPTSIZ0_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 19))) | ((x) << 19))
+#define DWC_DEPTSIZ0_XFER_SIZ_RW(reg, x) \
+ (((reg) & (~((u32)0x07f << 00))) | ((x) << 00))
+
+#define MAX_PERIO_FIFOS 15 /* Max periodic FIFOs */
+#define MAX_TX_FIFOS 15 /* Max non-periodic FIFOs */
+
+/* Maximum number of Endpoints/HostChannels */
+#define MAX_EPS_CHANNELS 12 /* This come from device tree or defconfig */
+
+/*
+ * The device_if structure contains information needed to manage the DWC_otg
+ * controller acting in device mode. It represents the programming view of the
+ * device-specific aspects of the controller.
+ */
+struct device_if {
+ /* Device Global Registers starting at offset 800h */
+ ulong dev_global_regs;
+#define DWC_DEV_GLOBAL_REG_OFFSET 0x800
+
+ /* Device Logical IN Endpoint-Specific Registers 900h-AFCh */
+ ulong in_ep_regs[MAX_EPS_CHANNELS];
+#define DWC_DEV_IN_EP_REG_OFFSET 0x900
+#define DWC_EP_REG_OFFSET 0x20
+
+ /* Device Logical OUT Endpoint-Specific Registers B00h-CFCh */
+ ulong out_ep_regs[MAX_EPS_CHANNELS];
+#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00
+
+ /* Device configuration information */
+ /* Device Speed 0: Unknown, 1: LS, 2:FS, 3: HS */
+ u8 speed;
+ /* Number # of Tx EP range: 0-15 exept ep0 */
+ u8 num_in_eps;
+ /* Number # of Rx EP range: 0-15 exept ep 0 */
+ u8 num_out_eps;
+
+ /* Size of periodic FIFOs (Bytes) */
+ u16 perio_tx_fifo_size[MAX_PERIO_FIFOS];
+
+ /* Size of Tx FIFOs (Bytes) */
+ u16 tx_fifo_size[MAX_TX_FIFOS];
+
+ /* Thresholding enable flags and length varaiables */
+ u16 rx_thr_en;
+ u16 iso_tx_thr_en;
+ u16 non_iso_tx_thr_en;
+ u16 rx_thr_length;
+ u16 tx_thr_length;
+};
+
+/*
+ * These Macros represents the bit fields in the Power and Clock Gating Control
+ * Register. Read the register into the u32 member then set/clear the
+ * bits using the bit elements.
+ */
+#define DWC_PCGCCTL_PHY_SUS_RD(x) (((x) & (0x001 << 4)) >> 4)
+#define DWC_PCGCCTL_RSTP_DWN_RD(x) (((x) & (0x001 << 3)) >> 3)
+#define DWC_PCGCCTL_PWR_CLAMP_RD(x) (((x) & (0x001 << 2)) >> 2)
+#define DWC_PCGCCTL_GATE_HCLK_RD(x) (((x) & (0x001 << 1)) >> 1)
+#define DWC_PCGCCTL_STOP_CLK_RD(x) (((x) & (0x001 << 0)) >> 0)
+
+#define DWC_PCGCCTL_RSTP_DWN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 3))) | ((x) << 3))
+#define DWC_PCGCCTL_PWR_CLAMP_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 2))) | ((x) << 2))
+#define DWC_PCGCCTL_GATE_HCLK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 1))) | ((x) << 1))
+#define DWC_PCGCCTL_STOP_CLK_SET(reg) \
+ (((reg) | 1))
+#define DWC_PCGCCTL_STOP_CLK_CLR(reg) \
+ (((reg) & (~((u32)0x001 << 0))))
+
+/*
+ * Host Mode Register Structures
+ */
+
+/*
+ * The Host Global Registers structure defines the size and relative field
+ * offsets for the Host Mode Global Registers. Host Global Registers offsets
+ * 400h-7FFh.
+*/
+#define DWC_HCFG 0x00
+#define DWC_HFIR 0x04
+#define DWC_HFNUM 0x08
+#define DWC_HPTXSTS 0x10
+#define DWC_HAINT 0x14
+#define DWC_HAINTMSK 0x18
+
+/*
+ * These Macros represents the bit fields in the Host Configuration Register.
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements. Write the u32 member to the hcfg register.
+ */
+#define DWC_HCFG_FSLSUPP_RD(x) (((x) & (0x001 << 2)) >> 2)
+#define DWC_HCFG_FSLSP_CLK_RD(x) (((x) & (0x003 << 0)) >> 0)
+#define DWC_HCFG_FSLSUPP_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 2))) | ((x) << 2))
+#define DWC_HCFG_FSLSP_CLK_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 0))) | ((x) << 0))
+
+#define DWC_HCFG_30_60_MHZ 0
+#define DWC_HCFG_48_MHZ 1
+#define DWC_HCFG_6_MHZ 2
+
+/*
+ * These Macros represents the bit fields in the Host Frame Remaing/Number
+ * Register.
+ */
+#define DWC_HFIR_FRINT_RD(x) (((x) & (0xffff << 0)) >> 0)
+#define DWC_HFIR_FRINT_RW(reg, x) \
+ (((reg) & (~((u32)0xffff << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Host Frame Remaing/Number
+ * Register.
+ */
+#define DWC_HFNUM_FRREM_RD(x) (((x) & (0xffff << 16)) >> 16)
+#define DWC_HFNUM_FRNUM_RD(x) (((x) & (0xffff << 0)) >> 0)
+#define DWC_HFNUM_FRREM_RW(reg, x) \
+ (((reg) & (~((u32)0xffff << 16))) | ((x) << 16))
+#define DWC_HFNUM_FRNUM_RW(reg, x) \
+ (((reg) & (~((u32)0xffff << 0))) | ((x) << 0))
+#define DWC_HFNUM_MAX_FRNUM 0x3FFF
+#define DWC_HFNUM_MAX_FRNUM 0x3FFF
+
+#define DWC_HPTXSTS_PTXQTOP_ODD_RD(x) (((x) & (0x01 << 31)) >> 31)
+#define DWC_HPTXSTS_PTXQTOP_CHNUM_RD(x) (((x) & (0x0f << 27)) >> 27)
+#define DWC_HPTXSTS_PTXQTOP_TKN_RD(x) (((x) & (0x03 << 25)) >> 25)
+#define DWC_HPTXSTS_PTXQTOP_TERM_RD(x) (((x) & (0x01 << 24)) >> 24)
+#define DWC_HPTXSTS_PTXSPC_AVAIL_RD(x) (((x) & (0xff << 16)) >> 16)
+#define DWC_HPTXSTS_PTXFSPC_AVAIL_RD(x) (((x) & (0xffff << 00)) >> 00)
+
+/*
+ * These Macros represents the bit fields in the Host Port Control and Status
+ * Register. Read the register into the u32 member then set/clear the bits using
+ * the bit elements. Write the u32 member to the hprt0 register.
+ */
+#define DWC_HPRT0_PRT_SPD_RD(x) (((x) & (0x3 << 17)) >> 17)
+#define DWC_HPRT0_PRT_TST_CTL_RD(x) (((x) & (0xf << 13)) >> 13)
+#define DWC_HPRT0_PRT_PWR_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HPRT0_PRT_LSTS_RD(x) (((x) & (0x3 << 10)) >> 10)
+#define DWC_HPRT0_PRT_RST_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HPRT0_PRT_SUS_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HPRT0_PRT_RES_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HPRT0_PRT_OVRCURR_CHG_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HPRT0_PRT_OVRCURR_ACT_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HPRT0_PRT_ENA_DIS_CHG_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HPRT0_PRT_ENA_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HPRT0_PRT_CONN_DET_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HPRT0_PRT_STS_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HPRT0_PRT_SPD_RW(reg, x) \
+ (((reg) & (~((u32)0x3 << 17))) | ((x) << 17))
+#define DWC_HPRT0_PRT_TST_CTL_RW(reg, x) \
+ (((reg) & (~((u32)0xf << 13))) | ((x) << 13))
+#define DWC_HPRT0_PRT_PWR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 12))) | ((x) << 12))
+#define DWC_HPRT0_PRT_LSTS_RW(reg, x) \
+ (((reg) & (~((u32)0x3 << 10))) | ((x) << 10))
+#define DWC_HPRT0_PRT_RST_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HPRT0_PRT_SUS_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HPRT0_PRT_RES_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HPRT0_PRT_OVRCURR_CHG_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HPRT0_PRT_OVRCURR_ACT_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HPRT0_PRT_ENA_DIS_CHG_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HPRT0_PRT_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HPRT0_PRT_CONN_DET_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HPRT0_PRT_STS_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+
+#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0
+#define DWC_HPRT0_PRTSPD_FULL_SPEED 1
+#define DWC_HPRT0_PRTSPD_LOW_SPEED 2
+
+/*
+ * These Macros represents the bit fields in the Host All Interrupt Register.
+ */
+#define DWC_HAINT_CH15_RD(x) (((x) & (0x1 << 15)) >> 15)
+#define DWC_HAINT_CH14_RD(x) (((x) & (0x1 << 14)) >> 14)
+#define DWC_HAINT_CH13_RD(x) (((x) & (0x1 << 13)) >> 13)
+#define DWC_HAINT_CH12_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HAINT_CH11_RD(x) (((x) & (0x1 << 11)) >> 11)
+#define DWC_HAINT_CH10_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HAINT_CH09_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HAINT_CH08_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HAINT_CH07_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HAINT_CH06_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HAINT_CH05_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HAINT_CH04_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HAINT_CH03_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HAINT_CH02_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HAINT_CH01_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HAINT_CH00_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HAINT_RD(x) (((x) & (0xffff << 0)) >> 0)
+
+/*
+ * These Macros represents the bit fields in the Host All Interrupt Register.
+ */
+#define DWC_HAINTMSK_CH15_RD(x) (((x) & (0x1 << 15)) >> 15)
+#define DWC_HAINTMSK_CH14_RD(x) (((x) & (0x1 << 14)) >> 14)
+#define DWC_HAINTMSK_CH13_RD(x) (((x) & (0x1 << 13)) >> 13)
+#define DWC_HAINTMSK_CH12_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HAINTMSK_CH11_RD(x) (((x) & (0x1 << 11)) >> 11)
+#define DWC_HAINTMSK_CH10_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HAINTMSK_CH09_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HAINTMSK_CH08_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HAINTMSK_CH07_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HAINTMSK_CH06_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HAINTMSK_CH05_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HAINTMSK_CH04_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HAINTMSK_CH03_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HAINTMSK_CH02_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HAINTMSK_CH01_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HAINTMSK_CH00_RD(x) (((x) & (0x1 << 0)) >> 0)
+#define DWC_HAINTMSK_RD(x) ((x) & 0xffff)
+
+#define DWC_HAINTMSK_CH15_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 15))) | ((x) << 15))
+#define DWC_HAINTMSK_CH14_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 14))) | ((x) << 14))
+#define DWC_HAINTMSK_CH13_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 13))) | ((x) << 13))
+#define DWC_HAINTMSK_CH12_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 12))) | ((x) << 12))
+#define DWC_HAINTMSK_CH11_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 11))) | ((x) << 11))
+#define DWC_HAINTMSK_CH10_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 10))) | ((x) << 10))
+#define DWC_HAINTMSK_CH09_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 9))) | ((x) << 9))
+#define DWC_HAINTMSK_CH08_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HAINTMSK_CH07_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HAINTMSK_CH06_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HAINTMSK_CH05_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HAINTMSK_CH04_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HAINTMSK_CH03_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HAINTMSK_CH02_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HAINTMSK_CH01_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HAINTMSK_CH00_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+#define DWC_HAINTMSK_RW(reg, x) \
+ (((reg) & (~((u32)0xffff))) | x)
+
+/*
+ * Host Channel Specific Registers. 500h-5FCh
+ */
+#define DWC_HCCHAR 0x00
+#define DWC_HCSPLT 0x04
+#define DWC_HCINT 0x08
+#define DWC_HCINTMSK 0x0C
+#define DWC_HCTSIZ 0x10
+#define DWC_HCDMA 0x14
+
+/*
+ * These Macros represents the bit fields in the Host Channel Characteristics
+ * Register. Read the register into the u32 member then set/clear the bits using
+ * the bit elements. Write the u32 member to the hcchar register.
+ */
+#define DWC_HCCHAR_ENA_RD(x) (((x) & (0x001 << 31)) >> 31)
+#define DWC_HCCHAR_DIS_RD(x) (((x) & (0x001 << 30)) >> 30)
+#define DWC_HCCHAR_ODD_FRAME_RD(x) (((x) & (0x001 << 29)) >> 29)
+#define DWC_HCCHAR_DEV_ADDR_RD(x) (((x) & (0x07f << 22)) >> 22)
+#define DWC_HCCHAR_MULTI_CNT_RD(x) (((x) & (0x003 << 20)) >> 20)
+#define DWC_HCCHAR_EPTYPE_RD(x) (((x) & (0x003 << 18)) >> 18)
+#define DWC_HCCHAR_LSP_DEV_RD(x) (((x) & (0x001 << 17)) >> 17)
+#define DWC_HCCHAR_EPDIR_RD(x) (((x) & (0x001 << 15)) >> 15)
+#define DWC_HCCHAR_EP_NUM_RD(x) (((x) & (0x00f << 11)) >> 11)
+#define DWC_HCCHAR_MPS_RD(x) (((x) & (0x7ff << 0)) >> 0)
+
+#define DWC_HCCHAR_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 31))) | ((x) << 31))
+#define DWC_HCCHAR_DIS_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 30))) | ((x) << 30))
+#define DWC_HCCHAR_ODD_FRAME_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 29))) | ((x) << 29))
+#define DWC_HCCHAR_DEV_ADDR_RW(reg, x) \
+ (((reg) & (~((u32)0x07f << 22))) | ((x) << 22))
+#define DWC_HCCHAR_MULTI_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 20))) | ((x) << 20))
+#define DWC_HCCHAR_EPTYPE_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 18))) | ((x) << 18))
+#define DWC_HCCHAR_LSP_DEV_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 17))) | ((x) << 17))
+#define DWC_HCCHAR_EPDIR_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 15))) | ((x) << 15))
+#define DWC_HCCHAR_EP_NUM_RW(reg, x) \
+ (((reg) & (~((u32)0x00f << 11))) | ((x) << 11))
+#define DWC_HCCHAR_MPS_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 0))) | ((x) << 0))
+
+#define DWC_HCSPLT_ENA_RD(x) (((x) & (0x01 << 31)) >> 31)
+#define DWC_HCSPLT_COMP_SPLT_RD(x) (((x) & (0x01 << 16)) >> 16)
+#define DWC_HCSPLT_TRANS_POS_RD(x) (((x) & (0x03 << 14)) >> 14)
+#define DWC_HCSPLT_HUB_ADDR_RD(x) (((x) & (0x7f << 7)) >> 7)
+#define DWC_HCSPLT_PRT_ADDR_RD(x) (((x) & (0x7f << 0)) >> 0)
+
+#define DWC_HCSPLT_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 31))) | ((x) << 31))
+#define DWC_HCSPLT_COMP_SPLT_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 16))) | ((x) << 16))
+#define DWC_HCSPLT_TRANS_POS_RW(reg, x) \
+ (((reg) & (~((u32)0x03 << 14))) | ((x) << 14))
+#define DWC_HCSPLT_HUB_ADDR_RW(reg, x) \
+ (((reg) & (~((u32)0x7f << 7))) | ((x) << 7))
+#define DWC_HCSPLT_PRT_ADDR_RW(reg, x) \
+ (((reg) & (~((u32)0x7f << 0))) | ((x) << 0))
+
+#define DWC_HCSPLIT_XACTPOS_MID 0
+#define DWC_HCSPLIT_XACTPOS_END 1
+#define DWC_HCSPLIT_XACTPOS_BEGIN 2
+#define DWC_HCSPLIT_XACTPOS_ALL 3
+
+/*
+ * These Macros represents the bit fields in the Host All Interrupt
+ * Register.
+ */
+#define DWC_HCINT_DATA_TOG_ERR_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HCINT_FRAME_OVERN_ERR_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HCINT_BBL_ERR_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HCINT_TRANS_ERR_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HCINT_NYET_RESP_REC_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HCINT_ACK_RESP_REC_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HCINT_NAK_RESP_REC_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HCINT_STALL_RESP_REC_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HCINT_AHB_ERR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HCINT_CHAN_HALTED_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HCINT_TXFER_CMPL_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HCINT_DATA_TOG_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 10))) | ((x) << 10))
+#define DWC_HCINT_FRAME_OVERN_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 9))) | ((x) << 9))
+#define DWC_HCINT_BBL_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HCINT_TRANS_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HCINT_NYET_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HCINT_ACK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HCINT_NAK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HCINT_STALL_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HCINT_AHB_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HCINT_CHAN_HALTED_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HCINT_TXFER_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Host Channel Transfer Size
+ * Register. Read the register into the u32 member then set/clear the bits
+ * using the bit elements. Write the u32 member to the hcchar register.
+ */
+#define DWC_HCTSIZ_DO_PING_PROTO_RD(x) (((x) & (0x00001 << 31)) >> 31)
+#define DWC_HCTSIZ_PKT_PID_RD(x) (((x) & (0x00003 << 29)) >> 29)
+#define DWC_HCTSIZ_PKT_CNT_RD(x) (((x) & (0x003ff << 19)) >> 19)
+#define DWC_HCTSIZ_XFER_SIZE_RD(x) (((x) & (0x7ffff << 00)) >> 00)
+
+#define DWC_HCTSIZ_DO_PING_PROTO_RW(reg, x) \
+ (((reg) & (~((u32)0x00001 << 31))) | ((x) << 31))
+#define DWC_HCTSIZ_PKT_PID_RW(reg, x) \
+ (((reg) & (~((u32)0x00003 << 29))) | ((x) << 29))
+#define DWC_HCTSIZ_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003ff << 19))) | ((x) << 19))
+#define DWC_HCTSIZ_XFER_SIZE_RW(reg, x) \
+ (((reg) & (~((u32)0x7ffff << 00))) | ((x) << 00))
+
+#define DWC_HCTSIZ_DATA0 0
+#define DWC_HCTSIZ_DATA1 2
+#define DWC_HCTSIZ_DATA2 1
+#define DWC_HCTSIZ_MDATA 3
+#define DWC_HCTSIZ_SETUP 3
+
+/*
+ * These Macros represents the bit fields in the Host Channel Interrupt Mask
+ * Register. Read the register into the u32 member then set/clear the bits using
+ * the bit elements. Write the u32 member to the hcintmsk register.
+ */
+#define DWC_HCINTMSK_DATA_TOG_ERR_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HCINTMSK_FRAME_OVERN_ERR_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HCINTMSK_BBL_ERR_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HCINTMSK_TRANS_ERR_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HCINTMSK_NYET_RESP_REC_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HCINTMSK_ACK_RESP_REC_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HCINTMSK_NAK_RESP_REC_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HCINTMSK_STALL_RESP_REC_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HCINTMSK_AHB_ERR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HCINTMSK_CHAN_HALTED_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HCINTMSK_TXFER_CMPL_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HCINTMSK_DATA_TOG_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 10))) | ((x) << 10))
+#define DWC_HCINTMSK_FRAME_OVERN_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 9))) | ((x) << 9))
+#define DWC_HCINTMSK_BBL_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HCINTMSK_TRANS_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HCINTMSK_NYET_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HCINTMSK_ACK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HCINTMSK_NAK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HCINTMSK_STALL_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HCINTMSK_AHB_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HCINTMSK_CHAN_HALTED_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HCINTMSK_TXFER_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+
+/*
+ * OTG Host Interface Structure.
+ *
+ * The OTG Host Interface Structure structure contains information needed to
+ * manage the DWC_otg controller acting in host mode. It represents the
+ * programming view of the host-specific aspects of the controller.
+ */
+struct dwc_host_if { /* CONFIG_DWC_OTG_REG_LE */
+ /* Host Global Registers starting at offset 400h. */
+ ulong host_global_regs;
+#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400
+
+ /* Host Port 0 Control and Status Register */
+ ulong hprt0;
+#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440
+
+ /* Host Channel Specific Registers at offsets 500h-5FCh. */
+ ulong hc_regs[MAX_EPS_CHANNELS];
+#define DWC_OTG_HOST_CHAN_REGS_OFFSET 0x500
+#define DWC_OTG_CHAN_REGS_OFFSET 0x20
+
+ /* Host configuration information */
+ /* Number of Host Channels (range: 1-16) */
+ u8 num_host_channels;
+ /* Periodic EPs supported (0: no, 1: yes) */
+ u8 perio_eps_supported;
+ /* Periodic Tx FIFO Size (Only 1 host periodic Tx FIFO) */
+ u16 perio_tx_fifo_size;
+};
+#endif
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 46f81ae..9f30cf5 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -112,6 +112,7 @@ config USB_GADGET_SELECTED
choice
prompt "USB Peripheral Controller"
depends on USB_GADGET
+ default USB_GADGET_S3C_OTGD if (ARCH_S5PV210)
help
A USB device uses a controller to talk to its host.
Systems should have only one such upstream link.
@@ -373,6 +374,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 (ARCH_S5PV210)
+ 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 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,6 +648,43 @@ config USB_DUMMY_HCD
endchoice
+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 "S3C OTGD transfer mode"
+ depends on USB_S3C_OTGD
+ default y
+ help
+ S3C USB OTG conteroller supports DMA mode and Slave mode
+ for the dat transfer. You must slect one for the core
+ operation mode.
+
+config USB_GADGET_S3C_OTGD_DMA_MODE
+ bool "enabled DMA MODE"
+ depends on USB_GADGET_S3C_OTGD
+ help
+ S3C USB OTG core operates in DMA mode.
+
+config USB_GADGET_S3C_OTGD_SLAVE_MODE
+ bool "enabled Slave MODE"
+ depends on USB_GADGET_S3C_OTGD
+ help
+ S3C USB OTG core operates in Slave mode.
+endchoice
+
# Selected by UDC drivers that support high-speed operation.
config USB_GADGET_DUALSPEED
bool
@@ -936,6 +988,20 @@ 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_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_RNDIS_DWORD_ALIGNED
+ boolean "Use double word aligned"
+ depends on USB_G_ANDROID
+ help
+ Provides dword aligned for DMA controller.
+
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..0cb5b62 100644..100755
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -12,6 +12,7 @@ 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_AT91) += at91_udc.o
obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o
obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o
@@ -49,6 +50,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 +69,4 @@ 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
diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c
new file mode 100644
index 0000000..1236d6f
--- /dev/null
+++ b/drivers/usb/gadget/android.c
@@ -0,0 +1,1349 @@
+/*
+ * 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 "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_audio_source.c"
+#include "f_mass_storage.c"
+#include "u_serial.c"
+#include "f_acm.c"
+#include "f_adb.c"
+#include "f_mtp.c"
+#include "f_accessory.c"
+#define USB_ETH_RNDIS y
+#include "f_rndis.c"
+#include "rndis.c"
+#include "u_ether.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 userspace */
+#define VENDOR_ID 0x18D1
+#define PRODUCT_ID 0x0001
+
+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 *);
+ /* Optional: called when the function is added the list of
+ * enabled functions */
+ void (*enable)(struct android_usb_function *);
+ /* Optional: called when it is removed */
+ void (*disable)(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;
+ int disable_depth;
+ 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);
+
+/* 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];
+
+/* 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,
+};
+
+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;
+
+ 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);
+ pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]);
+ } else {
+ pr_info("%s: did not send uevent (%d %d %p)\n", __func__,
+ dev->connected, dev->sw_connected, cdev->config);
+ }
+}
+
+static void android_enable(struct android_dev *dev)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ BUG_ON(!mutex_is_locked(&dev->mutex));
+ BUG_ON(!dev->disable_depth);
+
+ if (--dev->disable_depth == 0) {
+ usb_add_config(cdev, &android_config_driver,
+ android_bind_config);
+ usb_gadget_connect(cdev->gadget);
+ }
+}
+
+static void android_disable(struct android_dev *dev)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ BUG_ON(!mutex_is_locked(&dev->mutex));
+
+ if (dev->disable_depth++ == 0) {
+ usb_gadget_disconnect(cdev->gadget);
+ /* Cancel pending control requests */
+ usb_ep_dequeue(cdev->gadget->ep0, cdev->req);
+ usb_remove_config(cdev, &android_config_driver);
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+/* Supported functions initialization */
+
+struct adb_data {
+ bool opened;
+ bool enabled;
+};
+
+static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev)
+{
+ f->config = kzalloc(sizeof(struct adb_data), GFP_KERNEL);
+ if (!f->config)
+ return -ENOMEM;
+
+ return adb_setup();
+}
+
+static void adb_function_cleanup(struct android_usb_function *f)
+{
+ adb_cleanup();
+ kfree(f->config);
+}
+
+static int adb_function_bind_config(struct android_usb_function *f, struct usb_configuration *c)
+{
+ return adb_bind_config(c);
+}
+
+static void adb_android_function_enable(struct android_usb_function *f)
+{
+ struct android_dev *dev = _android_dev;
+ struct adb_data *data = f->config;
+
+ data->enabled = true;
+
+ /* Disable the gadget until adbd is ready */
+ if (!data->opened)
+ android_disable(dev);
+}
+
+static void adb_android_function_disable(struct android_usb_function *f)
+{
+ struct android_dev *dev = _android_dev;
+ struct adb_data *data = f->config;
+
+ data->enabled = false;
+
+ /* Balance the disable that was called in closed_callback */
+ if (!data->opened)
+ android_enable(dev);
+}
+
+static struct android_usb_function adb_function = {
+ .name = "adb",
+ .enable = adb_android_function_enable,
+ .disable = adb_android_function_disable,
+ .init = adb_function_init,
+ .cleanup = adb_function_cleanup,
+ .bind_config = adb_function_bind_config,
+};
+
+static void adb_ready_callback(void)
+{
+ struct android_dev *dev = _android_dev;
+ struct adb_data *data = adb_function.config;
+
+ mutex_lock(&dev->mutex);
+
+ data->opened = true;
+
+ if (data->enabled)
+ android_enable(dev);
+
+ mutex_unlock(&dev->mutex);
+}
+
+static void adb_closed_callback(void)
+{
+ struct android_dev *dev = _android_dev;
+ struct adb_data *data = adb_function.config;
+
+ mutex_lock(&dev->mutex);
+
+ data->opened = false;
+
+ if (data->enabled)
+ android_disable(dev);
+
+ mutex_unlock(&dev->mutex);
+}
+
+
+#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)
+{
+ f->config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL);
+ if (!f->config)
+ return -ENOMEM;
+
+ return gserial_setup(cdev->gadget, MAX_ACM_INSTANCES);
+}
+
+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;
+ }
+
+ pr_info("%s MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", __func__,
+ rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2],
+ rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]);
+
+ 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;
+
+ 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;
+}
+
+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;
+
+ config = kzalloc(sizeof(struct mass_storage_function_config),
+ GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+
+ 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;
+ }
+
+ 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);
+
+static struct device_attribute *mass_storage_function_attributes[] = {
+ &dev_attr_inquiry_string,
+ 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 audio_source_function_init(struct android_usb_function *f,
+ struct usb_composite_dev *cdev)
+{
+ struct audio_source_config *config;
+
+ config = kzalloc(sizeof(struct audio_source_config), GFP_KERNEL);
+ if (!config)
+ return -ENOMEM;
+ config->card = -1;
+ config->device = -1;
+ f->config = config;
+ return 0;
+}
+
+static void audio_source_function_cleanup(struct android_usb_function *f)
+{
+ kfree(f->config);
+}
+
+static int audio_source_function_bind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ struct audio_source_config *config = f->config;
+
+ return audio_source_bind_config(c, config);
+}
+
+static void audio_source_function_unbind_config(struct android_usb_function *f,
+ struct usb_configuration *c)
+{
+ struct audio_source_config *config = f->config;
+
+ config->card = -1;
+ config->device = -1;
+}
+
+static ssize_t audio_source_pcm_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct android_usb_function *f = dev_get_drvdata(dev);
+ struct audio_source_config *config = f->config;
+
+ /* print PCM card and device numbers */
+ return sprintf(buf, "%d %d\n", config->card, config->device);
+}
+
+static DEVICE_ATTR(pcm, S_IRUGO | S_IWUSR, audio_source_pcm_show, NULL);
+
+static struct device_attribute *audio_source_function_attributes[] = {
+ &dev_attr_pcm,
+ NULL
+};
+
+static struct android_usb_function audio_source_function = {
+ .name = "audio_source",
+ .init = audio_source_function_init,
+ .cleanup = audio_source_function_cleanup,
+ .bind_config = audio_source_function_bind_config,
+ .unbind_config = audio_source_function_unbind_config,
+ .attributes = audio_source_function_attributes,
+};
+
+static struct android_usb_function *supported_functions[] = {
+ &adb_function,
+ &acm_function,
+ &mtp_function,
+ &ptp_function,
+ &rndis_function,
+ &mass_storage_function,
+ &accessory_function,
+ &audio_source_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;
+ 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) {
+ 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;
+ 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)
+ 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);
+
+ strncpy(buf, buff, sizeof(buf));
+ b = strim(buf);
+
+ while (b) {
+ name = strsep(&b, ",");
+ if (name) {
+ err = android_enable_function(dev, name);
+ if (err)
+ pr_err("android_usb: Cannot enable '%s'", name);
+ }
+ }
+
+ 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);
+ 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;
+ struct android_usb_function *f;
+ int enabled = 0;
+
+ mutex_lock(&dev->mutex);
+
+ sscanf(buff, "%d", &enabled);
+ if (enabled && !dev->enabled) {
+ /* 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;
+ cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass;
+ cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol;
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ if (f->enable)
+ f->enable(f);
+ }
+ android_enable(dev);
+ dev->enabled = true;
+ } else if (!enabled && dev->enabled) {
+ android_disable(dev);
+ list_for_each_entry(f, &dev->enabled_functions, enabled_list) {
+ if (f->disable)
+ f->disable(f);
+ }
+ 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:
+ 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; \
+ return strlcpy(buffer, buf, sizeof(buffer)); \
+} \
+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;
+
+ 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);
+ strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1);
+
+ 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);
+ }
+
+ dev->cdev = cdev;
+
+ return 0;
+}
+
+static int android_usb_unbind(struct usb_composite_dev *cdev)
+{
+ struct android_dev *dev = _android_dev;
+
+ 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;
+ }
+ }
+
+ /* 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);
+ 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);
+ /* accessory HID support can be active while the
+ accessory function is not actually enabled,
+ so we need to inform it when we are disconnected.
+ */
+ acc_disconnect();
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ dev->connected = 0;
+ schedule_work(&dev->work);
+ 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;
+}
+
+
+static int __init init(void)
+{
+ struct android_dev *dev;
+ int err;
+
+ 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->disable_depth = 1;
+ 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;
+
+ 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..1d88a80 100644
--- a/drivers/usb/gadget/composite.c
+++ b/drivers/usb/gadget/composite.c
@@ -476,6 +476,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 +524,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) {
@@ -562,6 +564,46 @@ done:
return status;
}
+static int unbind_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" */
+ }
+ }
+ 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;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+
+ if (cdev->config == config)
+ reset_config(cdev);
+
+ list_del(&config->list);
+
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ return unbind_config(cdev, config);
+}
+
/*-------------------------------------------------------------------------*/
/* We support strings in multiple languages ... string descriptor zero
@@ -1041,28 +1083,10 @@ 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" */
- }
+ unbind_config(cdev, c);
}
if (composite->unbind)
composite->unbind(cdev);
diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c
index 9b7360f..431cc35 100644
--- a/drivers/usb/gadget/epautoconf.c
+++ b/drivers/usb/gadget/epautoconf.c
@@ -290,6 +290,60 @@ 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;
+ ep = find_ep (gadget, "ep12-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;
+ 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;
+ } 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;
+ ep = find_ep (gadget, "ep10-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ ep = find_ep (gadget, "ep13-bulk");
+ if (ep && ep_matches (gadget, ep, desc))
+ return ep;
+ } else if (USB_ENDPOINT_XFER_ISOC == type) {
+ ep = find_ep(gadget, "ep15-iso");
+ if (ep && ep_matches(gadget, ep, desc))
+ return ep;
+ }
}
/* Second, look at endpoints until an unclaimed one looks usable */
diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c
new file mode 100644
index 0000000..1df88a6
--- /dev/null
+++ b/drivers/usb/gadget/f_accessory.c
@@ -0,0 +1,1176 @@
+/*
+ * 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/hid.h>
+#include <linux/hiddev.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 2
+
+/* 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_hid_dev {
+ struct list_head list;
+ struct hid_device *hid;
+ struct acc_dev *dev;
+ /* accessory defined ID */
+ int id;
+ /* HID report descriptor */
+ u8 *report_desc;
+ /* length of HID report descriptor */
+ int report_desc_len;
+ /* number of bytes of report_desc we have received so far */
+ int report_desc_offset;
+};
+
+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;
+
+ int audio_mode;
+
+ /* 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;
+
+ /* delayed work for handling ACCESSORY_START */
+ struct delayed_work start_work;
+
+ /* worker for registering and unregistering hid devices */
+ struct work_struct hid_work;
+
+ /* list of active HID devices */
+ struct list_head hid_list;
+
+ /* list of new HID devices to register */
+ struct list_head new_hid_list;
+
+ /* list of dead HID devices to unregister */
+ struct list_head dead_hid_list;
+};
+
+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 void acc_complete_set_hid_report_desc(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct acc_hid_dev *hid = req->context;
+ struct acc_dev *dev = hid->dev;
+ int length = req->actual;
+
+ if (req->status != 0) {
+ pr_err("acc_complete_set_hid_report_desc, err %d\n",
+ req->status);
+ return;
+ }
+
+ memcpy(hid->report_desc + hid->report_desc_offset, req->buf, length);
+ hid->report_desc_offset += length;
+ if (hid->report_desc_offset == hid->report_desc_len) {
+ /* After we have received the entire report descriptor
+ * we schedule work to initialize the HID device
+ */
+ schedule_work(&dev->hid_work);
+ }
+}
+
+static void acc_complete_send_hid_event(struct usb_ep *ep,
+ struct usb_request *req)
+{
+ struct acc_hid_dev *hid = req->context;
+ int length = req->actual;
+
+ if (req->status != 0) {
+ pr_err("acc_complete_send_hid_event, err %d\n", req->status);
+ return;
+ }
+
+ hid_report_raw_event(hid->hid, HID_INPUT_REPORT, req->buf, length, 1);
+}
+
+static int acc_hid_parse(struct hid_device *hid)
+{
+ struct acc_hid_dev *hdev = hid->driver_data;
+
+ hid_parse_report(hid, hdev->report_desc, hdev->report_desc_len);
+ return 0;
+}
+
+static int acc_hid_start(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void acc_hid_stop(struct hid_device *hid)
+{
+}
+
+static int acc_hid_open(struct hid_device *hid)
+{
+ return 0;
+}
+
+static void acc_hid_close(struct hid_device *hid)
+{
+}
+
+static struct hid_ll_driver acc_hid_ll_driver = {
+ .parse = acc_hid_parse,
+ .start = acc_hid_start,
+ .stop = acc_hid_stop,
+ .open = acc_hid_open,
+ .close = acc_hid_close,
+};
+
+static struct acc_hid_dev *acc_hid_new(struct acc_dev *dev,
+ int id, int desc_len)
+{
+ struct acc_hid_dev *hdev;
+
+ hdev = kzalloc(sizeof(*hdev), GFP_ATOMIC);
+ if (!hdev)
+ return NULL;
+ hdev->report_desc = kzalloc(desc_len, GFP_ATOMIC);
+ if (!hdev->report_desc) {
+ kfree(hdev);
+ return NULL;
+ }
+ hdev->dev = dev;
+ hdev->id = id;
+ hdev->report_desc_len = desc_len;
+
+ return hdev;
+}
+
+static struct acc_hid_dev *acc_hid_get(struct list_head *list, int id)
+{
+ struct acc_hid_dev *hid;
+
+ list_for_each_entry(hid, list, list) {
+ if (hid->id == id)
+ return hid;
+ }
+ return NULL;
+}
+
+static int acc_register_hid(struct acc_dev *dev, int id, int desc_length)
+{
+ struct acc_hid_dev *hid;
+ unsigned long flags;
+
+ /* report descriptor length must be > 0 */
+ if (desc_length <= 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ /* replace HID if one already exists with this ID */
+ hid = acc_hid_get(&dev->hid_list, id);
+ if (!hid)
+ hid = acc_hid_get(&dev->new_hid_list, id);
+ if (hid)
+ list_move(&hid->list, &dev->dead_hid_list);
+
+ hid = acc_hid_new(dev, id, desc_length);
+ if (!hid) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -ENOMEM;
+ }
+
+ list_add(&hid->list, &dev->new_hid_list);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ /* schedule work to register the HID device */
+ schedule_work(&dev->hid_work);
+ return 0;
+}
+
+static int acc_unregister_hid(struct acc_dev *dev, int id)
+{
+ struct acc_hid_dev *hid;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ hid = acc_hid_get(&dev->hid_list, id);
+ if (!hid)
+ hid = acc_hid_get(&dev->new_hid_list, id);
+ if (!hid) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -EINVAL;
+ }
+
+ list_move(&hid->list, &dev->dead_hid_list);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ schedule_work(&dev->hid_work);
+ return 0;
+}
+
+static int __init 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:
+ pr_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;
+ case ACCESSORY_GET_AUDIO_MODE:
+ return dev->audio_mode;
+ }
+ 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/usb_accessory */
+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 int acc_hid_probe(struct hid_device *hdev,
+ const struct hid_device_id *id)
+{
+ int ret;
+
+ ret = hid_parse(hdev);
+ if (ret)
+ return ret;
+ return hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+}
+
+static struct miscdevice acc_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "usb_accessory",
+ .fops = &acc_fops,
+};
+
+static const struct hid_device_id acc_hid_table[] = {
+ { HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) },
+ { }
+};
+
+static struct hid_driver acc_hid_driver = {
+ .name = "USB accessory",
+ .id_table = acc_hid_table,
+ .probe = acc_hid_probe,
+};
+
+static int acc_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct acc_dev *dev = _acc_dev;
+ int value = -EOPNOTSUPP;
+ struct acc_hid_dev *hid;
+ int offset;
+ 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);
+ unsigned long flags;
+
+/*
+ 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->start_work, msecs_to_jiffies(10));
+ 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_request == ACCESSORY_SET_AUDIO_MODE &&
+ w_index == 0 && w_length == 0) {
+ dev->audio_mode = w_value;
+ value = 0;
+ } else if (b_request == ACCESSORY_REGISTER_HID) {
+ value = acc_register_hid(dev, w_value, w_index);
+ } else if (b_request == ACCESSORY_UNREGISTER_HID) {
+ value = acc_unregister_hid(dev, w_value);
+ } else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) {
+ spin_lock_irqsave(&dev->lock, flags);
+ hid = acc_hid_get(&dev->new_hid_list, w_value);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ if (!hid) {
+ value = -EINVAL;
+ goto err;
+ }
+ offset = w_index;
+ if (offset != hid->report_desc_offset
+ || offset + w_length > hid->report_desc_len) {
+ value = -EINVAL;
+ goto err;
+ }
+ cdev->req->context = hid;
+ cdev->req->complete = acc_complete_set_hid_report_desc;
+ value = w_length;
+ } else if (b_request == ACCESSORY_SEND_HID_EVENT) {
+ spin_lock_irqsave(&dev->lock, flags);
+ hid = acc_hid_get(&dev->hid_list, w_value);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ if (!hid) {
+ value = -EINVAL;
+ goto err;
+ }
+ cdev->req->context = hid;
+ cdev->req->complete = acc_complete_send_hid_event;
+ 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 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;
+ dev->audio_mode = 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__);
+ }
+
+err:
+ 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);
+
+ ret = hid_register_driver(&acc_hid_driver);
+ if (ret)
+ return ret;
+
+ 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
+kill_all_hid_devices(struct acc_dev *dev)
+{
+ struct acc_hid_dev *hid;
+ struct list_head *entry, *temp;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_for_each_safe(entry, temp, &dev->hid_list) {
+ hid = list_entry(entry, struct acc_hid_dev, list);
+ list_del(&hid->list);
+ list_add(&hid->list, &dev->dead_hid_list);
+ }
+ list_for_each_safe(entry, temp, &dev->new_hid_list) {
+ hid = list_entry(entry, struct acc_hid_dev, list);
+ list_del(&hid->list);
+ list_add(&hid->list, &dev->dead_hid_list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ schedule_work(&dev->hid_work);
+}
+
+static void
+acc_hid_unbind(struct acc_dev *dev)
+{
+ hid_unregister_driver(&acc_hid_driver);
+ kill_all_hid_devices(dev);
+}
+
+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);
+
+ acc_hid_unbind(dev);
+}
+
+static void acc_start_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_hid_init(struct acc_hid_dev *hdev)
+{
+ struct hid_device *hid;
+ int ret;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ return PTR_ERR(hid);
+
+ hid->ll_driver = &acc_hid_ll_driver;
+ hid->dev.parent = acc_device.this_device;
+
+ hid->bus = BUS_USB;
+ hid->vendor = HID_ANY_ID;
+ hid->product = HID_ANY_ID;
+ hid->driver_data = hdev;
+ ret = hid_add_device(hid);
+ if (ret) {
+ pr_err("can't add hid device: %d\n", ret);
+ hid_destroy_device(hid);
+ return ret;
+ }
+
+ hdev->hid = hid;
+ return 0;
+}
+
+static void acc_hid_delete(struct acc_hid_dev *hid)
+{
+ kfree(hid->report_desc);
+ kfree(hid);
+}
+
+static void acc_hid_work(struct work_struct *data)
+{
+ struct acc_dev *dev = _acc_dev;
+ struct list_head *entry, *temp;
+ struct acc_hid_dev *hid;
+ struct list_head new_list, dead_list;
+ unsigned long flags;
+
+ INIT_LIST_HEAD(&new_list);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ /* copy hids that are ready for initialization to new_list */
+ list_for_each_safe(entry, temp, &dev->new_hid_list) {
+ hid = list_entry(entry, struct acc_hid_dev, list);
+ if (hid->report_desc_offset == hid->report_desc_len)
+ list_move(&hid->list, &new_list);
+ }
+
+ if (list_empty(&dev->dead_hid_list)) {
+ INIT_LIST_HEAD(&dead_list);
+ } else {
+ /* move all of dev->dead_hid_list to dead_list */
+ dead_list.prev = dev->dead_hid_list.prev;
+ dead_list.next = dev->dead_hid_list.next;
+ dead_list.next->prev = &dead_list;
+ dead_list.prev->next = &dead_list;
+ INIT_LIST_HEAD(&dev->dead_hid_list);
+ }
+
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ /* register new HID devices */
+ list_for_each_safe(entry, temp, &new_list) {
+ hid = list_entry(entry, struct acc_hid_dev, list);
+ if (acc_hid_init(hid)) {
+ pr_err("can't add HID device %p\n", hid);
+ acc_hid_delete(hid);
+ } else {
+ spin_lock_irqsave(&dev->lock, flags);
+ list_move(&hid->list, &dev->hid_list);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ }
+ }
+
+ /* remove dead HID devices */
+ list_for_each_safe(entry, temp, &dead_list) {
+ hid = list_entry(entry, struct acc_hid_dev, list);
+ list_del(&hid->list);
+ if (hid->hid)
+ hid_destroy_device(hid->hid);
+ acc_hid_delete(hid);
+ }
+}
+
+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_LIST_HEAD(&dev->hid_list);
+ INIT_LIST_HEAD(&dev->new_hid_list);
+ INIT_LIST_HEAD(&dev->dead_hid_list);
+ INIT_DELAYED_WORK(&dev->start_work, acc_start_work);
+ INIT_WORK(&dev->hid_work, acc_hid_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);
+ pr_err("USB accessory gadget driver failed to initialize\n");
+ return ret;
+}
+
+static void acc_disconnect(void)
+{
+ /* unregister all HID devices if USB is disconnected */
+ kill_all_hid_devices(_acc_dev);
+}
+
+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..68b1a8e 100644
--- a/drivers/usb/gadget/f_acm.c
+++ b/drivers/usb/gadget/f_acm.c
@@ -405,10 +405,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 +418,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
@@ -697,6 +697,7 @@ 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);
}
@@ -768,7 +769,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..1c0166c
--- /dev/null
+++ b/drivers/usb/gadget/f_adb.c
@@ -0,0 +1,614 @@
+/*
+ * 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_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 void adb_ready_callback(void);
+static void adb_closed_callback(void);
+
+/* 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;
+ 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)
+{
+ pr_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;
+
+ adb_ready_callback();
+
+ return 0;
+}
+
+static int adb_release(struct inode *ip, struct file *fp)
+{
+ pr_info("adb_release\n");
+
+ adb_closed_callback();
+
+ 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;
+ }
+
+ 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_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.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_audio_source.c b/drivers/usb/gadget/f_audio_source.c
new file mode 100644
index 0000000..c5dbf71
--- /dev/null
+++ b/drivers/usb/gadget/f_audio_source.c
@@ -0,0 +1,821 @@
+/*
+ * Gadget Function Driver for USB audio source device
+ *
+ * Copyright (C) 2012 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/device.h>
+#include <linux/usb/audio.h>
+#include <linux/wait.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+
+#define SAMPLE_RATE 44100
+#define FRAMES_PER_MSEC (SAMPLE_RATE / 1000)
+
+#define IN_EP_MAX_PACKET_SIZE 384
+
+/* Number of requests to allocate */
+#define IN_EP_REQ_COUNT 4
+
+#define AUDIO_AC_INTERFACE 0
+#define AUDIO_AS_INTERFACE 1
+#define AUDIO_NUM_INTERFACES 2
+
+/* B.3.1 Standard AC Interface Descriptor */
+static struct usb_interface_descriptor ac_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
+};
+
+DECLARE_UAC_AC_HEADER_DESCRIPTOR(2);
+
+#define UAC_DT_AC_HEADER_LENGTH UAC_DT_AC_HEADER_SIZE(AUDIO_NUM_INTERFACES)
+/* 1 input terminal, 1 output terminal and 1 feature unit */
+#define UAC_DT_TOTAL_LENGTH (UAC_DT_AC_HEADER_LENGTH \
+ + UAC_DT_INPUT_TERMINAL_SIZE + UAC_DT_OUTPUT_TERMINAL_SIZE \
+ + UAC_DT_FEATURE_UNIT_SIZE(0))
+/* B.3.2 Class-Specific AC Interface Descriptor */
+static struct uac1_ac_header_descriptor_2 ac_header_desc = {
+ .bLength = UAC_DT_AC_HEADER_LENGTH,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_HEADER,
+ .bcdADC = __constant_cpu_to_le16(0x0100),
+ .wTotalLength = __constant_cpu_to_le16(UAC_DT_TOTAL_LENGTH),
+ .bInCollection = AUDIO_NUM_INTERFACES,
+ .baInterfaceNr = {
+ [0] = AUDIO_AC_INTERFACE,
+ [1] = AUDIO_AS_INTERFACE,
+ }
+};
+
+#define INPUT_TERMINAL_ID 1
+static struct uac_input_terminal_descriptor input_terminal_desc = {
+ .bLength = UAC_DT_INPUT_TERMINAL_SIZE,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_INPUT_TERMINAL,
+ .bTerminalID = INPUT_TERMINAL_ID,
+ .wTerminalType = UAC_INPUT_TERMINAL_MICROPHONE,
+ .bAssocTerminal = 0,
+ .wChannelConfig = 0x3,
+};
+
+DECLARE_UAC_FEATURE_UNIT_DESCRIPTOR(0);
+
+#define FEATURE_UNIT_ID 2
+static struct uac_feature_unit_descriptor_0 feature_unit_desc = {
+ .bLength = UAC_DT_FEATURE_UNIT_SIZE(0),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_FEATURE_UNIT,
+ .bUnitID = FEATURE_UNIT_ID,
+ .bSourceID = INPUT_TERMINAL_ID,
+ .bControlSize = 2,
+};
+
+#define OUTPUT_TERMINAL_ID 3
+static struct uac1_output_terminal_descriptor output_terminal_desc = {
+ .bLength = UAC_DT_OUTPUT_TERMINAL_SIZE,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
+ .bTerminalID = OUTPUT_TERMINAL_ID,
+ .wTerminalType = UAC_TERMINAL_STREAMING,
+ .bAssocTerminal = FEATURE_UNIT_ID,
+ .bSourceID = FEATURE_UNIT_ID,
+};
+
+/* B.4.1 Standard AS Interface Descriptor */
+static struct usb_interface_descriptor as_interface_alt_0_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bAlternateSetting = 0,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+static struct usb_interface_descriptor as_interface_alt_1_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bAlternateSetting = 1,
+ .bNumEndpoints = 1,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
+};
+
+/* B.4.2 Class-Specific AS Interface Descriptor */
+static struct uac1_as_header_descriptor as_header_desc = {
+ .bLength = UAC_DT_AS_HEADER_SIZE,
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_AS_GENERAL,
+ .bTerminalLink = INPUT_TERMINAL_ID,
+ .bDelay = 1,
+ .wFormatTag = UAC_FORMAT_TYPE_I_PCM,
+};
+
+DECLARE_UAC_FORMAT_TYPE_I_DISCRETE_DESC(1);
+
+static struct uac_format_type_i_discrete_descriptor_1 as_type_i_desc = {
+ .bLength = UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1),
+ .bDescriptorType = USB_DT_CS_INTERFACE,
+ .bDescriptorSubtype = UAC_FORMAT_TYPE,
+ .bFormatType = UAC_FORMAT_TYPE_I,
+ .bSubframeSize = 2,
+ .bBitResolution = 16,
+ .bSamFreqType = 1,
+};
+
+/* Standard ISO IN Endpoint Descriptor for highspeed */
+static struct usb_endpoint_descriptor hs_as_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_SYNC_SYNC
+ | USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
+ .bInterval = 4, /* poll 1 per millisecond */
+};
+
+/* Standard ISO IN Endpoint Descriptor for highspeed */
+static struct usb_endpoint_descriptor fs_as_in_ep_desc = {
+ .bLength = USB_DT_ENDPOINT_AUDIO_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_SYNC_SYNC
+ | USB_ENDPOINT_XFER_ISOC,
+ .wMaxPacketSize = __constant_cpu_to_le16(IN_EP_MAX_PACKET_SIZE),
+ .bInterval = 1, /* poll 1 per millisecond */
+};
+
+/* Class-specific AS ISO OUT Endpoint Descriptor */
+static struct uac_iso_endpoint_descriptor as_iso_in_desc = {
+ .bLength = UAC_ISO_ENDPOINT_DESC_SIZE,
+ .bDescriptorType = USB_DT_CS_ENDPOINT,
+ .bDescriptorSubtype = UAC_EP_GENERAL,
+ .bmAttributes = 1,
+ .bLockDelayUnits = 1,
+ .wLockDelay = __constant_cpu_to_le16(1),
+};
+
+static struct usb_descriptor_header *hs_audio_desc[] = {
+ (struct usb_descriptor_header *)&ac_interface_desc,
+ (struct usb_descriptor_header *)&ac_header_desc,
+
+ (struct usb_descriptor_header *)&input_terminal_desc,
+ (struct usb_descriptor_header *)&output_terminal_desc,
+ (struct usb_descriptor_header *)&feature_unit_desc,
+
+ (struct usb_descriptor_header *)&as_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_header_desc,
+
+ (struct usb_descriptor_header *)&as_type_i_desc,
+
+ (struct usb_descriptor_header *)&hs_as_in_ep_desc,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *fs_audio_desc[] = {
+ (struct usb_descriptor_header *)&ac_interface_desc,
+ (struct usb_descriptor_header *)&ac_header_desc,
+
+ (struct usb_descriptor_header *)&input_terminal_desc,
+ (struct usb_descriptor_header *)&output_terminal_desc,
+ (struct usb_descriptor_header *)&feature_unit_desc,
+
+ (struct usb_descriptor_header *)&as_interface_alt_0_desc,
+ (struct usb_descriptor_header *)&as_interface_alt_1_desc,
+ (struct usb_descriptor_header *)&as_header_desc,
+
+ (struct usb_descriptor_header *)&as_type_i_desc,
+
+ (struct usb_descriptor_header *)&fs_as_in_ep_desc,
+ (struct usb_descriptor_header *)&as_iso_in_desc,
+ NULL,
+};
+
+static struct snd_pcm_hardware audio_hw_info = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_BATCH |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .channels_min = 2,
+ .channels_max = 2,
+ .rate_min = SAMPLE_RATE,
+ .rate_max = SAMPLE_RATE,
+
+ .buffer_bytes_max = 1024 * 1024,
+ .period_bytes_min = 64,
+ .period_bytes_max = 512 * 1024,
+ .periods_min = 2,
+ .periods_max = 1024,
+};
+
+/*-------------------------------------------------------------------------*/
+
+struct audio_source_config {
+ int card;
+ int device;
+};
+
+struct audio_dev {
+ struct usb_function func;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ struct snd_pcm_substream *substream;
+
+ struct list_head idle_reqs;
+ struct usb_ep *in_ep;
+
+ spinlock_t lock;
+
+ /* beginning, end and current position in our buffer */
+ void *buffer_start;
+ void *buffer_end;
+ void *buffer_pos;
+
+ /* byte size of a "period" */
+ unsigned int period;
+ /* bytes sent since last call to snd_pcm_period_elapsed */
+ unsigned int period_offset;
+ /* time we started playing */
+ ktime_t start_time;
+ /* number of frames sent since start_time */
+ s64 frames_sent;
+};
+
+static inline struct audio_dev *func_to_audio(struct usb_function *f)
+{
+ return container_of(f, struct audio_dev, func);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_request *audio_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!req)
+ return NULL;
+
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+ req->length = buffer_size;
+ return req;
+}
+
+static void audio_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static void audio_req_put(struct audio_dev *audio, struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&audio->lock, flags);
+ list_add_tail(&req->list, &audio->idle_reqs);
+ spin_unlock_irqrestore(&audio->lock, flags);
+}
+
+static struct usb_request *audio_req_get(struct audio_dev *audio)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&audio->lock, flags);
+ if (list_empty(&audio->idle_reqs)) {
+ req = 0;
+ } else {
+ req = list_first_entry(&audio->idle_reqs, struct usb_request,
+ list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&audio->lock, flags);
+ return req;
+}
+
+/* send the appropriate number of packets to match our bitrate */
+static void audio_send(struct audio_dev *audio)
+{
+ struct snd_pcm_runtime *runtime;
+ struct usb_request *req;
+ int length, length1, length2, ret;
+ s64 msecs;
+ s64 frames;
+ ktime_t now;
+
+ /* audio->substream will be null if we have been closed */
+ if (!audio->substream)
+ return;
+ /* audio->buffer_pos will be null if we have been stopped */
+ if (!audio->buffer_pos)
+ return;
+
+ runtime = audio->substream->runtime;
+
+ /* compute number of frames to send */
+ now = ktime_get();
+ msecs = ktime_to_ns(now) - ktime_to_ns(audio->start_time);
+ do_div(msecs, 1000000);
+ frames = msecs * SAMPLE_RATE;
+ do_div(frames, 1000);
+
+ /* Readjust our frames_sent if we fall too far behind.
+ * If we get too far behind it is better to drop some frames than
+ * to keep sending data too fast in an attempt to catch up.
+ */
+ if (frames - audio->frames_sent > 10 * FRAMES_PER_MSEC)
+ audio->frames_sent = frames - FRAMES_PER_MSEC;
+
+ frames -= audio->frames_sent;
+
+ /* We need to send something to keep the pipeline going */
+ if (frames <= 0)
+ frames = FRAMES_PER_MSEC;
+
+ while (frames > 0) {
+ req = audio_req_get(audio);
+ if (!req)
+ break;
+
+ length = frames_to_bytes(runtime, frames);
+ if (length > IN_EP_MAX_PACKET_SIZE)
+ length = IN_EP_MAX_PACKET_SIZE;
+
+ if (audio->buffer_pos + length > audio->buffer_end)
+ length1 = audio->buffer_end - audio->buffer_pos;
+ else
+ length1 = length;
+ memcpy(req->buf, audio->buffer_pos, length1);
+ if (length1 < length) {
+ /* Wrap around and copy remaining length
+ * at beginning of buffer.
+ */
+ length2 = length - length1;
+ memcpy(req->buf + length1, audio->buffer_start,
+ length2);
+ audio->buffer_pos = audio->buffer_start + length2;
+ } else {
+ audio->buffer_pos += length1;
+ if (audio->buffer_pos >= audio->buffer_end)
+ audio->buffer_pos = audio->buffer_start;
+ }
+
+ req->length = length;
+ ret = usb_ep_queue(audio->in_ep, req, GFP_ATOMIC);
+ if (ret < 0) {
+ pr_err("usb_ep_queue failed ret: %d\n", ret);
+ audio_req_put(audio, req);
+ break;
+ }
+
+ frames -= bytes_to_frames(runtime, length);
+ audio->frames_sent += bytes_to_frames(runtime, length);
+ }
+}
+
+static void audio_control_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ /* nothing to do here */
+}
+
+static void audio_data_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct audio_dev *audio = req->context;
+
+ pr_debug("audio_data_complete req->status %d req->actual %d\n",
+ req->status, req->actual);
+
+ audio_req_put(audio, req);
+
+ if (!audio->buffer_start || req->status)
+ return;
+
+ audio->period_offset += req->actual;
+ if (audio->period_offset >= audio->period) {
+ snd_pcm_period_elapsed(audio->substream);
+ audio->period_offset = 0;
+ }
+ audio_send(audio);
+}
+
+static int audio_set_endpoint_req(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ int value = -EOPNOTSUPP;
+ u16 ep = le16_to_cpu(ctrl->wIndex);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+
+ pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+ ctrl->bRequest, w_value, len, ep);
+
+ switch (ctrl->bRequest) {
+ case UAC_SET_CUR:
+ case UAC_SET_MIN:
+ case UAC_SET_MAX:
+ case UAC_SET_RES:
+ value = len;
+ break;
+ default:
+ break;
+ }
+
+ return value;
+}
+
+static int audio_get_endpoint_req(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int value = -EOPNOTSUPP;
+ u8 ep = ((le16_to_cpu(ctrl->wIndex) >> 8) & 0xFF);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u8 *buf = cdev->req->buf;
+
+ pr_debug("bRequest 0x%x, w_value 0x%04x, len %d, endpoint %d\n",
+ ctrl->bRequest, w_value, len, ep);
+
+ if (w_value == UAC_EP_CS_ATTR_SAMPLE_RATE << 8) {
+ switch (ctrl->bRequest) {
+ case UAC_GET_CUR:
+ case UAC_GET_MIN:
+ case UAC_GET_MAX:
+ case UAC_GET_RES:
+ /* return our sample rate */
+ buf[0] = (u8)SAMPLE_RATE;
+ buf[1] = (u8)(SAMPLE_RATE >> 8);
+ buf[2] = (u8)(SAMPLE_RATE >> 16);
+ value = 3;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return value;
+}
+
+static int
+audio_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ 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; interface
+ * activation uses set_alt().
+ */
+ switch (ctrl->bRequestType) {
+ case USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+ value = audio_set_endpoint_req(f, ctrl);
+ break;
+
+ case USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT:
+ value = audio_get_endpoint_req(f, ctrl);
+ break;
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ pr_debug("audio 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;
+ req->complete = audio_control_complete;
+ value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (value < 0)
+ pr_err("audio response on err %d\n", value);
+ }
+
+ /* device either stalls (value < 0) or reports success */
+ return value;
+}
+
+static int audio_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct audio_dev *audio = func_to_audio(f);
+
+ pr_debug("audio_set_alt intf %d, alt %d\n", intf, alt);
+ usb_ep_enable(audio->in_ep, &fs_as_in_ep_desc);
+ return 0;
+}
+
+static void audio_disable(struct usb_function *f)
+{
+ struct audio_dev *audio = func_to_audio(f);
+
+ pr_debug("audio_disable\n");
+ usb_ep_disable(audio->in_ep);
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void audio_build_desc(struct audio_dev *audio)
+{
+ u8 *sam_freq;
+ int rate;
+
+ /* Set channel numbers */
+ input_terminal_desc.bNrChannels = 2;
+ as_type_i_desc.bNrChannels = 2;
+
+ /* Set sample rates */
+ rate = SAMPLE_RATE;
+ sam_freq = as_type_i_desc.tSamFreq[0];
+ memcpy(sam_freq, &rate, 3);
+}
+
+/* audio function driver setup/binding */
+static int
+audio_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct audio_dev *audio = func_to_audio(f);
+ int status;
+ struct usb_ep *ep;
+ struct usb_request *req;
+ int i;
+
+ audio_build_desc(audio);
+
+ /* allocate instance-specific interface IDs, and patch descriptors */
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ ac_interface_desc.bInterfaceNumber = status;
+
+ status = usb_interface_id(c, f);
+ if (status < 0)
+ goto fail;
+ as_interface_alt_0_desc.bInterfaceNumber = status;
+ as_interface_alt_1_desc.bInterfaceNumber = status;
+
+ status = -ENODEV;
+
+ /* allocate our endpoint */
+ ep = usb_ep_autoconfig(cdev->gadget, &fs_as_in_ep_desc);
+ if (!ep)
+ goto fail;
+ audio->in_ep = ep;
+ ep->driver_data = audio; /* claim */
+
+ if (gadget_is_dualspeed(c->cdev->gadget))
+ hs_as_in_ep_desc.bEndpointAddress =
+ fs_as_in_ep_desc.bEndpointAddress;
+
+ f->descriptors = fs_audio_desc;
+ f->hs_descriptors = hs_audio_desc;
+
+ for (i = 0, status = 0; i < IN_EP_REQ_COUNT && status == 0; i++) {
+ req = audio_request_new(ep, IN_EP_MAX_PACKET_SIZE);
+ if (req) {
+ req->context = audio;
+ req->complete = audio_data_complete;
+ audio_req_put(audio, req);
+ } else
+ status = -ENOMEM;
+ }
+
+fail:
+ return status;
+}
+
+static void
+audio_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct audio_dev *audio = func_to_audio(f);
+ struct usb_request *req;
+
+ while ((req = audio_req_get(audio)))
+ audio_request_free(req, audio->in_ep);
+
+ snd_card_free_when_closed(audio->card);
+ audio->card = NULL;
+ audio->pcm = NULL;
+ audio->substream = NULL;
+ audio->in_ep = NULL;
+}
+
+static void audio_pcm_playback_start(struct audio_dev *audio)
+{
+ audio->start_time = ktime_get();
+ audio->frames_sent = 0;
+ audio_send(audio);
+}
+
+static void audio_pcm_playback_stop(struct audio_dev *audio)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&audio->lock, flags);
+ audio->buffer_start = 0;
+ audio->buffer_end = 0;
+ audio->buffer_pos = 0;
+ spin_unlock_irqrestore(&audio->lock, flags);
+}
+
+static int audio_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct audio_dev *audio = substream->private_data;
+
+ runtime->private_data = audio;
+ runtime->hw = audio_hw_info;
+ snd_pcm_limit_hw_rates(runtime);
+ runtime->hw.channels_max = 2;
+
+ audio->substream = substream;
+ return 0;
+}
+
+static int audio_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct audio_dev *audio = substream->private_data;
+ unsigned long flags;
+
+ spin_lock_irqsave(&audio->lock, flags);
+ audio->substream = NULL;
+ spin_unlock_irqrestore(&audio->lock, flags);
+
+ return 0;
+}
+
+static int audio_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ unsigned int channels = params_channels(params);
+ unsigned int rate = params_rate(params);
+
+ if (rate != SAMPLE_RATE)
+ return -EINVAL;
+ if (channels != 2)
+ return -EINVAL;
+
+ return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+ params_buffer_bytes(params));
+}
+
+static int audio_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int audio_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct audio_dev *audio = runtime->private_data;
+
+ audio->period = snd_pcm_lib_period_bytes(substream);
+ audio->period_offset = 0;
+ audio->buffer_start = runtime->dma_area;
+ audio->buffer_end = audio->buffer_start
+ + snd_pcm_lib_buffer_bytes(substream);
+ audio->buffer_pos = audio->buffer_start;
+
+ return 0;
+}
+
+static snd_pcm_uframes_t audio_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct audio_dev *audio = runtime->private_data;
+ ssize_t bytes = audio->buffer_pos - audio->buffer_start;
+
+ /* return offset of next frame to fill in our buffer */
+ return bytes_to_frames(runtime, bytes);
+}
+
+static int audio_pcm_playback_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct audio_dev *audio = substream->runtime->private_data;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ audio_pcm_playback_start(audio);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ audio_pcm_playback_stop(audio);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static struct audio_dev _audio_dev = {
+ .func = {
+ .name = "audio_source",
+ .bind = audio_bind,
+ .unbind = audio_unbind,
+ .set_alt = audio_set_alt,
+ .setup = audio_setup,
+ .disable = audio_disable,
+ },
+ .lock = __SPIN_LOCK_UNLOCKED(_audio_dev.lock),
+ .idle_reqs = LIST_HEAD_INIT(_audio_dev.idle_reqs),
+};
+
+static struct snd_pcm_ops audio_playback_ops = {
+ .open = audio_pcm_open,
+ .close = audio_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = audio_pcm_hw_params,
+ .hw_free = audio_pcm_hw_free,
+ .prepare = audio_pcm_prepare,
+ .trigger = audio_pcm_playback_trigger,
+ .pointer = audio_pcm_pointer,
+};
+
+int audio_source_bind_config(struct usb_configuration *c,
+ struct audio_source_config *config)
+{
+ struct audio_dev *audio;
+ struct snd_card *card;
+ struct snd_pcm *pcm;
+ int err;
+
+ config->card = -1;
+ config->device = -1;
+
+ audio = &_audio_dev;
+
+ err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+ THIS_MODULE, 0, &card);
+ if (err)
+ return err;
+
+ snd_card_set_dev(card, &c->cdev->gadget->dev);
+
+ err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm);
+ if (err)
+ goto pcm_fail;
+ pcm->private_data = audio;
+ pcm->info_flags = 0;
+ audio->pcm = pcm;
+
+ strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name));
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops);
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ NULL, 0, 64 * 1024);
+
+ strlcpy(card->driver, "audio_source", sizeof(card->driver));
+ strlcpy(card->shortname, card->driver, sizeof(card->shortname));
+ strlcpy(card->longname, "USB accessory audio source",
+ sizeof(card->longname));
+
+ err = snd_card_register(card);
+ if (err)
+ goto register_fail;
+
+ err = usb_add_function(c, &audio->func);
+ if (err)
+ goto add_fail;
+
+ config->card = pcm->card->number;
+ config->device = pcm->device;
+ audio->card = card;
+ return 0;
+
+add_fail:
+register_fail:
+pcm_fail:
+ snd_card_free(audio->card);
+ return err;
+}
diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c
index 3bbdc9a..7405647 100644
--- a/drivers/usb/gadget/f_mass_storage.c
+++ b/drivers/usb/gadget/f_mass_storage.c
@@ -908,11 +908,13 @@ static int do_write(struct fsg_common *common)
curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
return -EINVAL;
}
+#ifndef CONFIG_USB_ANDROID_MASS_STORAGE
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);
}
+#endif
}
if (lba >= curlun->num_sectors) {
curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
@@ -3052,7 +3054,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..2829231
--- /dev/null
+++ b/drivers/usb/gadget/f_mtp.c
@@ -0,0 +1,1267 @@
+/*
+ * 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_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_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;
+ }
+
+ 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_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);
+ 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_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;
+ } else {
+ 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.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_rndis.c b/drivers/usb/gadget/f_rndis.c
index fa12ec8..96adf45 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,8 +86,11 @@ 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;
@@ -187,12 +190,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 */
};
@@ -486,10 +488,10 @@ 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);
}
+ 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,11 +505,11 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
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);
}
+ 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;
@@ -706,12 +708,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
@@ -786,7 +785,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;
@@ -794,14 +794,14 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN])
if (!can_support_rndis(c) || !ethaddr)
return -EINVAL;
+ /* setup RNDIS itself */
+ status = rndis_init();
+ if (status < 0)
+ return status;
+
/* 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)
@@ -831,6 +831,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/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
index bcdac7c..839c9a3 100644
--- a/drivers/usb/gadget/gadget_chips.h
+++ b/drivers/usb/gadget/gadget_chips.h
@@ -70,6 +70,12 @@
#define gadget_is_s3c2410(g) 0
#endif
+#if 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_GADGET_AT91
#define gadget_is_at91(g) !strcmp("at91_udc", (g)->name)
#else
@@ -223,6 +229,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_s3c(gadget))
+ return 0x31;
return -ENOENT;
}
diff --git a/drivers/usb/gadget/rndis.c b/drivers/usb/gadget/rndis.c
index d3cdffe..bbfbde7 100644
--- a/drivers/usb/gadget/rndis.c
+++ b/drivers/usb/gadget/rndis.c
@@ -1147,11 +1147,15 @@ static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS];
#endif /* CONFIG_USB_GADGET_DEBUG_FILES */
+static bool rndis_initialized;
int rndis_init(void)
{
u8 i;
+ if (rndis_initialized)
+ return 0;
+
for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
char name [20];
@@ -1178,6 +1182,7 @@ int rndis_init(void)
INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue));
}
+ rndis_initialized = true;
return 0;
}
@@ -1186,7 +1191,13 @@ void rndis_exit(void)
#ifdef CONFIG_USB_GADGET_DEBUG_FILES
u8 i;
char name[20];
+#endif
+ if (!rndis_initialized)
+ return;
+ rndis_initialized = false;
+
+#ifdef CONFIG_USB_GADGET_DEBUG_FILES
for (i = 0; i < RNDIS_MAX_CONFIGS; i++) {
sprintf(name, NAME_TEMPLATE, i);
remove_proc_entry(name, NULL);
diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h
new file mode 100644
index 0000000..a776167
--- /dev/null
+++ b/drivers/usb/gadget/s3c_udc.h
@@ -0,0 +1,164 @@
+/*
+ * 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 <asm/byteorder.h>
+#include <asm/dma.h>
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <asm/unaligned.h>
+/* #include <asm/hardware.h> */
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.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_ARCH_S5PV210)
+#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
+
+
+typedef enum ep_type {
+ ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt, ep_isochronous
+} 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;
+};
+
+struct s3c_udc {
+ struct usb_gadget gadget;
+ struct usb_gadget_driver *driver;
+ /* struct device *dev; */
+ struct platform_device *dev;
+ spinlock_t lock;
+ u16 status;
+ int ep0state;
+ struct s3c_ep ep[S3C_MAX_ENDPOINTS];
+
+ unsigned char usb_address;
+
+ unsigned req_pending:1, req_std:1, req_config:1;
+
+ struct regulator *udc_vcc_d, *udc_vcc_a;
+ int udc_enabled;
+ int soft_disconnected;
+};
+
+extern struct s3c_udc *the_controller;
+extern void otg_phy_init(void);
+extern void otg_phy_off(void);
+extern struct usb_ctrlrequest usb_ctrl;
+extern struct i2c_driver fsa9480_i2c_driver;
+
+#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)
+
+#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG
+#define USB_OTG_DRIVER_S3CHS 1
+#define USB_OTG_DRIVER_S3CFSLS 2
+#define USB_OTG_DRIVER_S3C USB_OTG_DRIVER_S3CHS | USB_OTG_DRIVER_S3CFSLS
+#define USB_OTG_DRIVER_DWC 4
+#endif
+extern atomic_t g_OtgHostMode; // actual mode: client (0) or host (1)
+extern atomic_t g_OtgOperationMode; // operation mode: 'c'lient, 'h'ost, 'o'tg or 'a'uto-host
+extern atomic_t g_OtgLastCableState; // last cable state: detached (0), client attached (1), otg attached (2)
+extern atomic_t g_OtgDriver; // driver to use: 0: S3C High-speed, 1: S3C Low-speed/Full-speed, 2: DWC
+#ifdef CONFIG_USB_S3C_OTG_HOST
+extern struct platform_driver s5pc110_otg_driver;
+#endif
+#ifdef CONFIG_USB_DWC_OTG
+extern struct platform_driver dwc_otg_driver;
+#endif
+extern int s3c_is_otgmode(void);
+extern int s3c_get_drivermode(void);
+#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..6170aba
--- /dev/null
+++ b/drivers/usb/gadget/s3c_udc_otg.c
@@ -0,0 +1,1544 @@
+/*
+ * 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/platform_device.h>
+#include <linux/clk.h>
+#include <mach/map.h>
+#include <plat/regs-otg.h>
+#include <linux/i2c.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <asm/uaccess.h>
+#if defined(CONFIG_USB_GADGET_S3C_OTGD_DMA_MODE) /* DMA mode */
+#define OTG_DMA_MODE 1
+
+#elif defined(CONFIG_USB_GADGET_S3C_OTGD_SLAVE_MODE) /* Slave mode */
+#define OTG_DMA_MODE 0
+#error " Slave Mode is not implemented to do later"
+#else
+#error " Unknown S3C OTG operation mode, Select a correct operation mode"
+#endif
+
+#define DEBUG_S3C_UDC_SETUP
+#undef DEBUG_S3C_UDC_EP0
+#define DEBUG_S3C_UDC_ISR
+#undef DEBUG_S3C_UDC_OUT_EP
+#undef DEBUG_S3C_UDC_IN_EP
+#undef DEBUG_S3C_UDC
+
+#define EP0_CON 0
+#define EP1_OUT 1
+#define EP2_IN 2
+#define EP3_IN 3
+#define EP_MASK 0xF
+
+#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...) pr_debug(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...) pr_debug(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 Device Controller Driver, (c) 2008-2009 Samsung Electronics"
+#define DRIVER_VERSION "15 March 2009"
+
+struct s3c_udc *the_controller;
+
+static struct clk *otg_clock;
+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_set_address(struct s3c_udc *dev, unsigned char address);
+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 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 */
+
+#if OTG_DMA_MODE /* DMA Mode */
+#include "s3c_udc_otg_xfer_dma.c"
+
+#else /* Slave Mode */
+#include "s3c_udc_otg_xfer_slave.c"
+#endif
+
+/*
+ * udc_disable - disable USB device controller
+ */
+static void udc_disable(struct s3c_udc *dev)
+{
+ DEBUG_SETUP("%s: %p\n", __func__, dev);
+
+ udc_set_address(dev, 0);
+
+ dev->ep0state = WAIT_FOR_SETUP;
+ dev->gadget.speed = USB_SPEED_UNKNOWN;
+ dev->usb_address = 0;
+
+ otg_phy_off();
+}
+
+/*
+ * 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)
+{
+ unsigned long flags;
+
+ DEBUG_SETUP("%s: %p\n", __func__, dev);
+
+ otg_phy_init();
+
+ spin_lock_irqsave(&dev->lock, flags);
+ reconfig_usbd();
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ DEBUG_SETUP("S3C USB 2.0 OTG Controller Core Initialized : 0x%x\n",
+ readl(S3C_UDC_OTG_GINTMSK));
+
+ dev->gadget.speed = USB_SPEED_UNKNOWN;
+
+ 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);
+
+/*
+ * adb composite fail to !driver->unbind in composite.c as below
+ * static struct usb_gadget_driver composite_driver = {
+ * .speed = USB_SPEED_HIGH,
+ * .bind = composite_bind,
+ * .unbind = __exit_p(composite_unbind),
+ */
+ 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;
+ /* initialize device status as self-powered.*/
+ dev->status = 1 << USB_DEVICE_SELF_POWERED;
+ dev->gadget.dev.driver = &driver->driver;
+ retval = device_add(&dev->gadget.dev);
+
+ if (retval) {
+ 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;
+ }
+
+ enable_irq(IRQ_OTG);
+
+ retval = usb_gadget_vbus_connect(&dev->gadget);
+ if (retval < 0)
+ dev_dbg(&dev->gadget.dev, "%s vbus connect %d\n",
+ __func__, retval);
+
+ if (!(readl(S3C_UDC_OTG_GOTGCTL) & B_SESSION_VALID)) {
+ retval = usb_gadget_vbus_disconnect(&dev->gadget);
+ if (retval < 0) {
+ dev_dbg(&dev->gadget.dev, "%s vbus disconnect %d\n",
+ __func__, retval);
+ }
+ }
+
+ printk(KERN_DEBUG "Registered gadget driver '%s'\n",
+ driver->driver.name);
+
+ 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);
+
+ disable_irq(IRQ_OTG);
+
+ printk(KERN_DEBUG "Unregistered gadget driver '%s'\n",
+ driver->driver.name);
+
+ udc_disable(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL(usb_gadget_unregister_driver);
+
+static int s3c_udc_power(struct s3c_udc *dev, char en)
+{
+ pr_debug("%s : %s\n", __func__, en ? "ON" : "OFF");
+
+ if (en) {
+ regulator_enable(dev->udc_vcc_d);
+ regulator_enable(dev->udc_vcc_a);
+ } else {
+ regulator_disable(dev->udc_vcc_d);
+ regulator_disable(dev->udc_vcc_a);
+ }
+
+ return 0;
+}
+
+int s3c_vbus_enable(struct usb_gadget *gadget, int enable)
+{
+ unsigned long flags;
+ struct s3c_udc *dev = the_controller;
+
+ if (dev->udc_enabled != enable) {
+ dev->udc_enabled = enable;
+ if (!enable) {
+ spin_lock_irqsave(&dev->lock, flags);
+ stop_activity(dev, dev->driver);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ udc_disable(dev);
+ clk_disable(otg_clock);
+ s3c_udc_power(dev, 0);
+ } else {
+ s3c_udc_power(dev, 1);
+ clk_enable(otg_clock);
+ udc_reinit(dev);
+ udc_enable(dev);
+ }
+ } else
+ dev_dbg(&dev->gadget.dev, "%s, udc : %d, en : %d\n",
+ __func__, dev->udc_enabled, enable);
+
+ return 0;
+}
+
+/*
+ * 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 reconfig_usbd(void)
+{
+ struct s3c_udc *dev = the_controller;
+ /* 2. Soft-reset OTG Core and then unreset again. */
+#ifdef DED_TX_FIFO
+ int i;
+#endif
+ unsigned int uTemp;
+ writel(CORE_SOFT_RESET, S3C_UDC_OTG_GRSTCTL);
+
+ writel(0<<15 /* PHY Low Power Clock sel*/
+ |1<<14 /* Non-Periodic TxFIFO Rewind Enable*/
+ |0x5<<10 /* Turnaround time*/
+ |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*/
+ |1<<3 /* phy i/f 0:8bit, 1:16bit*/
+ |0x7<<0, /* HS/FS Timeout**/
+ S3C_UDC_OTG_GUSBCFG);
+
+ /* 3. Put the OTG device core in the disconnected state.*/
+ uTemp = readl(S3C_UDC_OTG_DCTL);
+ uTemp |= SOFT_DISCONNECT;
+ writel(uTemp, S3C_UDC_OTG_DCTL);
+
+ udelay(20);
+
+ /* 4. Make the OTG device core exit from the disconnected state.*/
+ if (!dev->soft_disconnected) {
+ uTemp = readl(S3C_UDC_OTG_DCTL);
+ uTemp = uTemp & ~SOFT_DISCONNECT;
+ writel(uTemp, S3C_UDC_OTG_DCTL);
+ }
+
+ /* 5. Configure OTG Core to initial settings of device mode.*/
+ /* [][1: full speed(30Mhz) 0:high speed]*/
+ writel(1<<18|0x0<<0, S3C_UDC_OTG_DCFG);
+
+ mdelay(1);
+
+ /* 6. Unmask the core interrupts*/
+ writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK);
+
+ /* 7. Set NAK bit of EP0, EP1, EP2*/
+ writel(DEPCTL_EPDIS|DEPCTL_SNAK|(0<<0), S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ writel(DEPCTL_EPDIS|DEPCTL_SNAK|(0<<0), S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ /* 8. Unmask EPO interrupts*/
+ writel(((1<<EP0_CON)<<DAINT_OUT_BIT)|(1<<EP0_CON),
+ S3C_UDC_OTG_DAINTMSK);
+
+ /* 9. Unmask device OUT EP common interrupts*/
+ writel(DOEPMSK_INIT, S3C_UDC_OTG_DOEPMSK);
+
+ /* 10. Unmask device IN EP common interrupts*/
+ writel(DIEPMSK_INIT, S3C_UDC_OTG_DIEPMSK);
+
+ /* 11. Set Rx FIFO Size (in 32-bit words) */
+ writel(RX_FIFO_SIZE, S3C_UDC_OTG_GRXFSIZ);
+
+ /* 12. Set Non Periodic Tx FIFO Size*/
+ writel((NPTX_FIFO_SIZE) << 16 | (NPTX_FIFO_START_ADDR) << 0,
+ S3C_UDC_OTG_GNPTXFSIZ);
+
+#ifdef DED_TX_FIFO
+ for (i = 1; i < S3C_MAX_ENDPOINTS; i++)
+ writel((PTX_FIFO_SIZE) << 16 |
+ (NPTX_FIFO_START_ADDR + NPTX_FIFO_SIZE + PTX_FIFO_SIZE*(i-1)) << 0,
+ S3C_UDC_OTG_DIEPTXF(i));
+#endif
+
+ /* Flush the RX FIFO */
+ writel(0x10, S3C_UDC_OTG_GRSTCTL);
+ while (readl(S3C_UDC_OTG_GRSTCTL) & 0x10)
+ ;
+
+ /* Flush all the Tx FIFO's */
+ writel(0x10<<6, S3C_UDC_OTG_GRSTCTL);
+ writel((0x10<<6)|0x20, S3C_UDC_OTG_GRSTCTL);
+ while (readl(S3C_UDC_OTG_GRSTCTL) & 0x20)
+ ;
+
+ /* 13. Initialize OTG Link Core.*/
+ writel(GAHBCFG_INIT, S3C_UDC_OTG_GAHBCFG);
+}
+
+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 = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ writel(ep_ctrl|(0<<0), S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ /* EP0 - Control OUT (64 bytes)*/
+ ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ writel(ep_ctrl|(0<<0), 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 & USB_ENDPOINT_XFERTYPE_MASK)
+ && 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 = kzalloc(sizeof(*req), gfp_flags);
+ if (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 = container_of(_req, struct s3c_request, req);
+ DEBUG("%s: %p\n", __func__, ep);
+ 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)
+{
+ unsigned int frame;
+ DEBUG("%s: %p\n", __func__, _gadget);
+ /*fram count number [21:8]*/
+ frame = readl(S3C_UDC_OTG_DSTS);
+ frame &= SOFFN_MASK;
+ frame >>= SOFFN_SHIFT;
+ return frame;
+}
+
+static int s3c_udc_wakeup(struct usb_gadget *_gadget)
+{
+ unsigned int dev_gctl, dev_dctl, dev_status;
+ unsigned long flags;
+ int retVal = -EINVAL;
+ struct s3c_udc *dev = the_controller;
+
+ DEBUG("%s: %p\n", __func__, _gadget);
+
+ if (!_gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ if (!(dev->status & (1 << USB_DEVICE_REMOTE_WAKEUP))) {
+ DEBUG_SETUP("%s::Remote Wakeup is not set\n", __func__);
+ goto wakeup_exit;
+ }
+ /* check for session */
+ dev_gctl = readl(S3C_UDC_OTG_GOTGCTL);
+ if (dev_gctl & B_SESSION_VALID) {
+ /* check for suspend state */
+ dev_status = readl(S3C_UDC_OTG_DSTS);
+ if (dev_status & USB_SUSPEND) {
+ DEBUG_SETUP("%s:: Set Remote wakeup\n", __func__);
+ dev_dctl = readl(S3C_UDC_OTG_DCTL);
+ dev_dctl |= REMOTE_WAKEUP;
+ writel(dev_dctl, S3C_UDC_OTG_DCTL);
+ mdelay(1);
+ DEBUG_SETUP("%s:: Clear Remote Wakeup\n", __func__);
+ dev_dctl = readl(S3C_UDC_OTG_DCTL);
+ dev_dctl &= (~REMOTE_WAKEUP);
+ writel(dev_dctl, S3C_UDC_OTG_DCTL);
+ } else {
+ DEBUG_SETUP("%s:: already woke up\n", __func__);
+ }
+
+ } else if (dev_gctl & SESSION_REQ) {
+ DEBUG_SETUP("%s:: Session request already active\n", __func__);
+ goto wakeup_exit;
+ }
+
+ retVal = 0;
+wakeup_exit:
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return retVal;
+}
+
+void s3c_udc_soft_connect(void)
+{
+ struct s3c_udc *dev = the_controller;
+ unsigned long flags;
+ u32 uTemp;
+
+ DEBUG("[%s]\n", __func__);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ uTemp = readl(S3C_UDC_OTG_DCTL);
+ uTemp = uTemp & ~SOFT_DISCONNECT;
+ writel(uTemp, S3C_UDC_OTG_DCTL);
+ udelay(20);
+
+ reset_available = 1;
+
+ /* Unmask the core interrupt */
+ writel(readl(S3C_UDC_OTG_GINTSTS), S3C_UDC_OTG_GINTSTS);
+ writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK);
+
+ dev->soft_disconnected = 0;
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+void s3c_udc_soft_disconnect(void)
+{
+ u32 uTemp;
+ struct s3c_udc *dev = the_controller;
+ unsigned long flags;
+
+ DEBUG("[%s]\n", __func__);
+
+ spin_lock_irqsave(&dev->lock, flags);
+
+ /* Mask the core interrupt */
+ writel(0, S3C_UDC_OTG_GINTMSK);
+
+ uTemp = readl(S3C_UDC_OTG_DCTL);
+ uTemp |= SOFT_DISCONNECT;
+ writel(uTemp, S3C_UDC_OTG_DCTL);
+
+ stop_activity(dev, dev->driver);
+
+ dev->soft_disconnected = 1;
+ 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\n", __func__);
+}
+
+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 | 0xa,
+ .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 | 0xb,
+ .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 | 0xc,
+ .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 | 0xd,
+ .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 | 0xe,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+
+ .ep_type = ep_bulk_in,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP14_FIFO,
+ },
+ .ep[15] = {
+ .ep = {
+ .name = "ep15-iso",
+ .ops = &s3c_ep_ops,
+ .maxpacket = EP_FIFO_SIZE,
+ },
+ .dev = &memory,
+
+ .bEndpointAddress = USB_DIR_IN | 0xf,
+ .bmAttributes = USB_ENDPOINT_XFER_ISOC,
+
+ .ep_type = ep_isochronous,
+ .fifo = (unsigned int) S3C_UDC_OTG_EP15_FIFO,
+ },
+};
+
+#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG
+atomic_t g_OtgHostMode; // actual mode: client (0) or host (1)
+atomic_t g_OtgOperationMode; // operation mode: 'c'lient, 'h'ost, 'o'tg or 'a'uto-host
+atomic_t g_OtgLastCableState; // last cable state: detached (0), client attached (1), otg attached (2)
+atomic_t g_OtgDriver; // driver to use: 0: S3C High-speed, 1: S3C Low-speed/Full-speed, 2: DWC
+
+int s3c_is_otgmode(void)
+{
+ printk("otgmode = %d\n", atomic_read(&g_OtgHostMode));
+ return atomic_read(&g_OtgHostMode);
+}
+EXPORT_SYMBOL(s3c_is_otgmode);
+
+int s3c_get_drivermode(void)
+{
+ printk("drivermode = %d\n", atomic_read(&g_OtgDriver));
+ return atomic_read(&g_OtgDriver);
+}
+EXPORT_SYMBOL(s3c_get_drivermode);
+
+void set_otghost_mode(int mode) {
+//sztupy:
+// this function will determine what to do with the new information according to the current mode of operation
+// mode: 0: cable detached, 1: client cable attached, 2: otg cable attached, -1: operation config changed
+// modes of operation: c: always client, h: always host, g: otg mode (host if otg cable), a: automatic mode (host if cable plugged in)
+
+ struct s3c_udc *dev = the_controller;
+ int enable = 0;
+ int rval;
+ char opmode = atomic_read(&g_OtgOperationMode);
+ if (mode==-1) mode = atomic_read(&g_OtgLastCableState);
+ atomic_set(&g_OtgLastCableState, mode);
+ switch (opmode) {
+ case 'h': enable = 1; break;
+ case 'o': enable = (mode==2); break;
+ case 'a': enable = (mode>0); break;
+ default: atomic_set(&g_OtgOperationMode,'c'); enable = 0; break;
+ }
+
+ if (enable && !atomic_read(&g_OtgHostMode)) {
+ printk("Setting OTG host mode\n");
+ free_irq(IRQ_OTG, dev);
+ s3c_vbus_enable(&dev->gadget, 1);
+
+#if defined CONFIG_USB_S3C_OTG_HOST && defined CONFIG_USB_DWC_OTG
+ if (atomic_read(&g_OtgDriver) & USB_OTG_DRIVER_DWC) {
+ rval = platform_driver_register(&dwc_otg_driver);
+ } else {
+ rval = platform_driver_register(&s5pc110_otg_driver);
+ }
+ if (rval < 0)
+#elif defined CONFIG_USB_DWC_OTG
+ if (platform_driver_register(&dwc_otg_driver) < 0)
+#elif defined CONFIG_USB_S3C_OTG_HOST
+ if (platform_driver_register(&s5pc110_otg_driver) < 0)
+#endif
+ {
+ printk("platform_driver_register failed...\n");
+ atomic_set(&g_OtgHostMode , 0);
+ } else {
+ printk("platform_driver_registered\n");
+ atomic_set(&g_OtgHostMode , atomic_read(&g_OtgDriver)+1);
+ }
+ } else if (!enable && atomic_read(&g_OtgHostMode)) {
+// sztupy: also handle the disabling here
+ printk("Disabling OTG host mode\n");
+ s3c_vbus_enable(&dev->gadget, 0);
+#if defined CONFIG_USB_S3C_OTG_HOST && defined CONFIG_USB_DWC_OTG
+ if ((atomic_read(&g_OtgHostMode)-1) & USB_OTG_DRIVER_DWC) {
+ platform_driver_unregister(&dwc_otg_driver);
+ } else {
+ platform_driver_unregister(&s5pc110_otg_driver);
+ }
+#elif defined CONFIG_USB_S3C_OTG_HOST
+ platform_driver_unregister(&s5pc110_otg_driver);
+#elif defined CONFIG_USB_DWC_OTG
+ platform_driver_unregister(&dwc_otg_driver);
+#endif
+ printk("platform_driver_unregistered\n");
+ /* irq setup after old hardware state is cleaned up */
+ if (request_irq(IRQ_OTG, s3c_udc_irq, 0, driver_name, dev)) {
+ printk("Warning: Could not request IRQ for USB gadget!\n");
+ }
+ atomic_set(&g_OtgHostMode , 0);
+ } else {
+ printk("OTG: no changes needed\n");
+ }
+}
+
+EXPORT_SYMBOL(set_otghost_mode);
+
+static ssize_t usbmode_read(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ const char *msg = "client";
+ const char *msg2 = "disconnected";
+ const char *msg3 = "gadget";
+ switch(atomic_read(&g_OtgOperationMode)) {
+ case 'o': msg = "otg"; break;
+ case 'h': msg = "host"; break;
+ case 'a': msg = "auto-host"; break;
+ }
+ switch(atomic_read(&g_OtgLastCableState)) {
+ case 1: msg2 = "usb connected"; break;
+ case 2: msg2 = "otg connected"; break;
+ }
+ switch(atomic_read(&g_OtgHostMode)) {
+ case USB_OTG_DRIVER_S3CHS: msg3 = "host (S3C/HS)"; break;
+ case USB_OTG_DRIVER_S3CFSLS: msg3 = "host (S3C/FSLS)"; break;
+ case USB_OTG_DRIVER_DWC: msg3 = "host (DWC)"; break;
+ }
+
+ return sprintf(buf,"%s (cable: %s; state: %s)\n", msg, msg2, msg3);
+}
+
+static ssize_t usbmode_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ char c;
+ printk("input data --> %s", buf);
+ c = buf[0];
+ switch(c) {
+ case 'a': atomic_set(&g_OtgOperationMode, 'a'); set_otghost_mode(-1);break;
+ case 'h': atomic_set(&g_OtgOperationMode, 'h'); set_otghost_mode(-1);break;
+ case 'c': atomic_set(&g_OtgOperationMode, 'c'); set_otghost_mode(-1);break;
+ case 'o': atomic_set(&g_OtgOperationMode, 'o'); set_otghost_mode(-1);break;
+ default: printk("Invalid input data\n");
+ }
+ return size;
+}
+
+static ssize_t usbdriver_read(struct device *dev, struct device_attribute *attr, char *buf) {
+ const char *msg = "h:S3C driver (high-speed)";
+ switch(atomic_read(&g_OtgDriver)) {
+ case USB_OTG_DRIVER_S3CFSLS: msg = "l:S3C driver (low-speed / full-speed)";break;
+ case USB_OTG_DRIVER_DWC: msg = "d:DWC driver";break;
+ }
+ return sprintf(buf,"%s\n",msg);
+}
+
+static ssize_t usbdriver_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) {
+ char c;
+ printk("input data --> %s", buf);
+ c = buf[0];
+ switch(c) {
+#if defined CONFIG_USB_S3C_OTG_HOST
+ case 'h': atomic_set(&g_OtgDriver, USB_OTG_DRIVER_S3CHS); break;
+ case 'l': atomic_set(&g_OtgDriver, USB_OTG_DRIVER_S3CFSLS); break;
+#endif
+#if defined CONFIG_USB_DWC_OTG
+ case 'd': atomic_set(&g_OtgDriver, USB_OTG_DRIVER_DWC); break;
+#endif
+ default: printk("Invalid input data\n");
+ }
+ return size;
+
+}
+
+static ssize_t usbversion_read(struct device *dev, struct device_attribute *attr, char *buf) {
+ return sprintf(buf,"version: build 5\ndrivers:%s%s\n",
+#if defined CONFIG_USB_S3C_OTG_HOST
+ " S3CHS S3CFSLS"
+#else
+ ""
+#endif
+ ,
+#if defined CONFIG_USB_DWC_OTG
+ " DWC"
+#else
+ ""
+#endif
+ );
+}
+
+// kevinh - Allow changing USB host/target modes on S3C android devices without a special cable
+static DEVICE_ATTR(opmode, S_IRUGO | S_IWUSR, usbmode_read, usbmode_write);
+// sztupy - add some more attributes
+static DEVICE_ATTR(hostdriver, S_IRUGO | S_IWUSR, usbdriver_read, usbdriver_write);
+static DEVICE_ATTR(version, S_IRUGO, usbversion_read, NULL);
+#endif
+
+/*
+ * probe - binds to the platform device
+ */
+static int s3c_udc_probe(struct platform_device *pdev)
+{
+ struct s3c_udc *dev = &memory;
+ int retval;
+ DEBUG("%s: %p\n", __func__, pdev);
+
+ spin_lock_init(&dev->lock);
+ dev->dev = pdev;
+
+ device_initialize(&dev->gadget.dev);
+ dev->gadget.dev.parent = &pdev->dev;
+
+ 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;
+
+ dev->udc_vcc_d = regulator_get(&pdev->dev, "pd_io");
+ dev->udc_vcc_a = regulator_get(&pdev->dev, "pd_core");
+ if (IS_ERR(dev->udc_vcc_d) || IS_ERR(dev->udc_vcc_a)) {
+ printk(KERN_ERR "failed to find udc vcc source\n");
+ return -ENOENT;
+ }
+
+ the_controller = dev;
+ platform_set_drvdata(pdev, dev);
+
+ dev_set_name(&dev->gadget.dev, "gadget");
+ otg_clock = clk_get(&pdev->dev, "otg");
+ if (otg_clock == NULL) {
+ printk(KERN_ERR "failed to find otg clock source\n");
+ return -ENOENT;
+ }
+
+ udc_reinit(dev);
+
+ /* irq setup after old hardware state is cleaned up */
+ retval =
+ request_irq(IRQ_OTG, s3c_udc_irq, 0, driver_name, dev);
+
+ if (retval != 0) {
+ DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name,
+ IRQ_OTG, retval);
+ return -EBUSY;
+ }
+
+ disable_irq(IRQ_OTG);
+ create_proc_files();
+#if defined CONFIG_USB_S3C_OTG_HOST || defined CONFIG_USB_DWC_OTG
+ if (device_create_file(&pdev->dev, &dev_attr_opmode) < 0)
+ printk("Failed to create device file(%s)!\n", dev_attr_opmode.attr.name);
+ if (device_create_file(&pdev->dev, &dev_attr_hostdriver) < 0)
+ printk("Failed to create device file(%s)!\n", dev_attr_hostdriver.attr.name);
+ if (device_create_file(&pdev->dev, &dev_attr_version) < 0)
+ printk("Failed to create device file(%s)!\n", dev_attr_version.attr.name);
+#if !defined CONFIG_USB_S3C_OTG_HOST
+ atomic_set(&g_OtgDriver, USB_OTG_DRIVER_DWC);
+#endif
+#endif
+ 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);
+
+ if (otg_clock != NULL) {
+ clk_disable(otg_clock);
+ clk_put(otg_clock);
+ otg_clock = NULL;
+ }
+
+ remove_proc_files();
+ usb_gadget_unregister_driver(dev->driver);
+
+ free_irq(IRQ_OTG, dev);
+
+ platform_set_drvdata(pdev, 0);
+
+ the_controller = 0;
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c_udc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct s3c_udc *dev = the_controller;
+
+ disable_irq(IRQ_OTG);
+
+ if (dev->driver && dev->driver->suspend)
+ dev->driver->suspend(&dev->gadget);
+
+ if (dev->udc_enabled)
+ usb_gadget_vbus_disconnect(&dev->gadget);
+
+ return 0;
+}
+
+static int s3c_udc_resume(struct platform_device *pdev)
+{
+ struct s3c_udc *dev = the_controller;
+
+ if (dev->driver && dev->driver->resume)
+ dev->driver->resume(&dev->gadget);
+
+ enable_irq(IRQ_OTG);
+
+ 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 %s\n",
+ driver_name, DRIVER_DESC,
+ driver_name, DRIVER_VERSION, OTG_DMA_MODE ?
+ "(DMA Mode)" : "(Slave Mode)");
+
+ 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..70cf602
--- /dev/null
+++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c
@@ -0,0 +1,1387 @@
+/*
+ * drivers/usb/gadget/s3c_udc_otg_xfer_dma.c
+ * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers
+ *
+ * Copyright (C) 2009 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
+ *
+ */
+
+#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)
+
+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))) = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* JKJKJKJK x 9 */
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, /* JJKKJJKK x 8 */
+ 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, /* JJJJKKKK x 8 */
+ 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, /* JJJJJJJKKKKKKK x 8 - '1' */
+ 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, /* '1' + JJJJJJJK x 8 */
+ 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E /* {JKKKKKKK x 10}, JK */
+};
+
+void s3c_udc_ep_set_stall(struct s3c_ep *ep);
+
+#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210)
+extern void s3c_cable_check_status(int flag);
+
+void s3c_udc_cable_connect(struct s3c_udc *dev)
+{
+ s3c_cable_check_status(1);
+}
+
+void s3c_udc_cable_disconnect(struct s3c_udc *dev)
+{
+ s3c_cable_check_status(0);
+}
+#endif
+
+static inline void s3c_udc_ep0_zlp(void)
+{
+ u32 ep_ctrl;
+
+ writel(virt_to_phys(&usb_ctrl), S3C_UDC_OTG_DIEPDMA(EP0_CON));
+ writel((1<<19 | 0<<0), S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+
+ ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ DEBUG_EP0("%s:EP0 ZLP DIEPCTL0 = 0x%x\n",
+ __func__, readl(S3C_UDC_OTG_DIEPCTL(EP0_CON)));
+}
+
+static inline void s3c_udc_pre_setup(void)
+{
+ u32 ep_ctrl;
+
+ DEBUG_IN_EP("%s : Prepare Setup packets.\n", __func__);
+
+ writel((1 << 19)|sizeof(struct usb_ctrlrequest), S3C_UDC_OTG_DOEPTSIZ(EP0_CON));
+ writel(virt_to_phys(&usb_ctrl), S3C_UDC_OTG_DOEPDMA(EP0_CON));
+
+ ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, 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 device *dev = &the_controller->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 = readl(S3C_UDC_OTG_DOEPCTL(ep_num));
+
+ writel(virt_to_phys(buf), S3C_UDC_OTG_DOEPDMA(ep_num));
+ writel((pktcnt<<19)|(length<<0), S3C_UDC_OTG_DOEPTSIZ(ep_num));
+ writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, 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,
+ readl(S3C_UDC_OTG_DOEPDMA(ep_num)),
+ readl(S3C_UDC_OTG_DOEPTSIZ(ep_num)),
+ readl(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 device *dev = &the_controller->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 = readl(S3C_UDC_OTG_DIEPCTL(ep_num));
+ ctrl &= ~DEPCTL_TXFNUM_MASK;;
+ ctrl |= (ep_num << DEPCTL_TXFNUM_BIT);
+ writel(ctrl , S3C_UDC_OTG_DIEPCTL(ep_num));
+#endif
+
+ writel(virt_to_phys(buf), S3C_UDC_OTG_DIEPDMA(ep_num));
+ writel((pktcnt<<19)|(length<<0), S3C_UDC_OTG_DIEPTSIZ(ep_num));
+ ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num));
+ if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC)
+ ctrl |= DEPCTL_SET_ODD_FRM;
+ writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, S3C_UDC_OTG_DIEPCTL(ep_num));
+
+ 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,
+ readl(S3C_UDC_OTG_DIEPDMA(ep_num)),
+ readl(S3C_UDC_OTG_DIEPTSIZ(ep_num)),
+ readl(S3C_UDC_OTG_DIEPCTL(ep_num)),
+ buf, pktcnt, 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 = readl(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();
+
+ } 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 = readl(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) {
+ 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 = readl(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 = readl(S3C_UDC_OTG_DIEPINT(ep_num));
+ DEBUG_IN_EP("\tEP%d-IN : DIEPINT = 0x%x\n",
+ ep_num, ep_intr_status);
+
+ /* Interrupt Clear */
+ writel(ep_intr_status, 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();
+
+ /* 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, ep_ctrl;
+ u8 ep_num = 0;
+
+ ep_intr = readl(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 = readl(S3C_UDC_OTG_DOEPINT(ep_num));
+ DEBUG_OUT_EP("\tEP%d-OUT : DOEPINT = 0x%x\n",
+ ep_num, ep_intr_status);
+
+ /* Interrupt Clear */
+ writel(ep_intr_status, 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);
+
+ writel((3<<29)|(1<<19)|sizeof(struct usb_ctrlrequest),
+ S3C_UDC_OTG_DOEPTSIZ(EP0_CON));
+ writel(virt_to_phys(&usb_ctrl),
+ S3C_UDC_OTG_DOEPDMA(EP0_CON));
+
+ ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(EP0_CON));
+ writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_SNAK,
+ 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 = readl(S3C_UDC_OTG_GINTSTS);
+ gintmsk = readl(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,
+ readl(S3C_UDC_OTG_DAINT), readl(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");
+
+ writel(INT_ENUMDONE, S3C_UDC_OTG_GINTSTS);
+ usb_status = (readl(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);
+ }
+ }
+
+ if (intr_status & INT_EARLY_SUSPEND) {
+ DEBUG_ISR("\tEarly suspend interrupt\n");
+ writel(INT_EARLY_SUSPEND, S3C_UDC_OTG_GINTSTS);
+ }
+
+ if (intr_status & INT_SUSPEND) {
+ usb_status = readl(S3C_UDC_OTG_DSTS);
+ DEBUG_ISR("\tSuspend interrupt :(DSTS):0x%x\n", usb_status);
+ writel(INT_SUSPEND, S3C_UDC_OTG_GINTSTS);
+
+ if (dev->gadget.speed != USB_SPEED_UNKNOWN
+ && dev->driver
+ && dev->driver->suspend) {
+ spin_unlock(&dev->lock);
+ dev->driver->suspend(&dev->gadget);
+ spin_lock(&dev->lock);
+ }
+ if (dev->status & (1 << USB_DEVICE_REMOTE_WAKEUP)) {
+ DEBUG_ISR("device is under remote wakeup\n");
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return IRQ_HANDLED;
+ }
+ if (dev->driver) {
+ spin_unlock(&dev->lock);
+ dev->driver->disconnect(&dev->gadget);
+ spin_lock(&dev->lock);
+ }
+#if defined(CONFIG_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210)
+ s3c_udc_cable_disconnect(dev);
+#endif
+ }
+
+ if (intr_status & INT_RESUME) {
+ DEBUG_ISR("\tResume interrupt\n");
+ writel(INT_RESUME, 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 = readl(S3C_UDC_OTG_GOTGCTL);
+ DEBUG_ISR("\tReset interrupt - (GOTGCTL):0x%x\n", usb_status);
+ writel(INT_RESET, S3C_UDC_OTG_GINTSTS);
+
+ set_conf_done = 0;
+
+ if ((usb_status & 0xc0000) == (0x3 << 18)) {
+ if (reset_available) {
+ DEBUG_ISR("\t\tOTG core got reset (%d)!!\n", reset_available);
+ stop_activity(dev, dev->driver);
+ reconfig_usbd();
+ dev->ep0state = WAIT_FOR_SETUP;
+ reset_available = 0;
+ s3c_udc_pre_setup();
+ }
+ } else if (!(usb_status & B_SESSION_VALID)) {
+ reset_available = 1;
+ if (dev->udc_enabled) {
+ DEBUG_ISR("Reset without B_SESSION\n");
+ if (dev->driver) {
+ spin_unlock(&dev->lock);
+ dev->driver->disconnect(&dev->gadget);
+ spin_lock(&dev->lock);
+ }
+ }
+ } else {
+ reset_available = 1;
+ DEBUG_ISR("\t\tRESET handling skipped\n");
+ }
+ }
+
+ 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 = readl(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 = readl(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) || req->req.zero)
+ 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;
+}
+
+static inline int s3c_fifo_read(struct s3c_ep *ep, u32 *cp, int max)
+{
+ u32 bytes;
+
+ bytes = sizeof(struct usb_ctrlrequest);
+ __dma_single_cpu_to_dev(&usb_ctrl, bytes, DMA_FROM_DEVICE);
+ DEBUG_EP0("%s: bytes=%d, ep_index=%d\n", __func__, bytes, ep_index(ep));
+
+ return bytes;
+}
+
+/**
+ * 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 = readl(S3C_UDC_OTG_DCFG);
+ writel(address << 4 | ctrl, S3C_UDC_OTG_DCFG);
+
+ s3c_udc_ep0_zlp();
+
+ DEBUG_EP0("%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n",
+ __func__, address, readl(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 = readl(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;
+
+ writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ DEBUG_EP0("%s: set ep%d stall, DIEPCTL0 = 0x%x\n",
+ __func__, ep_index(ep), readl(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();
+}
+
+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();
+ done(ep, req, 0);
+ DEBUG_EP0("%s: req.length = 0, bRequest = %d\n", __func__, 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, need_zlp = 0;
+
+ 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);
+
+ if (req->req.length - req->req.actual == ep0_fifo_size) {
+ /* Next write will end with the packet size, */
+ /* so we need Zero-length-packet */
+ need_zlp = 1;
+ }
+
+ ret = write_fifo_ep0(ep, req);
+
+ if ((ret == 1) && !need_zlp) {
+ /* 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__);
+ }
+
+ if (need_zlp) {
+ dev->ep0state = DATA_STATE_NEED_ZLP;
+ DEBUG_EP0("%s: Need ZLP!\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:
+ /* update device status */
+ g_status = dev->status;
+ 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);
+
+ writel(virt_to_phys(&g_status), S3C_UDC_OTG_DIEPDMA(EP0_CON));
+ writel((1<<19)|(2<<0), S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+
+ ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, S3C_UDC_OTG_DIEPCTL(EP0_CON));
+ dev->ep0state = WAIT_FOR_SETUP;
+
+ return 0;
+}
+
+void s3c_udc_ep_set_stall(struct s3c_ep *ep)
+{
+ 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 = readl(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;
+
+ writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num));
+ DEBUG("%s: set stall, DIEPCTL%d = 0x%x\n",
+ __func__, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num)));
+
+ } else {
+ ep_ctrl = readl(S3C_UDC_OTG_DOEPCTL(ep_num));
+
+ /* set the stall bit */
+ ep_ctrl |= DEPCTL_STALL;
+
+ writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num));
+ DEBUG("%s: set stall, DOEPCTL%d = 0x%x\n",
+ __func__, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num)));
+ }
+
+ return;
+}
+
+void s3c_udc_ep_clear_stall(struct s3c_ep *ep)
+{
+ 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 = readl(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 */
+ }
+
+ writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num));
+ DEBUG("%s: cleared stall, DIEPCTL%d = 0x%x\n",
+ __func__, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num)));
+
+ } else {
+ ep_ctrl = readl(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 */
+ }
+
+ writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num));
+ DEBUG("%s: cleared stall, DOEPCTL%d = 0x%x\n",
+ __func__, ep_num, readl(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)
+{
+ u8 ep_num;
+ u32 ep_ctrl = 0, daintmsk = 0;
+
+ ep_num = ep_index(ep);
+
+ /* Read DEPCTLn register */
+ if (ep_is_in(ep)) {
+ ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(ep_num));
+ daintmsk = 1 << ep_num;
+ } else {
+ ep_ctrl = readl(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);
+
+ if (ep_is_in(ep)) {
+ writel(ep_ctrl, S3C_UDC_OTG_DIEPCTL(ep_num));
+ DEBUG("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n",
+ __func__, ep_num, ep_num, readl(S3C_UDC_OTG_DIEPCTL(ep_num)));
+ } else {
+ writel(ep_ctrl, S3C_UDC_OTG_DOEPCTL(ep_num));
+ DEBUG("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n",
+ __func__, ep_num, ep_num, readl(S3C_UDC_OTG_DOEPCTL(ep_num)));
+ }
+ }
+
+ /* Unmask EP Interrtupt */
+ writel(readl(S3C_UDC_OTG_DAINTMSK)|daintmsk, S3C_UDC_OTG_DAINTMSK);
+ DEBUG("%s: DAINTMSK = 0x%x\n", __func__, readl(S3C_UDC_OTG_DAINTMSK));
+
+}
+
+static int s3c_udc_clear_feature(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ u8 ep_num;
+ struct s3c_udc *dev = the_controller;
+ ep = container_of(_ep, struct s3c_ep, ep);
+ ep_num = ep_index(ep);
+
+ 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");
+ printk(KERN_INFO "%s:: USB_DEVICE_REMOTE_WAKEUP\n", __func__);
+ dev->status &= ~(1 << USB_DEVICE_REMOTE_WAKEUP);
+ break;
+
+ case USB_DEVICE_TEST_MODE:
+ DEBUG_SETUP("\tCLEAR_FEATURE: USB_DEVICE_TEST_MODE\n");
+ /** @todo Add CLEAR_FEATURE for TEST modes. */
+ break;
+ }
+
+ s3c_udc_ep0_zlp();
+ 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();
+
+ 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(void)
+{
+ u32 ep_ctrl, dctl;
+ u8 test_selector = (usb_ctrl.wIndex>>8) & TEST_SELECTOR_MASK;
+
+ if (test_selector > 0 && test_selector < 6) {
+ ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ writel(1<<19 | 0<<0, S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+ writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK|EP0_CON<<DEPCTL_NEXT_EP_BIT , 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 = readl(S3C_UDC_OTG_DCTL);
+ writel((dctl&~(TEST_CONTROL_MASK))|TEST_J_MODE, 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 = readl(S3C_UDC_OTG_DCTL);
+ writel((dctl&~(TEST_CONTROL_MASK))|TEST_K_MODE, 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 = readl(S3C_UDC_OTG_DCTL);
+ writel((dctl&~(TEST_CONTROL_MASK))|TEST_SE0_NAK_MODE, 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);
+ writel(virt_to_phys(test_pkt), S3C_UDC_OTG_DIEPDMA(EP0_CON));
+
+ ep_ctrl = readl(S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ writel(1<<19 | TEST_PKT_SIZE<<0, S3C_UDC_OTG_DIEPTSIZ(EP0_CON));
+ writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK|EP0_CON<<DEPCTL_NEXT_EP_BIT, S3C_UDC_OTG_DIEPCTL(EP0_CON));
+
+ dctl = readl(S3C_UDC_OTG_DCTL);
+ writel((dctl&~(TEST_CONTROL_MASK))|TEST_PACKET_MODE, 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 = readl(S3C_UDC_OTG_DCTL);
+ writel((dctl&~(TEST_CONTROL_MASK))|TEST_FORCE_ENABLE_MODE, S3C_UDC_OTG_DCTL);
+ break;
+ }
+}
+
+static int s3c_udc_set_feature(struct usb_ep *_ep)
+{
+ struct s3c_ep *ep;
+ u8 ep_num;
+ struct s3c_udc *dev = the_controller;
+ ep = container_of(_ep, struct s3c_ep, ep);
+ ep_num = ep_index(ep);
+
+ 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");
+ printk(KERN_INFO "%s:: USB_DEVICE_REMOTE_WAKEUP\n", __func__);
+ dev->status |= (1 << USB_DEVICE_REMOTE_WAKEUP);
+ break;
+
+ case USB_DEVICE_TEST_MODE:
+ DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_TEST_MODE\n");
+ set_test_mode();
+ 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;
+ }
+
+ s3c_udc_ep0_zlp();
+ 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();
+ 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, bytes, is_in;
+ u8 ep_num;
+
+ /* Nuke all previous transfers */
+ nuke(ep, -EPROTO);
+
+ /* read control req from fifo (8 bytes) */
+ bytes = s3c_fifo_read(ep, (u32 *)&usb_ctrl, 8);
+
+ 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_MACH_SMDKC110) || defined(CONFIG_MACH_SMDKV210)
+ 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);
+ return;
+ }
+
+ /* 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/storage_common.c b/drivers/usb/gadget/storage_common.c
index 1fa4f70..a872248 100644
--- a/drivers/usb/gadget/storage_common.c
+++ b/drivers/usb/gadget/storage_common.c
@@ -763,10 +763,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;
+
+#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')
diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c
index a52404a..f30c307 100644
--- a/drivers/usb/gadget/u_ether.c
+++ b/drivers/usb/gadget/u_ether.c
@@ -249,11 +249,13 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags)
goto enomem;
}
+#ifndef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED
/* 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.
*/
skb_reserve(skb, NET_IP_ALIGN);
+#endif
req->buf = skb->data;
req->length = size;
@@ -483,7 +485,10 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req)
list_add(&req->list, &dev->tx_reqs);
spin_unlock(&dev->req_lock);
dev_kfree_skb_any(skb);
-
+#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED
+ 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);
@@ -577,7 +582,21 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
length = skb->len;
}
+
+#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED
+ if ((int)skb->data & 3) {
+ req->buf = kmalloc(skb->len, GFP_ATOMIC);
+ if (!req->buf)
+ goto drop;
+ memcpy((void *)req->buf, (void *)skb->data, skb->len);
+ }
+ else {
+ req->buf = skb->data;
+ }
+#else
req->buf = skb->data;
+#endif
+
req->context = skb;
req->complete = tx_complete;
@@ -618,6 +637,10 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb,
dev_kfree_skb_any(skb);
drop:
dev->net->stats.tx_dropped++;
+#ifdef CONFIG_USB_ANDROID_RNDIS_DWORD_ALIGNED
+ if (req->buf != skb->data)
+ kfree(req->buf);
+#endif
spin_lock_irqsave(&dev->req_lock, flags);
if (list_empty(&dev->tx_reqs))
netif_start_queue(net);
@@ -765,6 +788,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,7 +830,7 @@ 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,
@@ -943,7 +986,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_serial.c b/drivers/usb/gadget/u_serial.c
index 40f7716..3fdcc9a 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;
@@ -1028,7 +1028,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 +1074,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..67c452d 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -578,3 +578,19 @@ 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..de59025 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -15,6 +15,7 @@ xhci-hcd-y := xhci.o xhci-mem.o xhci-pci.o
xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o
obj-$(CONFIG_USB_WHCI_HCD) += whci/
+obj-$(CONFIG_USB_S3C_OTG_HOST) += s3c-otg/
obj-$(CONFIG_PCI) += pci-quirks.o
diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c
index 2499b3b..e4dd26a 100644
--- a/drivers/usb/host/ehci-q.c
+++ b/drivers/usb/host/ehci-q.c
@@ -995,6 +995,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 +1088,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.h b/drivers/usb/host/ehci.h
index 3ffb27f..db0d14e 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -737,6 +737,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()
+{
+ mb();
+}
+#else
+static inline void ehci_sync_mem()
+{
+}
+#endif
+
/*-------------------------------------------------------------------------*/
#ifdef CONFIG_PCI
diff --git a/drivers/usb/host/s3c-otg/Makefile b/drivers/usb/host/s3c-otg/Makefile
new file mode 100644
index 0000000..37674c8
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for USB OTG Host Controller Drivers
+#
+
+obj-$(CONFIG_USB_S3C_OTG_HOST) += s3c_otg_hcd.o
+
+s3c_otg_hcd-objs += s3c-otg-hcdi-driver.o s3c-otg-hcdi-hcd.o
+s3c_otg_hcd-objs += s3c-otg-transfer-common.o s3c-otg-transfer-nonperiodic.o \
+ s3c-otg-transfer-periodic.o
+s3c_otg_hcd-objs += s3c-otg-scheduler-ischeduler.o s3c-otg-scheduler-scheduler.o \
+ s3c-otg-scheduler-readyq.o
+s3c_otg_hcd-objs += s3c-otg-oci.o
+s3c_otg_hcd-objs += s3c-otg-transferchecker-common.o \
+ s3c-otg-transferchecker-control.o \
+ s3c-otg-transferchecker-bulk.o \
+ s3c-otg-transferchecker-interrupt.o
+s3c_otg_hcd-objs += s3c-otg-isr.o
+s3c_otg_hcd-objs += s3c-otg-roothub.o
diff --git a/drivers/usb/host/s3c-otg/debug-mem.c b/drivers/usb/host/s3c-otg/debug-mem.c
new file mode 100644
index 0000000..de85853
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/debug-mem.c
@@ -0,0 +1,55 @@
+#include "s3c-otg-hcdi-hcd.h"
+#include "debug-mem.h"
+
+#define MAX_ALLOCS 1024
+
+typedef void *ElemType;
+
+static ElemType alloced[MAX_ALLOCS];
+static int numalloced = 0;
+
+#define fail(args...) ( printk(args), dump_stack() )
+
+void debug_alloc(void *addr) {
+ ElemType *freeloc = NULL;
+ ElemType *c = alloced;
+ int i;
+
+ if(!addr)
+ fail("MEMD alloc of NULL");
+
+ for(i = 0; i < numalloced; i++, c++) {
+ if(*c == NULL && freeloc == NULL)
+ freeloc = c;
+ else if(*c == addr)
+ fail("MEMD multiple allocs of %p", addr);
+ }
+
+ if(freeloc)
+ *freeloc = addr;
+ else {
+ if(numalloced >= MAX_ALLOCS)
+ fail("MEMD too many allocs");
+ else {
+ alloced[numalloced++] = addr;
+ }
+ }
+}
+
+void debug_free(void *addr) {
+ ElemType *c = alloced;
+ int i;
+
+ if(!addr)
+ fail("free of NULL");
+
+ for(i = 0; i < numalloced; i++, c++) {
+ if(*c == addr) {
+ *c = NULL;
+ return;
+ }
+ }
+
+ fail("MEMD freed addr %p was never alloced\n", addr);
+}
+
diff --git a/drivers/usb/host/s3c-otg/debug-mem.h b/drivers/usb/host/s3c-otg/debug-mem.h
new file mode 100644
index 0000000..4164793
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/debug-mem.h
@@ -0,0 +1,9 @@
+#ifndef __DEBUGMEM_H
+#define __DEBUGMEM_H
+
+// kevinh quick hack to see if the s3c stuff is doing memory properly
+
+void debug_alloc(void *addr);
+void debug_free(void *addr);
+
+#endif
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-common.h b/drivers/usb/host/s3c-otg/s3c-otg-common-common.h
new file mode 100644
index 0000000..720154e
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-common-common.h
@@ -0,0 +1,49 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-common-common.h
+ * @brief it includes common header files for all modules \n
+ * @version
+ * ex)-# Jun 11,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n
+ * : Creating the initial version of this code \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_COMMON_COMMON_H_
+#define _S3C_OTG_COMMON_COMMON_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-errorcode.h"
+#include <linux/errno.h>
+#include <linux/usb.h>
+
+//Define OS
+#define LINUX 1
+
+//Kernel Version
+#define KERNEL_2_6_21
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _S3C_OTG_COMMON_COMMON_H_ */
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-const.h b/drivers/usb/host/s3c-otg/s3c-otg-common-const.h
new file mode 100644
index 0000000..f0f5cb9
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-common-const.h
@@ -0,0 +1,167 @@
+/****************************************************************************
+ * (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_
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+#include "s3c-otg-common-common.h"
+
+//#include "s3c-otg-common-regdef.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/**
+ * @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..
+//typedef URB_SHORT_NOT_OK USB_TRANS_FLAG_NOT_SHORT;
+//typedef URB_ISO_ASAP USB_TRANS_FLAG_ISO_ASYNCH;
+
+#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
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h b/drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h
new file mode 100644
index 0000000..c31e8ab
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-common-datastruct.h
@@ -0,0 +1,811 @@
+/****************************************************************************
+ * (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
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+#include <linux/wakelock.h>
+#include <plat/s5p-otghost.h>
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-hcdi-list.h"
+
+//#include "s3c-otg-common-regdef.h"
+//#include "s3c-otg-common-errorcode.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+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;
+
+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;
+
+
+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;
+
+
+typedef struct isoch_packet_desc
+{
+ u32 isoch_packiet_start_addr;// start address of buffer is buffer address + uiOffsert.
+ u32 buf_size;
+ u32 transferred_szie;
+ u32 isoch_status;
+}isoch_packet_desc_t;//, *isoch_packet_desc_t *,**isoch_packet_desc_t **;
+
+
+typedef 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;
+}standard_dev_req_info_t;
+
+
+typedef struct control_data_tgl_t
+{
+ u8 setup_tgl;
+ u8 data_tgl;
+ u8 status_tgl;
+}control_data_tgl_t;
+
+
+
+typedef struct ed_status
+{
+ u8 data_tgl;
+ control_data_tgl_t control_data_tgl;
+ bool is_ping_enable;
+ bool is_in_transfer_ready_q;
+ bool is_in_transferring;
+ u32 in_transferring_td;
+ bool is_alloc_resource_for_ed;
+ bool is_complete_split;
+// sztupy: split transaction support
+#define ED_STATUS_SPLIT_POS_ALL 3
+#define ED_STATUS_SPLIT_POS_BEGIN 2
+#define ED_STATUS_SPLIT_POS_MID 1
+#define ED_STATUS_SPLIT_POS_END 0
+ u8 split_pos;
+ u32 split_offset;
+}ed_status_t;//, *ed_status_t *,**ed_status_t **;
+
+
+typedef 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;
+}ed_dest_t;//, *ed_dest_t *,**ed_dest_t **;
+
+
+//Defines the Data Structures of Transfer.
+typedef struct hc_reg
+{
+
+ hcintmsk_t hc_int_msk;
+ hcintn_t hc_int;
+ u32 dma_addr;
+
+}hc_reg_t;//, *hc_reg_t *, **hc_reg_t **;
+
+
+typedef struct stransfer
+{
+ u32 stransfer_id;
+ u32 parent_td;
+ ed_dest_t *ed_desc_p;
+ ed_status_t *ed_status_p;
+ u32 start_vir_buf_addr;
+ u32 start_phy_buf_addr;
+ u32 buf_size;
+ u32 packet_cnt;
+ u8 alloc_chnum;
+ hc_reg_t hc_reg;
+}stransfer_t;//, *stransfer_t *,**stransfer_t **;
+
+
+typedef struct ed
+{
+ u32 ed_id;
+ bool is_halted;
+ bool is_need_to_insert_scheduler;
+ ed_dest_t ed_desc;
+ ed_status_t ed_status;
+ otg_list_head ed_list_entry;
+ otg_list_head td_list_entry;
+ otg_list_head trans_ready_q_list_entry;
+ u32 num_td;
+ void *ed_private;
+}ed_t;//, *ed_t *, **ed_t **;
+
+
+
+typedef struct td
+{
+ u32 td_id;
+ ed_t *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;
+ standard_dev_req_info_t standard_dev_req_info;
+ u32 vir_buf_addr;
+ u32 phy_buf_addr;
+ u32 buf_size;
+ u32 transfer_flag;
+ stransfer_t cur_stransfer;
+ USB_ERROR_CODE error_code;
+ u32 err_cnt;
+ otg_list_head td_list_entry;
+
+ //Isochronous Transfer Specific
+ u32 isoch_packet_num;
+ isoch_packet_desc_t *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;
+}td_t;//, *td_t *,**td_t **;
+
+
+//Define Data Structures of Scheduler.
+typedef struct trans_ready_q
+{
+ bool is_periodic_transfer;
+ otg_list_head trans_ready_q_list_head;
+ u32 trans_ready_entry_num;
+
+ //In case of Periodic Transfer
+ u32 total_perio_bus_bandwidth;
+ u8 total_alloc_chnum;
+}trans_ready_q_t;//, *trans_ready_q_t *,**trans_ready_q_t **;
+
+
+//Define USB OTG Reg Data Structure by Kyuhyeok.
+
+#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;
+
+typedef union _gresetctl_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;
+} gresetctl_t;
+
+
+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 00
+#define INT_DMA_MODE_INCR 01
+#define INT_DMA_MODE_INCR4 03
+#define INT_DMA_MODE_INCR8 05
+#define INT_DMA_MODE_INCR16 07
+ 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;
+
+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;
+
+
+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;
+
+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;
+
+
+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;
+
+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;
+
+
+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;
+
+
+typedef struct _hc_t
+{
+
+ u8 hc_num; // Host channel number used for register address lookup
+
+ unsigned dev_addr : 7; // Device to access
+ unsigned ep_is_in : 1; // EP direction; 0: OUT, 1: IN
+
+ unsigned ep_num : 4; // EP to access
+ unsigned low_speed : 1; // 1: Low speed, 0: Not low speed
+ unsigned ep_type : 2; // Endpoint type.
+ // One of the following values:
+ // - OTG_EP_TYPE_CONTROL: 0
+ // - OTG_EP_TYPE_ISOC: 1
+ // - OTG_EP_TYPE_BULK: 2
+ // - OTG_EP_TYPE_INTR: 3
+
+ unsigned rsvdb1 : 1; // 8 bit padding
+
+ u8 rsvd2; // 4 byte boundary
+
+ unsigned max_packet : 12; // Max packet size in bytes
+
+ unsigned data_pid_start : 2;
+#define OTG_HC_PID_DATA0 0
+#define OTG_HC_PID_DATA2 1
+#define OTG_HC_PID_DATA1 2
+#define OTG_HC_PID_MDATA 3
+#define OTG_HC_PID_SETUP 3
+
+ unsigned multi_count : 2; // Number of periodic transactions per (micro)frame
+
+
+ // Flag to indicate whether the transfer has been started. Set to 1 if
+ // it has been started, 0 otherwise.
+ u8 xfer_started;
+
+
+ // Set to 1 to indicate that a PING request should be issued on this
+ // channel. If 0, process normally.
+ u8 do_ping;
+
+ // Set to 1 to indicate that the error count for this transaction is
+ // non-zero. Set to 0 if the error count is 0.
+ u8 error_state;
+ u32 *xfer_buff; // Pointer to the current transfer buffer position.
+ u16 start_pkt_count; // Packet count at start of transfer.
+
+ u32 xfer_len; // Total number of bytes to transfer.
+ u32 xfer_count; // Number of bytes transferred so far.
+
+
+ // Set to 1 if the host channel has been halted, but the core is not
+ // finished flushing queued requests. Otherwise 0.
+ u8 halt_pending;
+ u8 halt_status; // Reason for halting the host channel
+ u8 short_read; // Set when the host channel does a short read.
+ u8 rsvd3; // 4 byte boundary
+
+} hc_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
+
+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;
+
+// sztupy: adding struct to handle HCSPLT register
+typedef union _hcsplt_t {
+ // raw register data
+ u32 d32;
+
+ // register bits
+ struct
+ {
+ unsigned prtaddr : 7;
+ unsigned hubaddr : 7;
+ unsigned xactpos : 2;
+ unsigned compsplt : 1;
+ unsigned reserved : 14;
+ unsigned spltena : 1;
+ } b;
+
+} hcsplt_t;
+
+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;
+
+
+
+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;
+
+typedef union _hfir_t
+{
+ // raw register data
+ u32 d32;
+
+ // register bits
+ struct
+ {
+ unsigned frint : 16;
+ unsigned Reserved : 16;
+ } b;
+} hfir_t;
+
+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;
+
+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;
+
+typedef struct hc_info
+{
+ hcintmsk_t hc_int_msk;
+ hcintn_t hc_int;
+ u32 dma_addr;
+ hcchar_t hc_char;
+ hctsiz_t hc_size;
+}hc_info_t;//, *hc_info_t *, **hc_info_t **;
+
+#ifndef USB_MAXCHILDREN
+ #define USB_MAXCHILDREN (31)
+#endif
+
+typedef struct _usb_hub_descriptor_t
+{
+ 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];
+}usb_hub_descriptor_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h b/drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h
new file mode 100644
index 0000000..b2b3bee
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-common-errorcode.h
@@ -0,0 +1,115 @@
+/****************************************************************************
+ * (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 _ERROR_CODE_DEF_H
+#define _ERROR_CODE_DEF_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-common.h"
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+typedef int USB_ERROR_CODE;
+
+//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.
+ ************************************************/
+
+//#ifdef LINUX
+
+#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
+
+//#else
+
+//#endif
+
+
+
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h b/drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h
new file mode 100644
index 0000000..ed119d7
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-common-regdef.h
@@ -0,0 +1,302 @@
+/****************************************************************************
+* (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
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef struct {
+ u32 OPHYPWR;
+ u32 OPHYCLK;
+ u32 ORSTCON;
+}OTG_PHY_REG, *PS_OTG_PHY_REG;
+
+#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
+
+// Device Logical Endpoints-Specific Registers
+
+#define DIEPCTL 0x900 // Device IN Endpoint 0 Control
+#define DOEPCTL(n) 0xB00 + ((n)*0x20)) // Device OUT Endpoint 0 Control
+#define DIEPINT(n) 0x908 + ((n)*0x20)) // Device IN Endpoint 0 Interrupt
+#define DOEPINT(n) 0xB08 + ((n)*0x20)) // Device OUT Endpoint 0 Interrupt
+#define DIEPTSIZ(n) 0x910 + ((n)*0x20)) // Device IN Endpoint 0 Transfer Size
+#define DOEPTSIZ(n) 0xB10 + ((n)*0x20)) // Device OUT Endpoint 0 Transfer Size
+#define DIEPDMA(n) 0x914 + ((n)*0x20)) // Device IN Endpoint 0 DMA Address
+#define DOEPDMA(n) 0xB14 + ((n)*0x20)) // Device OUT Endpoint 0 DMA Address
+
+#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
+
+#define EP_INTERRUPT_DISABLE_ALL 0x0 // Bits to write to EP_INT_EN_REG - Use CLEAR
+
+// 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)
+
+// For USB DMA
+//BOOL InitUsbdDriverGlobals(void); //:-)
+//void UsbdDeallocateVm(void); //:-)
+//BOOL UsbdAllocateVm(void); //:-)
+//void UsbdInitDma(int epnum, int bufIndex,int bufOffset); //:-)
+
+
+//by Kevin
+
+//////////////////////////////////////////////////////////////////////////
+
+/*
+inline u32 ReadReg(
+ u32 uiOffset
+ )
+{
+ volatile u32 *pbReg = ctrlr_base_reg_addr(uiOffset);
+ u32 uiValue = (u32) *pbReg;
+ return uiValue;
+}
+
+inline void WriteReg(
+ u32 uiOffset,
+ u32 bValue
+ )
+{
+ volatile ULONG *pbReg = ctrlr_base_reg_addr(uiOffset);
+ *pbReg = (ULONG) bValue;
+}
+
+inline void UpdateReg(
+ u32 uiOffset,
+ u32 bValue
+ )
+{
+ WriteReg(uiOffset, (ReadReg(uiOffset) | bValue));
+};
+
+inline void ClearReg(
+ u32 uiOffeset,
+ u32 bValue
+ )
+{
+ WriteReg(uiOffeset, (ReadReg(uiOffeset) & ~bValue));
+};
+*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h
new file mode 100644
index 0000000..20416b1
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-debug.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+ * (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 _S3C_OTG_HCDI_DEBUG_H_
+#define _S3C_OTG_HCDI_DEBUG_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#define OTG_DEBUG
+
+#ifdef OTG_DEBUG
+#if 0
+#include <linux/stddef.h>
+#endif
+
+#define OTG_DBG_OTGHCDI_DRIVER true
+#define OTG_DBG_OTGHCDI_HCD false
+#define OTG_DBG_OTGHCDI_KAL false
+#define OTG_DBG_OTGHCDI_LIST false
+#define OTG_DBG_OTGHCDI_MEM false
+
+#define OTG_DBG_TRANSFER false
+#define OTG_DBG_SCHEDULE false
+#define OTG_DBG_OCI false
+#define OTG_DBG_DONETRASF false
+#define OTG_DBG_ISR false
+#define OTG_DBG_ROOTHUB false
+
+
+#include <linux/kernel.h> //for printk
+
+#define otg_err(is_active, msg...) \
+ do{ if (/*(is_active) == */true)\
+ {\
+ pr_err("otg_err: in %s()::%05d ", __func__ , __LINE__); \
+ pr_err("=> " msg); \
+ }\
+ }while(0)
+
+#define otg_dbg(is_active, msg...) \
+ do{ if ((is_active) == true)\
+ {\
+ pr_info("otg_dbg: in %s()::%05d ", __func__, __LINE__); \
+ pr_info("=> " msg); \
+ }\
+ }while(0)
+
+#else //OTG_DEBUG
+
+# define otg_err(is_active, msg...) do{}while(0)
+# define otg_dbg(is_active, msg...) do{}while(0)
+
+#endif
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _S3C_OTG_HCDI_DEBUG_H_ */
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c
new file mode 100644
index 0000000..f99261f
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.c
@@ -0,0 +1,386 @@
+/****************************************************************************
+ * (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. \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
+ ****************************************************************************/
+
+#include "s3c-otg-hcdi-driver.h"
+#include "../../gadget/s3c_udc.h"
+extern void otg_phy_off(void);
+
+/**
+ * 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 \n
+ * USB_ERR_FAIL : If fail \n
+ * @remark
+ * it allocates resources of it and call other modules' init function.
+ * then call usb_create_hcd, usb_add_hcd, s5pc110_otghcd_start functions
+ */
+
+struct usb_hcd* g_pUsbHcd = NULL;
+
+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) {
+ hdata->set_pwr_cb(0);
+#ifdef CONFIG_USB_HOST_NOTIFY
+ if (g_pUsbHcd)
+ host_state_notify(&g_pUsbHcd->ndev, NOTIFY_HOST_OVERCURRENT);
+#endif
+ } else {
+ otg_err(true, "invalid otghost data\n");
+ }
+}
+
+static int s5pc110_otg_drv_probe (struct platform_device *pdev)
+{
+ int ret_val = 0;
+ u32 reg_val = 0;
+ struct sec_otghost *otghost = NULL;
+ struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev);
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s3c_otg_drv_probe\n");
+
+ reset_scheduler_numbers();
+ /*init for host mode*/
+ /**
+ Allocate memory for the base HCD & Initialize the base HCD.
+ */
+ g_pUsbHcd = usb_create_hcd(&s5pc110_otg_hc_driver, &pdev->dev,
+ "s3cotg");/*pdev->dev.bus_id*/
+ if (g_pUsbHcd == NULL) {
+ ret_val = -ENOMEM;
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "failed to usb_create_hcd\n");
+ goto err_out_clk;
+ }
+
+ /* mapping hcd resource & device resource*/
+
+ 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");
+ ret_val = -EBUSY;
+ goto err_out_create_hcd;
+ }
+
+
+ /* Physical address => Virtual address */
+ g_pUsbHcd->regs = S3C_VA_OTG;
+ g_pUsbHcd->self.otg_port = 1;
+
+ g_pUDCBase = (u8 *)g_pUsbHcd->regs;
+
+ 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;
+
+ if ((s3c_get_drivermode()) & USB_OTG_DRIVER_S3CFSLS) {
+ otghost->is_hs = 0; // force USB 1.x mode
+ } else {
+ otghost->is_hs = 1;
+ }
+
+ INIT_WORK(&otghost->work, otg_power_work);
+ otghost->wq = create_singlethread_workqueue("sec_otghostd");
+
+ /* call others' init() */
+ 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);
+ 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_HOST_NOTIFY
+ if (otg_data->host_notify) {
+ g_pUsbHcd->host_notify = otg_data->host_notify;
+ g_pUsbHcd->ndev.name = dev_name(&pdev->dev);
+ ret_val = host_notify_dev_register(&g_pUsbHcd->ndev);
+ if (ret_val) {
+ otg_err(OTG_DBG_OTGHCDI_DRIVER,
+ "Failed to host_notify_dev_register\n");
+ goto err_out_create_hcd_init;
+ }
+ }
+#endif
+#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
+
+ /*
+ * 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) {
+ goto err_out_host_notify_register;
+ }
+
+ 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(); */
+
+ wake_lock_init(&otghost->wake_lock, WAKE_LOCK_SUSPEND, "usb_otg");
+ wake_lock(&otghost->wake_lock);
+
+ return USB_ERR_SUCCESS;
+err_out_host_notify_register:
+#ifdef CONFIG_USB_HOST_NOTIFY
+ host_notify_dev_unregister(&g_pUsbHcd->ndev);
+#endif
+
+err_out_create_hcd_init:
+ otg_hcd_deinit_modules(otghost);
+ release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len);
+
+err_out_create_hcd:
+ usb_put_hcd(g_pUsbHcd);
+
+err_out_clk:
+
+ return ret_val;
+}
+
+/**
+ * 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 \n
+ * USB_ERR_FAIL : If fail \n
+ * @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 *dev)
+{
+ struct sec_otghost *otghost = NULL;
+
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s5pc110_otg_drv_remove\n");
+
+ otghost = hcd_to_sec_otghost(g_pUsbHcd);
+
+#ifdef CONFIG_USB_HOST_NOTIFY
+ host_notify_dev_unregister(&g_pUsbHcd->ndev);
+#endif
+
+ otg_hcd_deinit_modules(otghost);
+
+ destroy_workqueue(otghost->wq);
+
+ wake_unlock(&otghost->wake_lock);
+ wake_lock_destroy(&otghost->wake_lock);
+
+ usb_remove_hcd(g_pUsbHcd);
+
+ release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len);
+
+ usb_put_hcd(g_pUsbHcd);
+
+ otg_phy_off();
+
+ 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 */
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "USB_CONTROL = 0x%x.\n", readl(0xfb10e80c));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "UPHYPWR = 0x%x.\n", readl(S3C_USBOTG_PHYPWR));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "UPHYCLK = 0x%x.\n", readl(S3C_USBOTG_PHYCLK));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "URSTCON = 0x%x.\n", readl(S3C_USBOTG_RSTCON));
+
+ /* OTG LINK Core registers (Core Global Registers) */
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GOTGCTL = 0x%x.\n", read_reg_32(GOTGCTL));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GOTGINT = 0x%x.\n", read_reg_32(GOTGINT));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GAHBCFG = 0x%x.\n", read_reg_32(GAHBCFG));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GUSBCFG = 0x%x.\n", read_reg_32(GUSBCFG));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GINTSTS = 0x%x.\n", read_reg_32(GINTSTS));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GINTMSK = 0x%x.\n", read_reg_32(GINTMSK));
+
+ /* Host Mode Registers */
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "HCFG = 0x%x.\n", read_reg_32(HCFG));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "HPRT = 0x%x.\n", read_reg_32(HPRT));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "HFIR = 0x%x.\n", read_reg_32(HFIR));
+
+ /* Synopsys ID */
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GSNPSID = 0x%x.\n", read_reg_32(GSNPSID));
+
+ /* HWCFG */
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GHWCFG1 = 0x%x.\n", read_reg_32(GHWCFG1));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GHWCFG2 = 0x%x.\n", read_reg_32(GHWCFG2));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GHWCFG3 = 0x%x.\n", read_reg_32(GHWCFG3));
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "GHWCFG4 = 0x%x.\n", read_reg_32(GHWCFG4));
+
+ /* PCGCCTL */
+ otg_dbg(OTG_DBG_OTGHCDI_DRIVER,
+ "PCGCCTL = 0x%x.\n", read_reg_32(PCGCCTL));
+}
+
+/*
+module_init(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/s3c-otg/s3c-otg-hcdi-driver.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.h
new file mode 100644
index 0000000..4488df2
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-driver.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-driver.h
+ * @brief header of s3c-otg-hcdi-driver \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 _S3C_OTG_HCDI_DRIVER_H_
+#define _S3C_OTG_HCDI_DRIVER_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <linux/module.h>
+#include <linux/init.h>
+//#include <linux/clk.h> //for clk_get, clk_enable etc.
+
+#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 "s3c-otg-common-common.h"
+#include "s3c-otg-common-regdef.h"
+
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-hcdi-hcd.h"
+#include "s3c-otg-hcdi-kal.h"
+
+
+volatile u8 * g_pUDCBase;
+
+static const char gHcdName[] = "EMSP_OTG_HCD";
+
+//extern int otg_hcd_init_modules(struct sec_otghost *otghost);
+//extern void otg_hcd_deinit_modules(struct sec_otghost *otghost);
+
+//void otg_print_registers();
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _S3C_OTG_HCDI_DRIVER_H_ */
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c
new file mode 100644
index 0000000..2b440a3
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.c
@@ -0,0 +1,708 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-hcd.c
+ * @brief implementation of structure hc_drive \n
+ * @version
+ * -# Jun 11,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
+ ****************************************************************************/
+
+#include "s3c-otg-hcdi-hcd.h"
+
+/**
+ * otg_hcd_init_modules(struct sec_otghost *otghost)
+ *
+ * @brief call other modules' init functions
+ *
+ * @return PASS : If success \n
+ * FAIL : If fail \n
+ */
+int otg_hcd_init_modules(struct sec_otghost *otghost)
+{
+ unsigned long spin_lock_flag = 0;
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_init_modules\n");
+
+ spin_lock_init(&otghost->lock);
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ init_transfer();
+ init_scheduler();
+ oci_init(otghost);
+
+ spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ return USB_ERR_SUCCESS;
+};
+
+/**
+ * void otg_hcd_deinit_modules(struct sec_otghost *otghost)
+ *
+ * @brief call other modules' de-init functions
+ *
+ * @return PASS : If success \n
+ * FAIL : If fail \n
+ */
+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_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ deinit_transfer(otghost);
+
+ spin_unlock_irq_save_otg(&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 \n
+ */
+irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd)
+{
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_irq \n");
+
+ spin_lock_otg(&otghost->lock);
+ otg_handle_interrupt(hcd);
+ spin_unlock_otg(&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 \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+int s5pc110_otghcd_start(struct usb_hcd *usb_hcd_p)
+{
+ struct usb_bus *usb_bus_p;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(usb_hcd_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;
+ }
+
+ set_bit(HCD_FLAG_POLL_RH,&usb_hcd_p->flags);
+ usb_hcd_p->uses_new_polling = 1;
+
+ /* init bus state before enable irq */
+ usb_hcd_p->state = HC_STATE_RUNNING;
+
+ oci_start(otghost); /* 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
+ *
+ */
+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_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ oci_stop(otghost);
+ root_hub_feature(hcd, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL);
+
+ spin_unlock_irq_save_otg(&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
+ *
+ */
+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_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ oci_stop(otghost);
+ root_hub_feature(usb_hcd_p, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL);
+
+ spin_unlock_irq_save_otg(&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 \n
+ */
+int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd)
+{
+ int ret = 0;
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_get_frame_number \n");
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+ ret = oci_get_frame_num();
+ spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ 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 \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+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;
+ ed_t *target_ed = NULL;
+ isoch_packet_desc_t *new_isoch_packet_desc = NULL;
+ unsigned long spin_lock_flag = 0;
+ struct sec_otghost *otghost = hcd_to_sec_otghost(hcd);
+
+ if (!otghost->port_flag.b.port_connect_status) {
+ printk(KERN_ERR"%s %d\n", __func__, __LINE__);
+ return USB_ERR_NOIO;
+ }
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_urb_enqueue \n");
+
+ /* 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_irq_save_otg(&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_irq_save_otg(&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)) {
+ if (otghost->is_hs) // only allow split transactions in HS mode
+ 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_irq_save_otg(&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_irq_save_otg(&otghost->lock,
+ spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+
+ urb->ep->hcpriv = (void *)(target_ed);
+ } /* if(!(ep->hcpriv)) */
+ else {
+ dev_addr = usb_pipedevice(urb->pipe);
+ if(((ed_t *)(urb->ep->hcpriv))->ed_desc.device_addr != dev_addr) {
+ ((ed_t *)urb->ep->hcpriv)->ed_desc.device_addr = dev_addr;
+ }
+ }
+
+ target_ed = (ed_t *)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_irq_save_otg(&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_irq_save_otg(&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_irq_save_otg(&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_irq_save_otg(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+ urb->hcpriv = (void *)return_td_addr;
+
+ spin_unlock_irq_save_otg(&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 \n
+ * USB_ERR_FAIL : If call fail \n
+ */
+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;
+ td_t *cancel_td;
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_urb_dequeue \n");
+
+ /* Dequeue should be performed only if endpoint is enabled */
+ if (_urb->ep->enabled == 0) {
+ spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag);
+ usb_hcd_giveback_urb(_hcd, _urb, status);
+ return USB_ERR_SUCCESS;
+ }
+
+ //kevinh read this from inside the spinlock
+ cancel_td = (td_t *)_urb->hcpriv;
+
+ if (cancel_td == NULL) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "cancel_td is NULL\n");
+ spin_unlock_irq_save_otg(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+ otg_dbg(OTG_DBG_OTGHCDI_HCD,
+ "s5pc110_otghcd_urb_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_irq_save_otg(&otghost->lock, spin_lock_flag);
+ usb_hcd_giveback_urb(_hcd, _urb, status);
+ return ret_val;
+ }
+
+ if (!HC_IS_RUNNING(_hcd->state)) {
+ otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state) \n");
+ spin_unlock_irq_save_otg(&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_irq_save_otg(&otghost->lock, spin_lock_flag);
+ return USB_ERR_FAIL;
+ }
+ otg_usbcore_giveback(cancel_td);
+ delete_td(otghost, cancel_td);
+ spin_unlock_irq_save_otg(&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
+ */
+void s5pc110_otghcd_endpoint_disable(
+ struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ 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_endpoint_disable \n");
+
+ if(!((ed_t *)ep->hcpriv))
+ return;
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+ ret_val = delete_ed(otghost, (ed_t *)ep->hcpriv);
+ spin_unlock_irq_save_otg(&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 \n
+ */
+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_irq_save_otg(&otghost->lock, spin_lock_flag);
+ ret_val = get_otg_port_status(_hcd, OTG_PORT_NUMBER, _buf);
+ spin_unlock_irq_save_otg(&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 \n
+ */
+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_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ ret_val = root_hub_feature(hcd, OTG_PORT_NUMBER,
+ typeReq, value, (void *)buf_p);
+
+ spin_unlock_irq_save_otg(&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 \n
+ */
+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_irq_save_otg(&otghost->lock, spin_lock_flag);
+ bus_suspend();
+ spin_unlock_irq_save_otg(&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 \n
+ */
+int s5pc110_otghcd_bus_resume(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_resume \n");
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+
+ if(bus_resume(otghost) != USB_ERR_SUCCESS) {
+ return USB_ERR_FAIL;
+ }
+
+ spin_unlock_irq_save_otg(&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 \n
+ * ret_val : If call fail \n
+ */
+int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port)
+{
+ 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_start_port_reset \n");
+
+ spin_lock_irq_save_otg(&otghost->lock, spin_lock_flag);
+ ret_val = reset_and_enable_port(hcd, OTG_PORT_NUMBER);
+ spin_unlock_irq_save_otg(&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;
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h
new file mode 100644
index 0000000..95764d8
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-hcd.h
@@ -0,0 +1,152 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-hcd.h
+ * @brief header of s3c-otg-hcdi-hcd \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 _S3C_OTG_HCDI_HCD_H_
+#define _S3C_OTG_HCDI_HCD_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+//for IRQ_NONE (0) IRQ_HANDLED (1) IRQ_RETVAL(x) ((x) != 0)
+#include <linux/interrupt.h>
+
+#include <linux/usb.h>
+
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-hcdi-kal.h"
+
+#include "s3c-otg-common-common.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-common-const.h"
+
+#include "s3c-otg-transfer-transfer.h"
+#include "s3c-otg-oci.h"
+#include "s3c-otg-roothub.h"
+
+//placed in ISR
+//void otg_handle_interrupt(struct usb_hcd *hcd);
+
+irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd);
+
+int s5pc110_otghcd_start(struct usb_hcd *hcd);
+void s5pc110_otghcd_stop(struct usb_hcd *hcd);
+void s5pc110_otghcd_shutdown(struct usb_hcd *hcd);
+
+int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd);
+
+int s5pc110_otghcd_urb_enqueue(
+ struct usb_hcd *hcd,
+ struct urb *urb,
+ gfp_t mem_flags);
+
+int s5pc110_otghcd_urb_dequeue(
+ struct usb_hcd *_hcd,
+ struct urb *_urb,
+ int status);
+
+void s5pc110_otghcd_endpoint_disable(
+ struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep);
+
+int s5pc110_otghcd_hub_status_data(
+ struct usb_hcd *_hcd,
+ char *_buf);
+
+int s5pc110_otghcd_hub_control(
+ struct usb_hcd *hcd,
+ u16 type_req,
+ u16 value,
+ u16 index,
+ char * buf,
+ u16 length);
+
+int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd);
+int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd);
+int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port);
+
+/**
+ * @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,
+};
+
+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);
+}
+
+int otg_hcd_init_modules(struct sec_otghost *otghost);
+void otg_hcd_deinit_modules(struct sec_otghost *otghost);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _S3C_OTG_HCDI_HCD_H_ */
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h
new file mode 100644
index 0000000..23bded6
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-kal.h
@@ -0,0 +1,420 @@
+/****************************************************************************
+ * (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 _S3C_OTG_HCDI_KAL_H_
+#define _S3C_OTG_HCDI_KAL_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-common-common.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-common-const.h"
+
+#include <asm/io.h> //for readl, writel
+#include <linux/usb/ch9.h> //for usb_device_driver, enum usb_device_speed
+#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;
+
+#include <linux/spinlock.h>
+#define SPINLOCK_t spinlock_t
+#define SPIN_LOCK_INIT SPIN_LOCK_UNLOCKED
+
+#define spin_lock_otg(lock) spin_lock(lock)
+#define spin_lock_irg_otg(lock) spin_lock_irq(lock)
+#define spin_lock_irq_save_otg(lock, flags) spin_lock_irqsave(lock, flags)
+
+#define spin_unlock_otg(lock) spin_unlock(lock)
+#define spin_unlock_irq_otg(lock) spin_unlock_irq(lock)
+#define spin_unlock_irq_save_otg(lock, flags) spin_unlock_irqrestore(lock, flags)
+
+#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
+(
+ ed_t *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
+(
+ ed_t *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(td_t td_p)
+ *
+ * @brief give-back a td as urb
+ *
+ * @param [in] td_p : pointer of td_t to give back
+ *
+ * @return void \n
+ */
+static inline void
+otg_usbcore_giveback(td_t * 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 for otg_usbcore_inc_periodic_transfer_cnt()\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 for otg_usbcore_des_periodic_transfer_cnt()\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));
+}
+//---------------------------------------------------------------------------------------
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _S3C_OTG_HCDI_KAL_H_ */
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h
new file mode 100644
index 0000000..03ae3d8
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-list.h
@@ -0,0 +1,217 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * @file s3c-otg-hcdi-list.h
+ * @brief list functions for otg \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_LIST_H_
+#define _S3C_OTG_HCDI_LIST_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "s3c-otg-common-common.h"
+#include "s3c-otg-hcdi-debug.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 *new_node_p, otg_list_head *list_head_p)
+ *
+ * @brief push a list node into the next of head
+ *
+ * @param [in] new_node_p : 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 *new_node_p, otg_list_head *list_head_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_next \n");
+ list_add(new_node_p, list_head_p);
+}
+//-------------------------------------------------------------------------------
+
+/**
+ * void otg_list_push_prev(otg_list_head *new_node_p, otg_list_head *list_head_p)
+ *
+ * @brief push a list node into the previous of head
+ *
+ * @param [in] new_node_p : 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 *new_node_p, otg_list_head *list_head_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_prev \n");
+ list_add_tail(new_node_p, list_head_p);
+}
+//-------------------------------------------------------------------------------
+
+/**
+ * void otg_list_pop(otg_list_head *list_entity_p)
+ *
+ * @brief pop a list node
+ *
+ * @param [in] new_node_p : 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");
+ list_del(list_entity_p);
+}
+//-------------------------------------------------------------------------------
+
+/**
+ * void otg_list_move_next(otg_list_head *node_p, otg_list_head *list_head_p)
+ *
+ * @brief move a list to next of head
+ *
+ * @param [in] new_node_p : 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_head_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_next \n");
+ list_move(node_p, list_head_p);
+}
+//-------------------------------------------------------------------------------
+
+/**
+ * void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list_head_p)
+ *
+ * @brief move a list to previous of head
+ *
+ * @param [in] new_node_p : 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_head_p)
+{
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_prev \n");
+ list_move_tail(node_p, list_head_p);
+}
+//-------------------------------------------------------------------------------
+
+/**
+ * bool otg_list_empty(otg_list_head *list_head_p)
+ *
+ * @brief check a list empty or not
+ *
+ * @param [in] list_head_p : node to check
+ *
+ * @return true : empty list \n
+ * false : not empty list
+ */
+static inline
+bool otg_list_empty(otg_list_head *list_head_p)
+{
+
+ otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_empty \n");
+ if(list_empty(list_head_p))
+ 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;
+}
+//-------------------------------------------------------------------------------
+
+/*
+void otg_list_push_next(otg_list_head *new_node_p, otg_list_head *list_head_p );
+void otg_list_push_prev(otg_list_head *new_node_p, otg_list_head *list_head_p);
+void otg_list_pop(otg_list_head *list_entity_p);
+
+void otg_list_move_next(otg_list_head *node_p, otg_list_head *list_head_p);
+void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list_head_p);
+
+bool otg_list_empty(otg_list_head *list_head_p);
+void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p);
+void otg_list_init(otg_list_head *list_p);
+*/
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _S3C_OTG_HCDI_LIST_H_ */
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h
new file mode 100644
index 0000000..c87f15e
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-hcdi-memory.h
@@ -0,0 +1,191 @@
+/****************************************************************************
+ * (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_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "s3c-otg-common-common.h"
+#include "s3c-otg-hcdi-debug.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
+ */
+typedef
+enum otg_mem_alloc_flag
+{
+ USB_MEM_SYNC, USB_MEM_ASYNC, USB_MEM_DMA
+}otg_mem_alloc_flag_t;
+//---------------------------------------------------------------------------------------
+
+/*
+inline int otg_mem_alloc(void ** addr_pp, u16 byte_size, otg_mem_alloc_flag_t type);
+inline int otg_mem_copy(void * to_addr_p, void * from_addr_p, u16 byte_size);
+//inline int otg_mem_free(void * addr_p);
+inline int otg_mem_set(void * addr_p, char value, u16 byte_size);
+*/
+
+/**
+ * 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 : otg_mem_alloc_flag_t 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,
+ otg_mem_alloc_flag_t 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 otg_mem_alloc_flag_t in otg_mem_alloc \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;
+}
+//-------------------------------------------------------------------------------
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _S3C_OTG_HCDI_MEMORY_H_ */
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-isr.c b/drivers/usb/host/s3c-otg/s3c-otg-isr.c
new file mode 100644
index 0000000..6bb790d
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-isr.c
@@ -0,0 +1,294 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : Isr.c
+ * [Description] : The file implement the external and internal functions of ISR
+ * [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 ISR
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * 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-otg-isr.h"
+
+/**
+ * void otg_handle_interrupt(void)
+ *
+ * @brief Main interrupt processing routine
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+
+__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(otghost);
+ }
+
+ 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);
+}
+
+/**
+ * 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
+ *
+ */
+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
+ *
+ */
+void unmask_channel_interrupt(u32 ch_num, u32 mask_info)
+{
+ update_reg_32(HCINTMSK(ch_num),mask_info);
+}
+
+/**
+ * int get_ch_info(hc_info_t * 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
+ *
+ */
+int get_ch_info(hc_info_t *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
+ *
+ */
+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
+ *
+ */
+void clear_ch_intr(u8 ch_num, u32 clear_bit)
+{
+ update_reg_32(HCINT(ch_num),clear_bit);
+}
+
+/**
+ * void enable_sof(void)
+ *
+ * @brief Generate SOF Interrupt.
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+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
+ *
+ */
+void disable_sof(void)
+{
+ gintmsk_t gintmsk = {.d32 = 0};
+ gintmsk.b.sofintr = 1;
+ clear_reg_32(GINTMSK, gintmsk.d32);
+}
+
+/*Internal function of isr */
+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);
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-isr.h b/drivers/usb/host/s3c-otg/s3c-otg-isr.h
new file mode 100644
index 0000000..cad4cf5
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-isr.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] :s3c-otg-isr.h
+ * [Description] : The Header file defines the external and internal functions of ISR.
+ * [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/18 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * - Created this file and defines functions of Scheduler
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * 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 _ISR_H_
+#define _ISR_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-common-regdef.h"
+#include "s3c-otg-hcdi-kal.h"
+#include "s3c-otg-scheduler-scheduler.h"
+#include "s3c-otg-transferchecker-common.h"
+#include "s3c-otg-roothub.h"
+#include "s3c-otg-oci.h"
+
+__inline__ void otg_handle_interrupt(struct usb_hcd *hcd);
+
+void process_port_intr(struct usb_hcd *hcd);
+
+void mask_channel_interrupt(u32 ch_num, u32 mask_info);
+
+void unmask_channel_interrupt(u32 ch_num, u32 mask_info);
+
+extern int get_ch_info(hc_info_t *hc_reg, u8 ch_num);
+
+extern void get_intr_ch(u32 *haint, u32 *haintmsk);
+
+extern void clear_ch_intr(u8 ch_num, u32 clear_bit);
+
+extern void enable_sof(void);
+
+extern void disable_sof(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _ISR_H_ */
+
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-oci.c b/drivers/usb/host/s3c-otg/s3c-otg-oci.c
new file mode 100644
index 0000000..0744b9e
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-oci.c
@@ -0,0 +1,815 @@
+/****************************************************************************
+ * (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
+ ****************************************************************************/
+
+#include "s3c-otg-oci.h"
+
+static bool ch_enable[16];
+
+/**
+ * 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
+ *
+ */
+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;
+}
+
+/**
+ * int oci_core_init(struct sec_otghost *otghost)
+ *
+ * @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
+ *
+ */
+int oci_core_init(struct sec_otghost *otghost)
+{
+ 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);
+
+ // per
+ // http://forum.xda-developers.com/showthread.php?t=709135&page=21
+ // we need a 25msec delay after setting this -kevinh
+
+ mdelay(100);
+
+ 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(struct sec_otghost *otghost)
+ *
+ * @brief Process host initialize as s5pc110 spec
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+int oci_host_init(struct sec_otghost *otghost)
+{
+ 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);
+
+ if (!otghost->is_hs) {
+ hcfg.b.fslssupp = 1; // force USB 1.x mode
+ } else {
+ hcfg.b.fslssupp = 0;
+ }
+ 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_start(struct sec_otghost *otghost)
+ *
+ * @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
+ *
+ */
+int oci_start(struct sec_otghost *otghost)
+{
+ otg_dbg(OTG_DBG_OCI, "oci_start \n");
+
+ if(oci_core_init(otghost) == USB_ERR_SUCCESS) {
+ mdelay(50);
+ if(oci_init_mode(otghost) == 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(struct sec_otghost *otghost)
+ *
+ * @brief stop to opearte otg core
+ *
+ * @param None
+ *
+ * @return USB_ERR_SUCCESS : If success \n
+ * USB_ERR_FAIL : If call fail \n
+ * @remark
+ *
+ */
+int oci_stop(struct sec_otghost *otghost)
+{
+ 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;
+}
+
+/**
+ * oci_start_transfer(struct sec_otghost *otghost, stransfer_t *st_t)
+ *
+ * @brief start transfer by using transfer information to receive from scheduler
+ *
+ * @param [IN] *st_t - 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, stransfer_t *st_t)
+{
+ hcchar_t hcchar = {.d32 = 0};
+
+ otg_dbg(OTG_DBG_OCI, "oci_start_transfer \n");
+
+ if(st_t->alloc_chnum ==CH_NONE) {
+
+ if( oci_channel_alloc(&(st_t->alloc_chnum)) == USB_ERR_SUCCESS) {
+ oci_channel_init(st_t->alloc_chnum, st_t);
+
+ hcchar.b.chen = 1;
+ update_reg_32(HCCHAR(st_t->alloc_chnum), hcchar.d32);
+ return st_t->alloc_chnum;
+ }
+ else {
+ otg_dbg(OTG_DBG_OCI,
+ "oci_start_transfer Fail - Channel Allocation Error\n");
+ return CH_NONE;
+ }
+ }
+ else {
+ oci_channel_init(st_t->alloc_chnum, st_t);
+
+ hcchar.b.chen = 1;
+ update_reg_32(HCCHAR(st_t->alloc_chnum), hcchar.d32);
+
+ return st_t->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);
+
+ //kevinh: wait for Channel Disabled Interrupt
+ count = 0;
+ do {
+ hcintmsk.d32 = read_reg_32(HCINT(ch_num));
+ if(count > max_error_count) {
+ printk("chhltd int never occurred! ch=%d, intreg=0x%x\n",
+ ch_num, hcintmsk.d32);
+ break;
+ }
+ count++;
+ } while(!hcintmsk.b.chhltd);
+
+ 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;
+}
+
+/**
+ * int oci_channel_init( u8 ch_num, stransfer_t *st_t)
+ *
+ * @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
+ *
+ */
+int oci_channel_init( u8 ch_num, stransfer_t *st_t)
+{
+ u32 intr_enable = 0;
+ gintmsk_t gintmsk = {.d32 = 0};
+ hcchar_t hcchar = {.d32 = 0};
+ hctsiz_t hctsiz = {.d32 = 0};
+ hcsplt_t hcsplt = {.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),st_t->hc_reg.hc_int_msk.d32);
+
+ /* Program the HCSIZn register with the endpoint characteristics for */
+ hctsiz.b.xfersize = st_t->buf_size;
+ hctsiz.b.pktcnt = st_t->packet_cnt;
+
+ /* Program the HCCHARn register with the endpoint characteristics for */
+ hcchar.b.mps = st_t->ed_desc_p->max_packet_size;
+ hcchar.b.epnum = st_t->ed_desc_p->endpoint_num;
+ hcchar.b.epdir = st_t->ed_desc_p->is_ep_in;
+ hcchar.b.lspddev = (st_t->ed_desc_p->dev_speed == LOW_SPEED_OTG);
+ hcchar.b.eptype = st_t->ed_desc_p->endpoint_type;
+ hcchar.b.multicnt = st_t->ed_desc_p->mc;
+ hcchar.b.devaddr = st_t->ed_desc_p->device_addr;
+
+ if(st_t->ed_desc_p->endpoint_type == INT_TRANSFER ||
+ st_t->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(st_t->ed_desc_p->endpoint_type == CONTROL_TRANSFER) {
+ td_t *td_p;
+ td_p = (td_t *)st_t->parent_td;
+
+ switch(td_p->standard_dev_req_info.conrol_transfer_stage) {
+ case SETUP_STAGE:
+ hctsiz.b.pid = st_t->ed_status_p->control_data_tgl.setup_tgl;
+ hcchar.b.epdir = EP_OUT;
+ break;
+ case DATA_STAGE:
+ hctsiz.b.pid = st_t->ed_status_p->control_data_tgl.data_tgl;
+ hcchar.b.epdir = st_t->ed_desc_p->is_ep_in;
+ break;
+ case STATUS_STAGE:
+ hctsiz.b.pid = st_t->ed_status_p->control_data_tgl.status_tgl;
+
+ if(td_p->standard_dev_req_info.is_data_stage) {
+ hcchar.b.epdir = ~(st_t->ed_desc_p->is_ep_in);
+ }
+ else {
+ hcchar.b.epdir = EP_IN;
+ }
+ break;
+ default:break;
+ }
+ }
+ else {
+ hctsiz.b.pid = st_t->ed_status_p->data_tgl;
+ }
+
+ hctsiz.b.dopng = st_t->ed_status_p->is_ping_enable;
+ write_reg_32(HCTSIZ(ch_num),hctsiz.d32);
+ st_t->ed_status_p->is_ping_enable = false;
+
+ /* Write DMA Address */
+ write_reg_32(HCDMA(ch_num),st_t->start_phy_buf_addr);
+
+ /* Wrote HCCHAR Register */
+ write_reg_32(HCCHAR(ch_num),hcchar.d32);
+
+ /* sztupy: Enable split transaction support if driver is in HS mode */
+ if (st_t->ed_desc_p->is_do_split) {
+ printk("splittrans");
+ hcsplt.b.spltena = 1;
+ hcsplt.b.hubaddr = st_t->ed_desc_p->hub_addr;
+ hcsplt.b.prtaddr = st_t->ed_desc_p->hub_port;
+ hcsplt.b.xactpos = st_t->ed_status_p->split_pos;
+ hcsplt.b.compsplt = st_t->ed_status_p->is_complete_split;
+ }
+ write_reg_32(HCSPLT(ch_num),hcsplt.d32);
+
+ 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);
+}
+
+/* OCI Internal Functions */
+
+int oci_channel_alloc(u8 *ch_num)
+{
+ u8 ch;
+
+ hcchar_t hcchar = {.d32 = 0};
+
+ 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;
+}
+
+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;
+}
+
+int oci_sys_init(struct sec_otghost *otghost)
+{
+ otg_host_phy_init();
+
+ return USB_ERR_SUCCESS;
+}
+
+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);
+ }
+}
+
+int oci_init_mode(struct sec_otghost *otghost)
+{
+ 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(otghost) == 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(otghost) == 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;
+}
+
+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();
+}
+
+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);
+}
+
+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);
+}
+
+int oci_core_reset(void)
+{
+ u32 count = 0;
+ grstctl_t greset = {.d32 = 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_dev_init(struct sec_otghost *otghost)
+{
+ otg_dbg(OTG_DBG_OCI, "oci_dev_init - do nothing.\n");
+ /* return USB_ERR_FAIL; */
+ return USB_ERR_SUCCESS;
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-oci.h b/drivers/usb/host/s3c-otg/s3c-otg-oci.h
new file mode 100644
index 0000000..03c97e9
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-oci.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+ * (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_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+//#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-regdef.h"
+#include "s3c-otg-hcdi-kal.h"
+#include "s3c-otg-hcdi-memory.h"
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-roothub.h"
+#include "s3c-otg-hcdi-hcd.h"
+
+#include <mach/map.h> //virtual address for smdk
+
+extern void otg_host_phy_init(void);
+#include <mach/regs-clock.h>
+
+//OCI interace
+int oci_init(struct sec_otghost *otghost);
+
+int oci_start(struct sec_otghost *otghost);
+int oci_stop(struct sec_otghost *otghost);
+
+u8 oci_start_transfer(struct sec_otghost *otghost, stransfer_t *st_t);
+int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num);
+
+int oci_channel_init(u8 ch_num, stransfer_t *st_t);
+u32 oci_get_frame_num(void);
+u16 oci_get_frame_interval(void);
+void oci_set_frame_interval(u16 intervl);
+
+///OCI Internal Functions
+int oci_sys_init(struct sec_otghost *otghost);
+int oci_core_init(struct sec_otghost *otghost);
+int oci_init_mode(struct sec_otghost *otghost);
+int oci_host_init(struct sec_otghost *otghost);
+int oci_dev_init(struct sec_otghost *otghost);
+
+int oci_channel_alloc(u8 *ch_num);
+int oci_channel_dealloc(u8 ch_num);
+
+void oci_config_flush_fifo(u32 mode);
+void oci_flush_tx_fifo(u32 num);
+void oci_flush_rx_fifo(void);
+
+int oci_core_reset(void);
+void oci_set_global_interrupt(bool set);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _OCI_H_ */
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-roothub.c b/drivers/usb/host/s3c-otg/s3c-otg-roothub.c
new file mode 100644
index 0000000..7d9d289
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-roothub.c
@@ -0,0 +1,605 @@
+/* for* ==========================================================================
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS 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.
+ * ========================================================================== */
+/****************************************************************************
+ * (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
+ ****************************************************************************/
+
+#include "s3c-otg-roothub.h"
+
+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
+ *
+ */
+__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
+ *
+ */
+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
+ *
+ */
+__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;
+ usb_hub_descriptor_t *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 the driver's internal 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 = (usb_hub_descriptor_t *)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;
+}
+
+/**
+ * void bus_suspend(void)
+ *
+ * @brief Make suspend status when this platform support PM Mode
+ *
+ * @param None
+ *
+ * @return None
+ *
+ * @remark
+ *
+ */
+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
+ *
+ */
+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(otghost) == USB_ERR_SUCCESS) {
+ otg_dbg(OTG_DBG_ROOTHUB, "OTG Init Success\n");
+ return USB_ERR_SUCCESS;
+ }
+ }
+
+ return USB_ERR_FAIL;
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-roothub.h b/drivers/usb/host/s3c-otg/s3c-otg-roothub.h
new file mode 100644
index 0000000..03883d1
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-roothub.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] :s3c-otg-roothub.h
+ * [Description] : The Header file defines 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]: 2008/06/13
+ * [Revision History]
+ * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com }
+ * - Created this file and defines 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
+ ****************************************************************************/
+
+#ifndef _ROOTHUB_H_
+#define _ROOTHUB_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-regdef.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-hcdi-kal.h"
+#include "s3c-otg-hcdi-memory.h"
+#include "s3c-otg-oci.h"
+
+__inline__ int root_hub_feature(
+ struct usb_hcd *hcd,
+ const u8 port,
+ const u16 type_req,
+ const u16 feature,
+ void *buf);
+
+__inline__ int get_otg_port_status(
+ struct usb_hcd *hcd, const u8 port, char *status);
+
+int reset_and_enable_port(struct usb_hcd *hcd, const u8 port);
+void bus_suspend(void);
+
+int bus_resume(struct sec_otghost *otghost);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _ROOTHUB_H_ */
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c
new file mode 100644
index 0000000..d94fb1a
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-ischeduler.c
@@ -0,0 +1,369 @@
+/****************************************************************************
+ * (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 "s3c-otg-scheduler-scheduler.h"
+
+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(ed_t *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, td_t *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 td_t 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, td_t *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, td_t *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 td_t 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, td_t *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(td_t *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 td_t 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(td_t *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;
+
+ }
+ else
+ {
+ /* this case is not support.... */
+ }
+ }
+
+ return USB_ERR_SUCCESS;
+}
+
+/******************************************************************************/
+/*!
+ * @name int deallocate(td_t *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 td_t at ed_t, deallocate() insert the ed_t to TransferReadyQ.
+ *
+ * @param [IN] deallocate_td = indicates the pointer ot the td_t 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(td_t *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.... */
+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 td_t at TransferringTDArray[chnum]
+ *
+ * @param [IN] chnum = indicates the index of TransferringTDArray
+ * to include the address of td_t which we gets
+ * [OUT] td_addr_p= indicate pointer to store the address of td_t.
+ *
+ * @return USB_ERR_SUCCESS -if success to get the address of td_t.
+ * USB_ERR_FAIL -if fail to get the address of td_t.
+ */
+ /******************************************************************************/
+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/s3c-otg/s3c-otg-scheduler-readyq.c b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-readyq.c
new file mode 100644
index 0000000..7b41f0c
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-readyq.c
@@ -0,0 +1,264 @@
+/****************************************************************************
+ * (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
+ ****************************************************************************/
+
+#include "s3c-otg-scheduler-scheduler.h"
+
+static trans_ready_q_t periodic_trans_ready_q;
+static trans_ready_q_t nonperiodic_trans_ready_q;
+
+/******************************************************************************/
+/*!
+ * @name void init_transfer_ready_q(void)
+ *
+ * @brief this function initiates PeriodicTransferReadyQ and NonPeriodicTransferReadyQ.
+ *
+ *
+ * @param void
+ *
+ * @return void.
+ */
+/******************************************************************************/
+void init_transfer_ready_q(void)
+{
+ otg_dbg(OTG_DBG_SCHEDULE,"start init_transfer_ready_q\n");
+
+ otg_list_init(&periodic_trans_ready_q.trans_ready_q_list_head);
+ periodic_trans_ready_q.is_periodic_transfer = true;
+ periodic_trans_ready_q.trans_ready_entry_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.trans_ready_q_list_head);
+ nonperiodic_trans_ready_q.is_periodic_transfer = false;
+ nonperiodic_trans_ready_q.trans_ready_entry_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(ed_t *insert_ed,
+ * bool f_isfirst)
+ *
+ * @brief this function inserts ed_t * to TransferReadyQ.
+ *
+ *
+ * @param [IN] insert_ed = indicates the ed_t to be inserted to TransferReadyQ.
+ * [IN] f_isfirst = 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.
+ */
+/******************************************************************************/
+int insert_ed_to_ready_q(ed_t *insert_ed,
+ bool f_isfirst)
+{
+
+ if(insert_ed->ed_desc.endpoint_type == BULK_TRANSFER||
+ insert_ed->ed_desc.endpoint_type == CONTROL_TRANSFER)
+ {
+ if(f_isfirst)
+ {
+ otg_list_push_next(&insert_ed->trans_ready_q_list_entry,&nonperiodic_trans_ready_q.trans_ready_q_list_head);
+ }
+ else
+ {
+ otg_list_push_prev(&insert_ed->trans_ready_q_list_entry,&nonperiodic_trans_ready_q.trans_ready_q_list_head);
+ }
+ nonperiodic_trans_ready_q.trans_ready_entry_num++;
+ }
+ else
+ {
+ if(f_isfirst)
+ {
+ otg_list_push_next(&insert_ed->trans_ready_q_list_entry,&periodic_trans_ready_q.trans_ready_q_list_head);
+ }
+ else
+ {
+ otg_list_push_prev(&insert_ed->trans_ready_q_list_entry,&periodic_trans_ready_q.trans_ready_q_list_head);
+ }
+ periodic_trans_ready_q.trans_ready_entry_num++;
+ }
+
+ return USB_ERR_SUCCESS;
+
+}
+
+
+u32 get_periodic_ready_q_entity_num(void)
+{
+ return periodic_trans_ready_q.trans_ready_entry_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.
+ */
+/******************************************************************************/
+int remove_ed_from_ready_q(ed_t *remove_ed)
+{
+// SPINLOCK_t SLForRemoveED_t = SPIN_LOCK_INIT;
+// u32 uiSLFlag=0;
+
+ otg_list_pop(&remove_ed->trans_ready_q_list_entry);
+
+ if(remove_ed->ed_desc.endpoint_type == BULK_TRANSFER||
+ remove_ed->ed_desc.endpoint_type == CONTROL_TRANSFER)
+ {
+// spin_lock_irq_save_otg(&SLForRemoveED_t, uiSLFlag);
+// otg_list_pop(&remove_ed->trans_ready_q_list_entry);
+ nonperiodic_trans_ready_q.trans_ready_entry_num--;
+// spin_unlock_irq_save_otg(&SLForRemoveED_t, uiSLFlag);
+ }
+ else
+ {
+// spin_lock_irq_save_otg(&SLForRemoveED_t, uiSLFlag);
+// otg_list_pop(&remove_ed->trans_ready_q_list_entry);
+ periodic_trans_ready_q.trans_ready_entry_num--;
+// spin_unlock_irq_save_otg(&SLForRemoveED_t, uiSLFlag);
+ }
+
+ return USB_ERR_SUCCESS;
+
+}
+
+//by ss1 unused func
+/*
+bool check_ed_on_ready_q(ed_t *check_ed_p)
+{
+
+ if(check_ed_p->ed_status.is_in_transfer_ready_q)
+ return true;
+ else
+ return false;
+}*/
+
+/******************************************************************************/
+/*!
+ * @name int get_ed_from_ready_q(bool f_isperiodic,
+ * td_t **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] f_isperiodic = 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.
+ */
+/******************************************************************************/
+
+int get_ed_from_ready_q(bool f_isperiodic,
+ ed_t **get_ed)
+{
+ if(f_isperiodic)
+ {
+ otg_list_head *transreadyq_list_entity=NULL;
+
+ if(periodic_trans_ready_q.trans_ready_entry_num==0)
+ {
+ return USB_ERR_NO_ENTITY;
+ }
+
+ transreadyq_list_entity = periodic_trans_ready_q.trans_ready_q_list_head.next;
+
+ //if(transreadyq_list_entity!= &periodic_trans_ready_q.trans_ready_q_list_head)
+ if(!otg_list_empty(&periodic_trans_ready_q.trans_ready_q_list_head))
+ {
+ *get_ed = otg_list_get_node(transreadyq_list_entity,ed_t,trans_ready_q_list_entry);
+ if (transreadyq_list_entity->prev == LIST_POISON2 ||
+ transreadyq_list_entity->next == LIST_POISON1) {
+ printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n");
+ periodic_trans_ready_q.trans_ready_entry_num =0;
+ }
+ else {
+ otg_list_pop(transreadyq_list_entity);
+ periodic_trans_ready_q.trans_ready_entry_num--;
+ }
+
+ return USB_ERR_SUCCESS;
+ }
+ else
+ {
+ return USB_ERR_NO_ENTITY;
+ }
+ }
+ else
+ {
+ otg_list_head *transreadyq_list_entity=NULL;
+
+ if(nonperiodic_trans_ready_q.trans_ready_entry_num==0)
+ {
+ return USB_ERR_NO_ENTITY;
+ }
+
+ transreadyq_list_entity = nonperiodic_trans_ready_q.trans_ready_q_list_head.next;
+
+ //if(transreadyq_list_entity!= &nonperiodic_trans_ready_q.trans_ready_q_list_head)
+ if(!otg_list_empty(&nonperiodic_trans_ready_q.trans_ready_q_list_head))
+ {
+ *get_ed = otg_list_get_node(transreadyq_list_entity,ed_t, trans_ready_q_list_entry);
+ if (transreadyq_list_entity->prev == LIST_POISON2 ||
+ transreadyq_list_entity->next == LIST_POISON1) {
+ printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n");
+ nonperiodic_trans_ready_q.trans_ready_entry_num =0;
+ }
+ else {
+ otg_list_pop(transreadyq_list_entity);
+ nonperiodic_trans_ready_q.trans_ready_entry_num--;
+ }
+
+ return USB_ERR_SUCCESS;
+ }
+ else
+ {
+ return USB_ERR_NO_ENTITY;
+ }
+ }
+}
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c
new file mode 100644
index 0000000..034cec4
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.c
@@ -0,0 +1,424 @@
+/****************************************************************************
+ * (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) \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 "s3c-otg-scheduler-scheduler.h"
+
+//Define constant variables
+
+//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;
+
+ //Define global variables
+// kevinh: add volatile
+static volatile u32 perio_used_bustime = 0;
+static volatile u8 perio_used_chnum = 0;
+static volatile u8 nonperio_used_chnum = 0;
+static volatile u8 total_used_chnum = 0;
+static volatile u32 transferring_td_array[16]={0};
+
+void reset_scheduler_numbers(void) {
+ total_chnum_threshold = 16;
+ perio_used_bustime = 0;
+ perio_used_chnum = 0;
+ nonperio_used_chnum = 0;
+ total_used_chnum = 0;
+ memset(transferring_td_array,0,sizeof(transferring_td_array));
+}
+
+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;
+}
+
+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;
+ }
+}
+
+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;
+}
+
+u8 get_avail_chnum(void)
+{
+ return total_chnum_threshold - total_used_chnum;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+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 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, ed_t *insert_ed)
+{
+ if(!insert_ed->is_need_to_insert_scheduler)
+ return USB_ERR_ALREADY_EXIST;
+
+ 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;
+}
+
+/******************************************************************************/
+/*!
+ * @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
+ */
+/******************************************************************************/
+void do_periodic_schedule(struct sec_otghost *otghost)
+{
+ ed_t *scheduling_ed= NULL;
+ int err_sched = USB_ERR_SUCCESS;
+ u32 sched_cnt = 0;
+
+ otg_dbg(OTG_DBG_SCHEDULE,"*******Start to DoPeriodicSchedul*********\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;
+
+ err_sched = get_ed_from_ready_q(true, &scheduling_ed);
+
+ if(err_sched==USB_ERR_SUCCESS) {
+ otg_list_head *td_list_entry;
+ td_t *td;
+ u32 cur_frame_num = 0;
+
+ otg_dbg(OTG_DBG_SCHEDULE,"the ed_t to be scheduled :%d",(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 td_t. so we schedules another ed_t 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_t 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, td_t, td_list_entry);
+
+ if((!td->is_transferring) && (!td->is_transfer_done)) {
+ u8 alloc_ch;
+ otg_dbg(OTG_DBG_SCHEDULE,"the td_t 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;
+ set_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 the ed_t 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;
+ }
+
+ }
+ else { // the selected td_t was already transferring or completed to transfer.
+ //we should decide how to control this case.
+ goto end_sched_perio_transfer;
+ }
+ }
+ else {
+ // there is no ED on PeriodicTransferQ. So we finish scheduling.
+ 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_t.
+ * if there are some ed_t on NonPeriodicTransferReadyQ
+ * , this function request to start USB Trasnfer to S3C6400OCI.
+ *
+ *
+ * @param void
+ *
+ * @return void
+ */
+/******************************************************************************/
+void do_nonperiodic_schedule(struct sec_otghost *otghost)
+{
+ if(total_used_chnum<total_chnum_threshold) {
+ ed_t *scheduling_ed;
+ int err_sched;
+
+ while(1) {
+
+start_sched_nonperio_transfer:
+
+ //check there is available channel resource for Non-Periodic Transfer.
+ if(total_used_chnum==total_chnum_threshold) {
+ goto end_sched_nonperio_transfer;
+ }
+
+ err_sched = get_ed_from_ready_q(false, &scheduling_ed);
+
+ if(err_sched ==USB_ERR_SUCCESS ) {
+ otg_list_head *td_list_entry;
+ td_t *td;
+
+ td_list_entry = scheduling_ed->td_list_entry.next;
+
+ //if(td_list_entry == &scheduling_ed->td_list_entry)
+ if(otg_list_empty(&scheduling_ed->td_list_entry)) {
+ //scheduling_ed has no td_t. so we schedules another ed_t on PeriodicTransferReadyQ.
+ goto start_sched_nonperio_transfer;
+ }
+
+ if(scheduling_ed->ed_status.is_in_transferring) {
+ //scheduling_ed is already Scheduled. so we schedules another ed_t on PeriodicTransferReadyQ.
+ goto start_sched_nonperio_transfer;
+ }
+
+ td = otg_list_get_node(td_list_entry, td_t, td_list_entry);
+
+ if((!td->is_transferring) && (!td->is_transfer_done)) {
+ u8 alloc_ch;
+
+ alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer);
+
+ if(alloc_ch<total_chnum_threshold) {
+ td->cur_stransfer.alloc_chnum = alloc_ch;
+ set_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 should insert the ed_t to TransferReadyQ, because the USB Transfer of the ed_t 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_sched_nonperio_transfer;
+ }
+ }
+ else {
+ goto end_sched_nonperio_transfer;
+ }
+ }
+ else { //there is no ed_t on NonPeriodicTransferReadyQ.
+ //So, we finish do_nonperiodic_schedule().
+ goto end_sched_nonperio_transfer;
+ }
+ }
+ }
+
+end_sched_nonperio_transfer:
+
+ return;
+}
+
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h
new file mode 100644
index 0000000..f9905fa
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-scheduler-scheduler.h
@@ -0,0 +1,100 @@
+/****************************************************************************
+ * (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) \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
+ ****************************************************************************/
+
+#ifndef _SCHEDULER_H
+#define _SCHEDULER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-hcdi-memory.h"
+#include "s3c-otg-hcdi-kal.h"
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-oci.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+//Defines external functions of IScheduler.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(ed_t *remove_ed);
+extern int cancel_to_transfer_td(struct sec_otghost *otghost, td_t *cancel_td);
+extern int retransmit(struct sec_otghost *otghost, td_t *retransmit_td);
+extern int reschedule(td_t *resched_td);
+extern int deallocate(td_t *dealloc_td);
+extern void do_schedule(struct sec_otghost *otghost);
+extern int get_td_info(u8 chnum,unsigned int *td_addr);
+
+// Defines functiions of TranferReadyQ.
+void init_transfer_ready_q(void);
+int insert_ed_to_ready_q(ed_t *insert_ed, bool f_isfirst);
+int remove_ed_from_ready_q(ed_t *remove_ed);
+int get_ed_from_ready_q(bool f_isperiodic, ed_t **get_ed);
+
+//Define functions of Scheduler
+void do_periodic_schedule(struct sec_otghost *otghost);
+void do_nonperiodic_schedule(struct sec_otghost *otghost);
+int set_transferring_td_array(u8 chnum, u32 td_addr);
+int get_transferring_td_array(u8 chnum, unsigned int *td_addr);
+
+
+//Define fuctions to manage some static global variable.
+int inc_perio_bus_time(u32 uiBusTime, u8 dev_speed);
+int dec_perio_bus_time(u32 uiBusTime);
+
+u8 get_avail_chnum(void);
+int inc_perio_chnum(void);
+int dec_perio_chnum(void);
+int inc_non_perio_chnum(void);
+int dec_nonperio_chnum(void);
+u32 get_periodic_ready_q_entity_num(void);
+
+int insert_ed_to_scheduler(struct sec_otghost *otghost, ed_t *insert_ed);
+
+void reset_scheduler_numbers(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c b/drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c
new file mode 100644
index 0000000..93b6349
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-common.c
@@ -0,0 +1,810 @@
+/****************************************************************************
+ * (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 )n
+ * - Optimizing for performance \n
+ * (3) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com )
+ * - Modifying for successful rmmod & disconnecting \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 "s3c-otg-transfer-transfer.h"
+
+// the header pointer to indicate the ED_list to manage the ed_t to be created and initiated.
+static otg_list_head ed_list_head;
+static u32 ref_periodic_transfer;
+
+/******************************************************************************/
+/*!
+ * @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_t to be existed.
+ *
+ * @param void
+ *
+ * @return void
+ */
+/******************************************************************************/
+
+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_t on ED_list_head. if some ed_t exists, deinit_transfer() deletes the ed_t.
+ *
+ *
+ * @param void
+ *
+ * @return void
+ */
+/******************************************************************************/
+void deinit_transfer(struct sec_otghost *otghost)
+{
+ otg_list_head *ed_list_member;
+ ed_t *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,ed_t,ed_list_entry);
+
+ delete_ed(otghost, delete_ed_p);
+ }
+}
+
+/******************************************************************************/
+/*!
+ * @name int delete_ed(ed_t *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 td_t
+ *
+ *
+ * @param [IN] delete_ed = indicates the address of ed_t to be deleted.
+ *
+ * @return USB_ERR_SUCCESS -if successes to delete the ed_t.
+ * USB_ERR_FAILl -if fails to delete the ed_t.
+ */
+/******************************************************************************/
+int delete_ed(struct sec_otghost *otghost, ed_t *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 int delete_td(struct sec_otghost *otghost, td_t *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 S3CScheduler.
+ *
+ *
+ * @param [OUT] new_td_p = returns the address of the new td_t .
+ *
+ * @return USB_ERR_SUCCESS -if successes to create the new td_t.
+ * USB_ERR_FAILl -if fails to create to new td_t.
+ */
+/******************************************************************************/
+int delete_td(struct sec_otghost *otghost, td_t *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;
+}
+
+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);
+}
+
+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 init_isoch_packet_desc.
+ *
+ * @return void
+ */
+/******************************************************************************/
+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;
+}
+
+/******************************************************************************/
+/*!
+ * @name int create_ed(ed_t **new_ed)
+ *
+ * @brief this function creates a new ed_t and returns the ed_t to Caller
+ *
+ *
+ * @param [OUT] new_ed = returns the address of the new ed_t .
+ *
+ * @return USB_ERR_SUCCESS -if successes to create the new ed_t.
+ * USB_ERR_FAILl -if fails to create to new ed_t.
+ */
+/******************************************************************************/
+int create_ed(ed_t **new_ed)
+{
+ int err_code = USB_ERR_SUCCESS;
+
+ err_code = otg_mem_alloc((void **)new_ed,(u16)sizeof(ed_t), USB_MEM_ASYNC);
+ otg_mem_set(*new_ed, 0, sizeof(ed_t));
+ return err_code;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int init_ed( ed_t *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 ed_t 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_t.
+ * USB_ERR_FAILl -if fails to initiate the ed_t.
+ * USB_ERR_NOSPACE -if fails to initiate the ed_t
+ * because there is no USB Resource for this init_ed.
+ */
+/******************************************************************************/
+int init_ed(ed_t *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_tgl = DATA0;
+ break;
+
+ case CONTROL_TRANSFER:
+ init_ed->ed_status.control_data_tgl.setup_tgl = SETUP;
+ init_ed->ed_status.control_data_tgl.data_tgl = DATA1;
+ init_ed->ed_status.control_data_tgl.status_tgl = DATA1;
+ break;
+
+ case ISOCH_TRANSFER:
+ if(f_is_ep_in) {
+ switch(multi_count) {
+ case MULTI_COUNT_ZERO :
+ init_ed->ed_status.data_tgl = DATA0;
+ break;
+ case MULTI_COUNT_ONE :
+ init_ed->ed_status.data_tgl = DATA1;
+ break;
+ case MULTI_COUNT_TWO :
+ init_ed->ed_status.data_tgl = DATA2;
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ switch(multi_count) {
+ case MULTI_COUNT_ZERO :
+ init_ed->ed_status.data_tgl = DATA0;
+ break;
+ case MULTI_COUNT_ONE :
+ init_ed->ed_status.data_tgl = MDATA;
+ break;
+ case MULTI_COUNT_TWO :
+ init_ed->ed_status.data_tgl = 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;
+
+// sztupy: split transaction support
+ init_ed->ed_status.is_complete_split = false;
+ init_ed->ed_status.split_pos = ED_STATUS_SPLIT_POS_ALL;
+ init_ed->ed_status.split_offset = 0;
+
+ //push the ed_t 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(td_t **new_td)
+ *
+ * @brief this function creates a new td_t and returns the td_t to Caller
+ *
+ *
+ * @param [OUT] new_td = returns the address of the new td_t .
+ *
+ * @return USB_ERR_SUCCESS -if successes to create the new td_t.
+ * USB_ERR_FAILl -if fails to create to new td_t.
+ */
+/******************************************************************************/
+int create_td(td_t **new_td)
+{
+ int err_code = USB_ERR_SUCCESS;
+
+ err_code = otg_mem_alloc((void **)new_td,(u16)sizeof(td_t), USB_MEM_ASYNC);
+ otg_mem_set(*new_td, 0, sizeof(td_t));
+ return err_code;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int init_td( td_t *init_td,
+ * ed_t *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 td_t to be initiated.
+ * [IN] parent_ed - indicate the ed_t 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 USB Standard Request
+ * [IN] phy_setup - the physical address of buffer to store the USB Standard Request.
+ * [IN] vir_setup - the virtual address of buffer to store the USB Standard 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 of linux.
+ * td_priv stores the urb of linux.
+ *
+ * @return USB_ERR_SUCCESS -if successes to initiate the new td_t.
+ * USB_ERR_FAILl -if fails to create to new td_t.
+ */
+/******************************************************************************/
+int init_td( td_t *init_td,
+ ed_t *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)
+{
+ 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 td_t 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,
+ * ed_t *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_t 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 USB Standard Request.
+ * [IN] setup_phy_addr - the physical address of buffer to store the USB Standard 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] 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 of linux.
+ * td_priv stores the urb of linux.
+ * [OUT] return_td_addr - indicates the variable address to store the new td_t for this transfer
+ *
+ * @return USB_ERR_SUCCESS -if successes to initiate the new td_t.
+ * USB_ERR_FAILl -if fails to create to new td_t.
+ */
+/******************************************************************************/
+int issue_transfer(struct sec_otghost *otghost,
+ ed_t *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)
+{
+ td_t *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;
+ }
+}
+
+/******************************************************************************/
+/*!
+ * @name int cancel_transfer(struct sec_otghost *otghost,
+ * ed_t *parent_ed,
+ * td_t *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 td_t at parent_ed, then this function requests to release
+ * some usb resources for the ed_t to S3CScheduler. finally this function deletes the cancel_td.
+ *
+ * @param [IN] pUpdateTD = indicates the pointer ot the td_t 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.
+ */
+ /******************************************************************************/
+int cancel_transfer(struct sec_otghost *otghost,
+ ed_t *parent_ed,
+ td_t *cancel_td)
+{
+ int err = USB_ERR_DEQUEUED;
+ otg_list_head *tmp_list_p, *tmp_list2_p;
+ bool cond_found = false;
+
+ if(parent_ed == NULL || cancel_td == NULL) {
+ otg_dbg(OTG_DBG_TRANSFER, "parent_ed == NULL || cancel_td == NULL\n");
+ cancel_td->error_code = USB_ERR_NOELEMENT;
+ otg_usbcore_giveback(cancel_td);
+ return cancel_td->error_code;
+ }
+
+ 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;
+ }
+
+ }
+ // kevinh - even if the record was in the ready queue it is important to delete it as well. We can also always remove the ed from the scheduler
+ // once all tds have been removed
+ 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);
+ }
+ }
+
+ if(parent_ed->num_td) {
+ // kevinh - we do not want to force insert_scheduler, because if this endpoint _was_ already scheduled
+ // because the deleted td was not the active td then we will now put ed into the scheduler list twice, thus
+ // corrupting it.
+ // 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 ed_t.
+ //but, not release memory for this ed_t.
+ 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;
+ // kevinh - fixed bug, the caller should take care of calling delete_td because they might still want to do some
+ // operations on that memory
+ // delete_td(cancel_td);
+ // otg_usbcore_giveback(cancel_td);
+
+ErrorStatus:
+
+ return err;
+}
+
+
+/******************************************************************************/
+/*!
+ * @name int cancel_all_td(struct sec_otghost *otghost, ed_t *parent_ed)
+ *
+ * @brief this function cancels all Transfer which parent_ed manages.
+ *
+ * @param [IN] parent_ed = indicates the pointer ot the ed_t 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.
+ */
+ /******************************************************************************/
+int cancel_all_td(struct sec_otghost *otghost, ed_t *parent_ed)
+{
+ otg_list_head *cancel_td_list_entry;
+ td_t *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,td_t, td_list_entry);
+
+ if(cancel_transfer(otghost, parent_ed, cancel_td) == USB_ERR_DEQUEUED)
+ // kevinh FIXME - do we also need to giveback?
+ delete_td(otghost,cancel_td);
+ } while(parent_ed->num_td);
+
+ return USB_ERR_SUCCESS;
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c b/drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c
new file mode 100644
index 0000000..013a2a2
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-nonperiodic.c
@@ -0,0 +1,140 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : NonPeriodicTransfer.c
+ * [Description] : This source file implements the functions to be defined at NonPeriodicTransfer Module.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/07
+ * [Revision History]
+ * (1) 2008/06/07 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements some functions of NonPeriodicTransfer.
+ * -# 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 "s3c-otg-transfer-transfer.h"
+
+/******************************************************************************/
+/*!
+ * @name int init_nonperio_stransfer( bool f_is_standard_dev_req,
+ * td_t *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 transfer is Control or not.
+ * [IN] parent_td = indicates the address of td_t 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.
+ */
+ /******************************************************************************/
+int init_nonperio_stransfer(bool f_is_standard_dev_req,
+ td_t *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(hc_reg_t));
+
+ 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.start_phy_buf_addr = parent_td->standard_dev_req_info.phy_standard_dev_req_addr;
+ parent_td->cur_stransfer.start_vir_buf_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.start_phy_buf_addr = parent_td->phy_buf_addr;
+ parent_td->cur_stransfer.start_vir_buf_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 void update_nonperio_stransfer(td_t *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 td_t 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.
+ */
+ /******************************************************************************/
+void update_nonperio_stransfer(td_t *parent_td)
+{
+ switch(parent_td->parent_ed_p->ed_desc.endpoint_type) {
+ case BULK_TRANSFER:
+ parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr+parent_td->transferred_szie;
+ parent_td->cur_stransfer.start_vir_buf_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.start_phy_buf_addr = parent_td->standard_dev_req_info.phy_standard_dev_req_addr;
+ parent_td->cur_stransfer.start_vir_buf_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.start_phy_buf_addr = parent_td->phy_buf_addr+parent_td->transferred_szie;
+ parent_td->cur_stransfer.start_vir_buf_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.start_phy_buf_addr = 0;
+ parent_td->cur_stransfer.start_vir_buf_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);
+
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c b/drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c
new file mode 100644
index 0000000..f254b78
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-periodic.c
@@ -0,0 +1,128 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : NonPeriodicTransfer.c
+ * [Description] : This source file implements the functions to be defined at NonPeriodicTransfer Module.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/09
+ * [Revision History]
+ * (1) 2008/06/09 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and implements some functions of PeriodicTransfer.
+ * -# 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 "s3c-otg-transfer-transfer.h"
+
+
+/******************************************************************************/
+/*!
+ * @name int init_perio_stransfer( bool f_is_isoch_transfer,
+ * td_t *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 td_t 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.
+ */
+ /******************************************************************************/
+int init_perio_stransfer( bool f_is_isoch_transfer,
+ td_t *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(hc_reg_t));
+
+ 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.start_phy_buf_addr =parent_td->phy_buf_addr + parent_td->isoch_packet_desc_p[0].isoch_packiet_start_addr;
+ parent_td->cur_stransfer.start_vir_buf_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.start_phy_buf_addr =parent_td->phy_buf_addr;
+ parent_td->cur_stransfer.start_vir_buf_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 void update_perio_stransfer(td_t *parent_td)
+ *
+ * @brief this function updates the parent_td->cur_stransfer to be used by S3COCI.
+ * the STransfer of parent_td is for Periodic Transfer.
+ *
+ * @param [IN/OUT]parent_td = indicates the pointer of td_t 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.
+ */
+ /******************************************************************************/
+void update_perio_stransfer(td_t *parent_td)
+{
+ switch(parent_td->parent_ed_p->ed_desc.endpoint_type) {
+ case INT_TRANSFER:
+ parent_td->cur_stransfer.start_phy_buf_addr = parent_td->phy_buf_addr+parent_td->transferred_szie;
+ parent_td->cur_stransfer.start_vir_buf_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.start_phy_buf_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.start_vir_buf_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;
+ }
+}
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h b/drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h
new file mode 100644
index 0000000..6e1532c
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transfer-transfer.h
@@ -0,0 +1,144 @@
+/****************************************************************************
+ * (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) \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
+ ****************************************************************************/
+
+#ifndef _TRANSFER_H
+#define _TRANSFER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+//#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-hcdi-memory.h"
+#include "s3c-otg-hcdi-kal.h"
+#include "s3c-otg-hcdi-debug.h"
+
+#include "s3c-otg-scheduler-scheduler.h"
+#include "s3c-otg-isr.h"
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+void init_transfer(void);
+void deinit_transfer(struct sec_otghost *otghost);
+
+int issue_transfer(struct sec_otghost *otghost,
+ ed_t *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);
+
+int cancel_transfer(struct sec_otghost *otghost,
+ ed_t *parent_ed,
+ td_t *cancel_td);
+
+int cancel_all_td(struct sec_otghost *otghost, ed_t *parent_ed);
+
+int create_ed(ed_t ** new_ed);
+
+int init_ed(ed_t * 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);
+
+int delete_ed(struct sec_otghost *otghost, ed_t *delete_ed);
+
+int delete_td(struct sec_otghost *otghost, td_t * delete_td);
+
+int create_isoch_packet_desc( isoch_packet_desc_t **new_isoch_packet_desc,
+ u32 isoch_packet_num);
+
+int delete_isoch_packet_desc( isoch_packet_desc_t *del_isoch_packet_desc,
+ u32 isoch_packet_num);
+
+
+void init_isoch_packet_desc( isoch_packet_desc_t *init_isoch_packet_desc,
+ u32 offset,
+ u32 isoch_packet_size,
+ u32 index);
+
+// NonPeriodicTransfer.c implements.
+
+int init_nonperio_stransfer(bool f_is_standard_dev_req,
+ td_t *parent_td);
+
+void update_nonperio_stransfer(td_t *parent_td);
+
+//PeriodicTransfer.c implements
+
+int init_perio_stransfer( bool f_is_isoch_transfer,
+ td_t *parent_td);
+
+void update_perio_stransfer(td_t *parent_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;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c
new file mode 100644
index 0000000..e544f5c
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.c
@@ -0,0 +1,720 @@
+/****************************************************************************
+ * (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
+ ****************************************************************************/
+
+#include "s3c-otg-transferchecker-bulk.h"
+#include "s3c-otg-isr.h"
+
+/******************************************************************************/
+/*!
+ * @name u8 process_bulk_transfer(td_t *result_td,
+ hc_info_t *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 td_t 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,
+ */
+/******************************************************************************/
+u8 process_bulk_transfer(td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+ hcintn_t hc_intr_info;
+ u8 return_val=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)
+ {
+ return_val = process_chhltd_on_bulk(result_td, hc_reg_data);
+ }
+
+ else if (hc_intr_info.b.ack)
+ {
+ return_val =process_ack_on_bulk(result_td, hc_reg_data);
+ }
+
+ else if (hc_intr_info.b.nak)
+ {
+ return_val =process_nak_on_bulk(result_td, hc_reg_data);
+ }
+
+ else if (hc_intr_info.b.datatglerr)
+ {
+ return_val = process_datatgl_on_bulk(result_td,hc_reg_data);
+ }
+ }
+ else
+ {
+ if(hc_intr_info.b.chhltd)
+ {
+ return_val = process_chhltd_on_bulk(result_td, hc_reg_data);
+
+ }
+
+ else if(hc_intr_info.b.ack)
+ {
+ return_val =process_ack_on_bulk( result_td, hc_reg_data);
+ }
+ }
+
+ return return_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_chhltd_on_bulk(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function processes Channel Halt event according to Synopsys OTG Spec.
+ * 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 td_t 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.
+ */
+/******************************************************************************/
+u8 process_chhltd_on_bulk(td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+#if 0
+ if(result_td->parent_ed_p->ed_desc.is_ep_in)
+ {
+ 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
+ {
+ //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;
+ }
+ }
+ else
+ {
+ 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.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;
+ }
+
+
+
+ }
+#else
+ 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;
+ }
+#endif
+ return USB_ERR_SUCCESS;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xfercompl_on_bulk( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xfercompl event according to Synopsys OTG Spec.
+ * 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.
+ * BulkDataTgl.
+ * 5. calculates the tranferred size by calling calc_transferred_size() on DATA_STAGE.
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+u8 process_xfercompl_on_bulk(td_t *result_td,
+ hc_info_t *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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ 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(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theAHB Errorl event according to Synopsys OTG Spec.
+ * this function stop the channel to be executed
+ * TBD....
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+u8 process_ahb_on_bulk(td_t *result_td,
+ hc_info_t *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;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_stall_on_bulk(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theStall event according to Synopsys OTG Spec.
+ * when Stall is occured at Bulk Transfer, we should reset the PID as DATA0
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_stall_on_bulk( td_t * result_td,
+ hc_info_t * 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_datatgl(DATA0, result_td);
+
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nak_on_bulk(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nak event according to Synopsys OTG Spec.
+ * 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 td_t 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
+ */
+/******************************************************************************/
+u8 process_nak_on_bulk(td_t *result_td,
+ hc_info_t *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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ return RE_TRANSMIT;
+ }
+ return NO_ACTION;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ack_on_bulk(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the ack event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+u8 process_ack_on_bulk(td_t *result_td,
+ hc_info_t *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(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nyet event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+u8 process_nyet_on_bulk(td_t *result_td,
+ hc_info_t *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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ return RE_TRANSMIT;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xacterr_on_bulk( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xacterr event according to Synopsys OTG Spec.
+ * 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.
+ * 2. unmasks ack/nak/datatglerr bit of HCINTMSK.
+ * 3. clears the xacterr 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 td_t 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
+ */
+/******************************************************************************/
+u8 process_xacterr_on_bulk( td_t *result_td,
+ hc_info_t *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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ return ret_val;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name void process_bblerr_on_bulk(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the Babble event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_bblerr_on_bulk(td_t *result_td,
+ hc_info_t *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( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the datatglerr event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+u8 process_datatgl_on_bulk(td_t *result_td,
+ hc_info_t *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;
+
+}
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h
new file mode 100644
index 0000000..e1f84ca
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-bulk.h
@@ -0,0 +1,88 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : BulkTransferChecker.h
+ * [Description] : The Header file defines 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/18
+ * [Revision History]
+ * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines 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
+ ****************************************************************************/
+
+#ifndef _BULK_TRANSFER_CHECKER_H
+#define _BULK_TRANSFER_CHECKER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+u8 process_bulk_transfer(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_xfercompl_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_chhltd_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_ahb_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_stall_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_nak_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_ack_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_nyet_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_xacterr_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_bblerr_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_datatgl_on_bulk(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h
new file mode 100644
index 0000000..cefbcaf
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-checker.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : DoneTransferChecker.h
+ * [Description] : The Header file defines the external and internal functions of S3CDoneTransferChecker.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/12
+ * [Revision History]
+ * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines functions of S3CDoneTransferChecker
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * 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 _DONE_TRANSFER_CHECKER_H
+#define _DONE_TRANSFER_CHECKER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+
+#include "s3c-otg-hcdi-debug.h"
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
+
+
+
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c
new file mode 100644
index 0000000..689d379
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.c
@@ -0,0 +1,238 @@
+/****************************************************************************
+ * (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 "s3c-otg-transferchecker-common.h"
+
+/******************************************************************************/
+/*!
+ * @name int init_done_transfer_checker(void)
+ *
+ * @brief this function initiates S3CDoneTransferChecker Module.
+ *
+ *
+ * @param void
+ *
+ * @return void
+ */
+/******************************************************************************/
+//ss1
+/*void init_done_transfer_checker(void)
+{
+ return USB_ERR_SUCCESS;
+}*/
+
+/******************************************************************************/
+/*!
+ * @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_t to S3CScheduler.
+ * 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;
+
+ hc_info_t ch_info;
+ u32 td_addr = 0;
+ td_t *done_td = {0};
+ u8 proc_result = 0;
+
+ //by ss1
+ otg_mem_set((void *)&ch_info, 0, sizeof(hc_info_t));
+
+ // 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 td_t to have the channel to be interrupted.
+ if(get_td_info(do_try_cnt, &td_addr) == USB_ERR_SUCCESS) {
+
+ done_td = (td_t *)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_otg(&otghost->lock);
+ otg_usbcore_giveback( done_td);
+ spin_lock_otg(&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);
+}
+
+
+int release_trans_resource(struct sec_otghost *otghost, td_t *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;
+}
+
+u32 calc_transferred_size(bool f_is_complete, td_t *td, hc_info_t *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;
+ }
+}
+
+void update_frame_number(td_t *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;
+ }
+}
+
+void update_datatgl(u8 ubCurDataTgl, td_t *td)
+{
+ 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_tgl.data_tgl = ubCurDataTgl;
+ }
+ break;
+ case BULK_TRANSFER:
+ case INT_TRANSFER:
+ td->parent_ed_p->ed_status.data_tgl = ubCurDataTgl;
+ break;
+
+ case ISOCH_TRANSFER:
+ break;
+ default:break;
+ }
+}
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h
new file mode 100644
index 0000000..2ea05b8
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-common.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : CommonTransferChecker.h
+ * [Description] : The Header file defines the external and internal functions of CommonTransferChecker.
+ * [Author] : Yang Soon Yeal { syatom.yang@samsung.com }
+ * [Department] : System LSI Division/System SW Lab
+ * [Created Date]: 2008/06/12
+ * [Revision History]
+ * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines 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
+ ****************************************************************************/
+
+#ifndef _COMMON_TRANSFER_CHECKER_H
+#define _COMMON_TRANSFER_CHECKER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+#include "s3c-otg-common-common.h"
+//#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-common-regdef.h"
+#include "s3c-otg-transfer-transfer.h"
+
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-hcdi-memory.h"
+#include "s3c-otg-scheduler-scheduler.h"
+#include "s3c-otg-isr.h"
+#include "s3c-otg-transferchecker-control.h"
+#include "s3c-otg-transferchecker-bulk.h"
+#include "s3c-otg-transferchecker-interrupt.h"
+//#include "s3c-otg-transferchecker-iso.h"
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+//void init_done_transfer_checker (void);
+void do_transfer_checker (struct sec_otghost *otghost);
+int release_trans_resource(struct sec_otghost *otghost, td_t *done_td);
+u32 calc_transferred_size(bool f_is_complete,
+ td_t *td,
+ hc_info_t *hc_info);
+void update_frame_number(td_t *result_td);
+void update_datatgl(u8 cur_data_tgl,
+ td_t *td);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c
new file mode 100644
index 0000000..863f951
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.c
@@ -0,0 +1,698 @@
+/****************************************************************************
+ * (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
+ ****************************************************************************/
+
+#include "s3c-otg-transferchecker-control.h"
+#include "s3c-otg-isr.h"
+
+
+
+/******************************************************************************/
+/*!
+ * @name u8 process_control_transfer(td_t *result_td,
+ hc_info_t *hc_reg_data)
+
+ *
+ * @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 td_t to be mapped with the uChNum.
+ * [IN] ubChNum -indicates the number of the channel to be interrupted.
+ * [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.
+ */
+/******************************************************************************/
+u8 process_control_transfer(td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+ hcintn_t hcintr_info;
+ u8 ret_val=0;
+
+ //we just deal with the interrupts to be unmasked.
+ hcintr_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(hcintr_info.b.chhltd)
+ {
+ ret_val = process_chhltd_on_control(result_td, hc_reg_data);
+ }
+
+ else if (hcintr_info.b.ack)
+ {
+ ret_val =process_ack_on_control(result_td, hc_reg_data);
+ }
+
+ else if (hcintr_info.b.nak)
+ {
+ ret_val =process_nak_on_control(result_td, hc_reg_data);
+ }
+
+ else if (hcintr_info.b.datatglerr)
+ {
+ ret_val = process_datatgl_on_control(result_td,hc_reg_data);
+ }
+ }
+ else
+ {
+ if(hcintr_info.b.chhltd)
+ {
+ ret_val = process_chhltd_on_control(result_td, hc_reg_data);
+ }
+
+ else if(hcintr_info.b.ack)
+ {
+ ret_val =process_ack_on_control( result_td, hc_reg_data);
+
+ }
+ }
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_chhltd_on_control( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function processes Channel Halt event according to Synopsys OTG Spec.
+ * 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 td_t 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.
+ */
+/******************************************************************************/
+u8 process_chhltd_on_control( td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+ if(hc_reg_data->hc_int.b.xfercompl)
+ {
+ return process_xfercompl_on_control( result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.stall)
+ {
+ return process_stall_on_control(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.bblerr)
+ {
+ return process_bblerr_on_control(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.xacterr)
+ {
+ return process_xacterr_on_control(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.nak)
+ {
+ return process_nak_on_control(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.nyet)
+ {
+ return process_nyet_on_control(result_td, hc_reg_data);
+ }
+ 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_xfercompl_on_control(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xfercompl event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return USB_ERR_SUCCESS
+ */
+/******************************************************************************/
+u8 process_xfercompl_on_control(td_t *result_td, hc_info_t *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;
+
+ 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, hc_reg_data);
+
+ /* 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&& 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;
+ 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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+ ret_val = RE_TRANSMIT;
+ break;
+
+ case STATUS_STAGE:
+ result_td->standard_dev_req_info.conrol_transfer_stage = COMPLETE_STAGE;
+
+ 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;
+ }
+ }
+ }
+
+ ret_val = DE_ALLOCATE;
+ break;
+
+ default:
+ break;
+ }
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ahb_on_control( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theAHB Errorl event according to Synopsys OTG Spec.
+ * this function stop the channel to be executed
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_ahb_on_control( td_t *result_td,
+ hc_info_t *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 Control Transfer.
+ if(result_td->standard_dev_req_info.conrol_transfer_stage == DATA_STAGE)
+ {
+ result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data);
+ }
+
+ return DE_ALLOCATE;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_stall_on_control( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theStall event according to Synopsys OTG Spec.
+ * but USB2.0 Spec don't permit the Stall on Setup Stage of Control Transfer.
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_stall_on_control( td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+ 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, hc_reg_data);
+ }
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nak_on_control( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nak event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+u8 process_nak_on_control( td_t *result_td,
+ hc_info_t *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);
+
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+ }
+
+ 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( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the ack event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+u8 process_ack_on_control(td_t *result_td,
+ hc_info_t *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_control(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nyet event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+u8 process_nyet_on_control(td_t *result_td,
+ hc_info_t *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_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)
+ {
+ 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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ return RE_SCHEDULE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xacterr_on_control( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xacterr event according to Synopsys OTG Spec.
+ * 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.
+ * 2. unmasks ack/nak/datatglerr bit of HCINTMSK.
+ * 3. clears the xacterr 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 td_t 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 Counter is less than RETRANSMIT_THRESHOLD
+ * DE_ALLOCATE -if the Error Counter is equal to RETRANSMIT_THRESHOLD
+ */
+/******************************************************************************/
+u8 process_xacterr_on_control(td_t *result_td,
+ hc_info_t *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;
+ 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, hc_reg_data);
+
+ }
+
+ 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_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ return ret_val;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name void process_bblerr_on_control( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the Babble event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ * NO_ACTION -if the direction is OUT
+ */
+/******************************************************************************/
+u8 process_bblerr_on_control( td_t *result_td,
+ hc_info_t *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;
+
+ // 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, hc_reg_data);
+ }
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_datatgl_on_control(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the datatglerr event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] hc_reg_data -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+u8 process_datatgl_on_control( td_t * result_td,
+ hc_info_t * 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;
+
+}
+
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h
new file mode 100644
index 0000000..4d85367
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-control.h
@@ -0,0 +1,99 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : ControlTransferChecker.h
+ * [Description] : The Header file defines 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/01/12
+ * [Revision History]
+ * (1) 2008/06/13 by Yang Soon Yeal { syatom.yang@samsung.com }
+ * - Created this file and defines functions of ControlTransferChecker
+ *
+ ****************************************************************************/
+/****************************************************************************
+ * 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 _CONTROL_TRANSFER_CHECKER_H
+#define _CONTROL_TRANSFER_CHECKER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+#include "s3c-otg-common-common.h"
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-common-regdef.h"
+
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-scheduler-scheduler.h"
+
+#include "s3c-otg-transferchecker-checker.h"
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+u8 process_control_transfer( td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_xfercompl_on_control(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_chhltd_on_control( td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_ahb_on_control( td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_stall_on_control( td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_nak_on_control (td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_ack_on_control (td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_nyet_on_control( td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_xacterr_on_control(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_bblerr_on_control( td_t *raw_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_datatgl_on_control(td_t *raw_td,
+ hc_info_t *hc_reg_data);
+u8 process_indirection_on_control( td_t *result_td,
+ hc_info_t *hc_reg_data);
+
+u8 process_outdirection_on_control(td_t *result_td,
+ hc_info_t *hc_reg_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c
new file mode 100644
index 0000000..653628e
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.c
@@ -0,0 +1,574 @@
+/****************************************************************************
+ * (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
+ ****************************************************************************/
+
+
+#include "s3c-otg-transferchecker-interrupt.h"
+
+/******************************************************************************/
+/*!
+ * @name u8 process_intr_transfer(td_t *result_td,
+ * hc_info_t *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 td_t 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,
+ */
+/******************************************************************************/
+u8 process_intr_transfer(td_t *result_td, hc_info_t *hc_reg_data)
+{
+ hcintn_t hc_intr_info;
+ u8 ret_val=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_val = process_chhltd_on_intr(result_td, hc_reg_data);
+ }
+
+ else if (hc_intr_info.b.ack) {
+ ret_val = process_ack_on_intr(result_td, hc_reg_data);
+ }
+ }
+ else {
+ if(hc_intr_info.b.chhltd) {
+ ret_val = process_chhltd_on_intr(result_td, hc_reg_data);
+ }
+
+ else if(hc_intr_info.b.ack) {
+ ret_val = process_ack_on_intr( result_td, hc_reg_data);
+ }
+ }
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_chhltd_on_intr(td_t *result_td,
+ * hc_info_t *HCRegData)
+ *
+ *
+ * @brief this function processes Channel Halt event according to Synopsys OTG Spec.
+ * 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 td_t 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.
+ */
+/******************************************************************************/
+u8 process_chhltd_on_intr(td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+ if(result_td->parent_ed_p->ed_desc.is_ep_in)
+ {
+ if(hc_reg_data->hc_int.b.xfercompl)
+ {
+ return process_xfercompl_on_intr( result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.stall)
+ {
+ return process_stall_on_intr(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.bblerr)
+ {
+ return process_bblerr_on_intr(result_td, hc_reg_data);
+ }
+ else if (hc_reg_data->hc_int.b.nak)
+ {
+ return process_nak_on_intr(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.datatglerr)
+ {
+ return process_datatgl_on_intr(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.frmovrun)
+ {
+ return process_frmovrrun_on_intr(result_td,hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.xacterr)
+ {
+ return process_xacterr_on_intr(result_td, hc_reg_data);
+ }
+ else
+ {
+ 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);
+ return RE_TRANSMIT;
+ }
+ }
+ else
+ {
+ if(hc_reg_data->hc_int.b.xfercompl)
+ {
+ return process_xfercompl_on_intr( result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.stall)
+ {
+ return process_stall_on_intr(result_td, hc_reg_data);
+ }
+ else if (hc_reg_data->hc_int.b.nak)
+ {
+ return process_nak_on_intr(result_td, hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.frmovrun)
+ {
+ return process_frmovrrun_on_intr(result_td,hc_reg_data);
+ }
+ else if(hc_reg_data->hc_int.b.xacterr)
+ {
+ return process_xacterr_on_intr(result_td, hc_reg_data);
+ }
+ else
+ {
+ 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;
+ }
+
+ }
+
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xfercompl_on_intr( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the xfercompl event according to Synopsys OTG Spec.
+ * 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 td_t 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.
+ */
+/******************************************************************************/
+u8 process_xfercompl_on_intr( td_t *result_td,
+ hc_info_t *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->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
+ {
+ // this routine will not be executed on Interrupt Transfer.
+ // So, we should decide to remove this routine or not.
+ 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
+ { // the Data Stage is not completed. So we need to continue Data Stage.
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+ ret_val = RE_TRANSMIT;
+ }
+ }
+
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ return ret_val;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ahb_on_intr(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with theAHB Errorl event according to Synopsys OTG Spec.
+ * this function stop the channel to be executed
+ *
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_ahb_on_intr(td_t *result_td,
+ hc_info_t *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 Int 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;
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_stall_on_intr(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the Stall event according to Synopsys OTG Spec.
+ * when Stall is occured at Int Transfer, we should reset the PID as DATA0
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t to be mapped with the uChNum.
+ * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_stall_on_intr(td_t *result_td,
+ hc_info_t *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->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data);
+
+ update_datatgl(DATA0, result_td);
+
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_nak_on_intr(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the nak event according to Synopsys OTG Spec.
+ * 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 td_t 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
+ */
+/******************************************************************************/
+u8 process_nak_on_intr(td_t *result_td,
+ hc_info_t *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);
+
+
+ result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data);
+
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ update_frame_number(result_td);
+
+ return RE_SCHEDULE;
+// return RE_TRANSMIT;
+
+
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_ack_on_intr(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the ack event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return NO_ACTION
+ */
+/******************************************************************************/
+u8 process_ack_on_intr(td_t *result_td,
+ hc_info_t *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);
+
+ return NO_ACTION;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_xacterr_on_intr(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ * @brief this function deals with the xacterr event according to Synopsys OTG Spec.
+ * 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.
+ * 2. unmasks ack/nak/datatglerr bit of HCINTMSK.
+ * 3. clears the xacterr bit of HCINT
+ * 4. calculates the size of the transferred data.
+ * 5. update the Data Toggle.
+ * 6. update the frame number to start retransmitting the Interrupt Transfer.
+ *
+ * @param [IN] result_td -indicates the pointer of the td_t 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
+ */
+/******************************************************************************/
+u8 process_xacterr_on_intr(td_t *result_td,
+ hc_info_t *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_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, hc_reg_data);
+
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ if(ret_val == RE_SCHEDULE)
+ { //Calculates the frame number
+ update_frame_number(result_td);
+ }
+
+ return ret_val;
+}
+
+/******************************************************************************/
+/*!
+ * @name void process_bblerr_on_intr(td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ *
+ * @brief this function deals with the Babble event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return DE_ALLOCATE
+ */
+/******************************************************************************/
+u8 process_bblerr_on_intr(td_t *result_td,
+ hc_info_t *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->transferred_szie += calc_transferred_size(false, result_td, hc_reg_data);
+ return DE_ALLOCATE;
+}
+
+/******************************************************************************/
+/*!
+ * @name u8 process_datatgl_on_intr( td_t *result_td,
+ * hc_info_t *hc_reg_data)
+ *
+ * @brief this function deals with the datatglerr event according to Synopsys OTG Spec.
+ * 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 td_t to be mapped with the uChNum.
+ * [IN] HCRegData -indicates the interrupt information of the Channel to be interrupted
+ *
+ * @return RE_SCHEDULE
+ */
+/******************************************************************************/
+u8 process_datatgl_on_intr(td_t *result_td,
+ hc_info_t *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);
+
+ result_td->transferred_szie += calc_transferred_size(false,result_td, hc_reg_data);
+
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ update_frame_number(result_td);
+
+ return RE_SCHEDULE;
+}
+
+u8 process_frmovrrun_on_intr(td_t *result_td,
+ hc_info_t *hc_reg_data)
+{
+
+ clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK);
+
+ update_datatgl(hc_reg_data->hc_size.b.pid, result_td);
+
+ update_frame_number(result_td);
+
+ return RE_TRANSMIT;
+}
+
diff --git a/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h
new file mode 100644
index 0000000..0a735a2
--- /dev/null
+++ b/drivers/usb/host/s3c-otg/s3c-otg-transferchecker-interrupt.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+ * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved
+ *
+ * [File Name] : IntTransferChecker.h
+ * [Description] : The Header file defines 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 defines 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
+ ****************************************************************************/
+
+#ifndef _INT_TRANSFER_CHECKER_H
+#define _INT_TRANSFER_CHECKER_H
+
+/*
+// ----------------------------------------------------------------------------
+// Include files : None.
+// ----------------------------------------------------------------------------
+*/
+
+#include "s3c-otg-common-common.h"
+//#include "s3c-otg-common-typedef.h"
+#include "s3c-otg-common-const.h"
+#include "s3c-otg-common-errorcode.h"
+#include "s3c-otg-common-datastruct.h"
+#include "s3c-otg-common-regdef.h"
+
+#include "s3c-otg-hcdi-debug.h"
+#include "s3c-otg-scheduler-scheduler.h"
+#include "s3c-otg-isr.h"
+#include "s3c-otg-transferchecker-checker.h"
+
+
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+u8 process_intr_transfer(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_xfercompl_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_chhltd_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_ahb_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_stall_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_nak_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_frmovrrun_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_ack_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_xacterr_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_bblerr_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+u8 process_datatgl_on_intr(td_t *pRawTD,
+ hc_info_t *pHCRegData);
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif
+
diff --git a/drivers/usb/notify/Kconfig b/drivers/usb/notify/Kconfig
new file mode 100755
index 0000000..159affa
--- /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 100755
index 0000000..996eaa3
--- /dev/null
+++ b/drivers/usb/notify/Makefile
@@ -0,0 +1,4 @@
+
+# host notify driver
+obj-y += host_notify_class.o
+
diff --git a/drivers/usb/notify/host_notify_class.c b/drivers/usb/notify/host_notify_class.c
new file mode 100755
index 0000000..09ad529
--- /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;
+ }
+
+ 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) {
+ if (!strcmp(mode, "HOST"))
+ ndev->set_mode(NOTIFY_SET_ON);
+ else if (!strcmp(mode, "NONE"))
+ ndev->set_mode(NOTIFY_SET_OFF);
+ printk(KERN_INFO "host_notify: set mode %s\n", mode);
+ }
+ 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;
+ }
+
+ 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) {
+ if (!strcmp(booster, "ON")) {
+ ndev->set_booster(NOTIFY_SET_ON);
+ ndev->mode = NOTIFY_TEST_MODE;
+ } else if (!strcmp(booster, "OFF")) {
+ ndev->set_booster(NOTIFY_SET_OFF);
+ ndev->mode = NOTIFY_NONE_MODE;
+ }
+ printk(KERN_INFO "host_notify: set booster %s\n", booster);
+ }
+ 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);
+}