diff options
Diffstat (limited to 'drivers/usb')
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); +} |