From patchwork Thu Jul 29 00:28:42 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Fushen Chen X-Patchwork-Id: 60184 Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from bilbo.ozlabs.org (localhost [127.0.0.1]) by ozlabs.org (Postfix) with ESMTP id 5D6EF100DE6 for ; Thu, 29 Jul 2010 10:30:05 +1000 (EST) Received: by ozlabs.org (Postfix) id 362B910095D; Thu, 29 Jul 2010 10:28:55 +1000 (EST) Delivered-To: linuxppc-dev@ozlabs.org Received: from denmail01.apm.COM (denmail01-v4020.amcc.com [192.195.68.30]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 25A44100869 for ; Thu, 29 Jul 2010 10:28:52 +1000 (EST) Received: from localhost.localdomain (svdc-dhcp-10-66-13-186.amcc.com [10.66.13.186]) by denmail01.apm.COM (8.13.8/8.13.8) with ESMTP id o6T0UAOJ026615; Wed, 28 Jul 2010 17:30:11 -0700 From: Fushen Chen To: linux-usb@vger.kernel.org Subject: [PATCH 1/2 v1.04] Add support for DWC OTG HCD function. Date: Wed, 28 Jul 2010 17:28:42 -0700 Message-Id: <12803633243712-git-send-email-fchen@apm.com> X-Mailer: git-send-email 1.5.3 In-Reply-To: <12803633233020-git-send-email-fchen@apm.com> References: <12803633233020-git-send-email-fchen@apm.com> Cc: linuxppc-dev@ozlabs.org, gregkh@suse.de, Mark Miesfeld , Fushen Chen X-BeenThere: linuxppc-dev@lists.ozlabs.org X-Mailman-Version: 2.1.13 Precedence: list List-Id: Linux on PowerPC Developers Mail List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@lists.ozlabs.org Errors-To: linuxppc-dev-bounces+patchwork-incoming=ozlabs.org@lists.ozlabs.org This adds support for the USB host controller on APM SoC using Synopsys Designware IP. Signed-off-by: Fushen Chen Signed-off-by: Mark Miesfeld --- drivers/Makefile | 1 + drivers/usb/Kconfig | 2 + drivers/usb/dwc_otg/Kconfig | 99 + drivers/usb/dwc_otg/Makefile | 19 + drivers/usb/dwc_otg/dwc_otg_apmppc.c | 409 ++++ drivers/usb/dwc_otg/dwc_otg_cil.c | 905 +++++++++ drivers/usb/dwc_otg/dwc_otg_cil.h | 1194 +++++++++++ drivers/usb/dwc_otg/dwc_otg_cil_intr.c | 631 ++++++ drivers/usb/dwc_otg/dwc_otg_driver.h | 94 + drivers/usb/dwc_otg/dwc_otg_hcd.c | 2413 +++++++++++++++++++++++ drivers/usb/dwc_otg/dwc_otg_hcd.h | 426 ++++ drivers/usb/dwc_otg/dwc_otg_hcd_intr.c | 1478 ++++++++++++++ drivers/usb/dwc_otg/dwc_otg_hcd_queue.c | 710 +++++++ drivers/usb/dwc_otg/dwc_otg_param.c | 744 +++++++ drivers/usb/dwc_otg/dwc_otg_regs.h | 3282 +++++++++++++++++++++++++++++++ 15 files changed, 12407 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/dwc_otg/Kconfig create mode 100644 drivers/usb/dwc_otg/Makefile create mode 100644 drivers/usb/dwc_otg/dwc_otg_apmppc.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_cil.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_cil.h create mode 100644 drivers/usb/dwc_otg/dwc_otg_cil_intr.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_driver.h create mode 100644 drivers/usb/dwc_otg/dwc_otg_hcd.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_hcd.h create mode 100644 drivers/usb/dwc_otg/dwc_otg_hcd_intr.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_hcd_queue.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_param.c create mode 100644 drivers/usb/dwc_otg/dwc_otg_regs.h diff --git a/drivers/Makefile b/drivers/Makefile index 20dcced..f3fc7c7 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_UWB) += uwb/ obj-$(CONFIG_USB_OTG_UTILS) += usb/otg/ obj-$(CONFIG_USB) += usb/ obj-$(CONFIG_USB_MUSB_HDRC) += usb/musb/ +obj-$(CONFIG_USB_DWC_OTG) += usb/dwc_otg/ obj-$(CONFIG_PCI) += usb/ obj-$(CONFIG_USB_GADGET) += usb/gadget/ obj-$(CONFIG_SERIO) += input/serio/ diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 6a58cb1..f48920b 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -113,6 +113,8 @@ source "drivers/usb/host/Kconfig" source "drivers/usb/musb/Kconfig" +source "drivers/usb/dwc_otg/Kconfig" + source "drivers/usb/class/Kconfig" source "drivers/usb/storage/Kconfig" diff --git a/drivers/usb/dwc_otg/Kconfig b/drivers/usb/dwc_otg/Kconfig new file mode 100644 index 0000000..174141d --- /dev/null +++ b/drivers/usb/dwc_otg/Kconfig @@ -0,0 +1,99 @@ +# +# 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 + depends on (USB || USB_GADGET) + depends on 405EZ || 405EX || 460EX + select NOP_USB_XCEIV + select USB_OTG_UTILS + tristate "Synopsys DWC OTG Controller" + 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" if 405EX || 460EX + +config DWC_OTG_MODE + bool "DWC OTG Mode" if 405EX || 460EX + select USB_GADGET_SELECTED + +config DWC_DEVICE_ONLY + bool "DWC Device Only Mode" + select USB_GADGET_SELECTED + +endchoice + +# enable peripheral support (including with OTG) +config USB_GADGET_DWC_HDRC + bool + depends on USB_DWC_OTG && (DWC_DEVICE_ONLY || USB_DWC_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" if 405EX || 460EX + +config DWC_DMA_MODE + bool "DWC DMA Mode" if 405EX || (460EX && \ + (!USB_EHCI_HCD || !USB_OHCI_HCD)) + +endchoice + +config USB_OTG_WHITELIST + bool "Rely on OTG Targeted Peripherals List" + depends on !USB_SUSPEND && USB_DWC_OTG + default n + help + This is the same flag as in ../core/Kconfig. + It is here for easy deselect. + +config DWC_OTG_REG_LE + depends on USB_DWC_OTG + bool "DWC Little Endian Register" if 405EX || 460EX + default y + help + OTG core register access is Little-Endian. + +config DWC_OTG_FIFO_LE + depends on USB_DWC_OTG + bool "DWC FIFO Little Endian" if 405EZ + default n + help + OTG core FIFO access is Little-Endian. + +config DWC_LIMITED_XFER_SIZE + depends on USB_GADGET_DWC_HDRC + bool "DWC Endpoint Limited Xfer Size" if 405EZ || 405EX || 460EX + default n if 460EX || 405EX + default y if 405EZ + help + Bit fields in the Device EP Transfer Size Register is 11 bits. diff --git a/drivers/usb/dwc_otg/Makefile b/drivers/usb/dwc_otg/Makefile new file mode 100644 index 0000000..31dd5e8 --- /dev/null +++ b/drivers/usb/dwc_otg/Makefile @@ -0,0 +1,19 @@ +# +# OTG infrastructure and transceiver drivers +# +obj-$(CONFIG_USB_DWC_OTG) += dwc_otg.o + +dwc_otg-objs := dwc_otg_cil.o dwc_otg_cil_intr.o dwc_otg_param.o + +ifeq ($(CONFIG_4xx_SOC),y) +dwc_otg-objs += dwc_otg_apmppc.o +endif + +ifneq ($(CONFIG_DWC_DEVICE_ONLY),y) +dwc_otg-objs += dwc_otg_hcd.o dwc_otg_hcd_intr.o \ + dwc_otg_hcd_queue.o +endif + +ifneq ($(CONFIG_DWC_HOST_ONLY),y) +dwc_otg-objs += dwc_otg_pcd.o dwc_otg_pcd_intr.o +endif diff --git a/drivers/usb/dwc_otg/dwc_otg_apmppc.c b/drivers/usb/dwc_otg/dwc_otg_apmppc.c new file mode 100644 index 0000000..ef40917 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_apmppc.c @@ -0,0 +1,409 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * 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 + +#include "dwc_otg_driver.h" + +/* Based on Synopsys driver version 2.60a */ +#define DWC_DRIVER_VERSION "1.04" +#define DWC_DRIVER_DESC "HS OTG USB Controller driver" +static const char dwc_driver_name[] = "dwc_otg"; + +/** + * This function is the top level interrupt handler for the Common + * (Device and host modes) interrupts. + */ +static irqreturn_t dwc_otg_common_irq(int _irq, void *dev) +{ + struct dwc_otg_device *dwc_dev = dev; + int retval = IRQ_NONE; + + retval = dwc_otg_handle_common_intr(dwc_dev->core_if); + return IRQ_RETVAL(retval); +} + +/** + * This function is the interrupt handler for the OverCurrent condition + * from the external charge pump (if enabled) + */ +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; + union hprt0_data hprt0 = {.d32 = 0}; + + dwc_hcd = dwc_dev->hcd; + spin_lock(&dwc_hcd->lock); + dwc_hcd->flags.b.port_over_current_change = 1; + + hprt0.b.prtpwr = 0; + dwc_write_reg32(dwc_dev->core_if->host_if->hprt0, + hprt0.d32); + spin_unlock(&dwc_hcd->lock); + } else { + /* Device mode - This int is n/a for device mode */ + printk(KERN_ERR "DeviceMode: OTG OverCurrent Detected\n"); + } + + return IRQ_HANDLED; +} + +/** + * This function is called when a device is unregistered with the + * dwc_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 __devexit dwc_otg_driver_remove(struct of_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; + + usb_nop_xceiv_unregister(); + + /* Free the IRQ */ + if (dwc_dev->common_irq_installed) + free_irq(dwc_dev->irq, dwc_dev); + + if (!dwc_has_feature(dwc_dev->core_if, DWC_DEVICE_ONLY)) + if (dwc_dev->hcd) + dwc_otg_hcd_remove(dev); + + if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) + 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); + kfree(dwc_dev); + + /* Clear the drvdata pointer. */ + dev_set_drvdata(dev, 0); + return 0; +} + +/** + * This function is called when an device is bound to a + * dwc_otg_driver. It creates the driver components required to + * control the device (CIL, HCD, and PCD) and it initializes the + * device. The driver components are stored in a dwc_otg_device + * structure. A reference to the dwc_otg_device is saved in the + * device. This allows the driver to access the dwc_otg_device + * structure on subsequent calls to driver methods for this device. + */ +static int __devinit dwc_otg_driver_probe(struct of_device *ofdev, + const struct of_device_id *match) +{ + int retval = 0; + struct dwc_otg_device *dwc_dev; + struct device *dev = &ofdev->dev; + struct resource res; + u32 *gusbcfg_addr; + union gusbcfg_data usbcfg = {.d32 = 0}; + u32 cp_irq; + + dev_dbg(dev, "dwc_otg_driver_probe(%p)\n", dev); + + 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->reg_offset = 0xFFFFFFFF; + + /* Retrieve the memory and IRQ resources. */ + dwc_dev->irq = irq_of_parse_and_map(ofdev->dev.of_node, 0); + if (dwc_dev->irq == NO_IRQ) { + dev_err(dev, "no device irq\n"); + retval = -ENODEV; + goto fail; + } + dev_dbg(dev, "OTG - device irq: %d\n", dwc_dev->irq); + + if (of_address_to_resource(ofdev->dev.of_node, 0, &res)) { + printk(KERN_ERR "%s: Can't get USB-OTG register address\n", + __func__); + retval = -ENOMEM; + goto fail; + } + dev_dbg(dev, "OTG - ioresource_mem start0x%08x: end:0x%08x\n", + (unsigned)res.start, (unsigned)res.end); + + 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; + } + + /* 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, "ioremap64() failed\n"); + retval = -ENOMEM; + goto fail; + } + dev_dbg(dev, "mapped base=0x%08x\n", (unsigned)dwc_dev->base); + + /* + * Initialize driver data to point to the global DWC_otg + * Device structure. + */ + dev_set_drvdata(dev, dwc_dev); + + dev_dbg(dev, "dwc_dev=0x%p\n", 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; + } + usb_nop_xceiv_register(); + dwc_dev->core_if->xceiv = otg_get_transceiver(); + if (!dwc_dev->core_if->xceiv) { + retval = -ENODEV; + goto fail; + } + dwc_set_feature(dwc_dev->core_if); + + gusbcfg_addr = &dwc_dev->core_if->core_global_regs->gusbcfg; + + /* + * Validate parameter values. + */ + if (check_parameters(dwc_dev->core_if)) { + retval = -EINVAL; + goto fail; + } + + /* Added for PLB DMA phys virt mapping */ + dwc_dev->core_if->phys_addr = dwc_dev->phys_addr; + + /* + * Disable the global interrupt until all the interrupt + * handlers are installed. + */ + dwc_otg_disable_global_interrupts(dwc_dev->core_if); + + /* + * 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) { + printk(KERN_ERR "request of irq%d failed retval: %d\n", + dwc_dev->irq, retval); + retval = -EBUSY; + goto fail; + } else { + dwc_dev->common_irq_installed = 1; + } + + /* Initialize the DWC_otg core. */ + dwc_otg_core_init(dwc_dev->core_if); + + /* configure chargepump interrupt */ + cp_irq = irq_of_parse_and_map(ofdev->dev.of_node, 3); + if (cp_irq) { + retval = request_irq(cp_irq, dwc_otg_externalchgpump_irq, + IRQF_SHARED, "dwc_otg_ext_chg_pump", dwc_dev); + if (retval) { + printk(KERN_ERR "request of irq failed retval: %d\n", + retval); + retval = -EBUSY; + goto fail; + } else { + printk(KERN_INFO "%s: ExtChgPump Detection " + "IRQ registered\n", dwc_driver_name); + } + } + + if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) { + /* Initialize the PCD */ + retval = dwc_otg_pcd_init(dev); + if (retval) { + printk(KERN_ERR "dwc_otg_pcd_init failed\n"); + dwc_dev->pcd = NULL; + goto fail; + } + } + + if (!dwc_has_feature(dwc_dev->core_if, DWC_DEVICE_ONLY)) { + /* Initialize the HCD and force_host_mode */ + usbcfg.d32 = dwc_read_reg32(gusbcfg_addr); + usbcfg.b.force_host_mode = 1; + dwc_write_reg32(gusbcfg_addr, usbcfg.d32); + + retval = dwc_otg_hcd_init(dev, dwc_dev); + if (retval) { + printk(KERN_ERR "dwc_otg_hcd_init failed\n"); + dwc_dev->hcd = NULL; + goto fail; + } + } + /* + * Enable the global interrupt after all the interrupt + * handlers are installed. + */ + dwc_otg_enable_global_interrupts(dwc_dev->core_if); + + usbcfg.d32 = dwc_read_reg32(gusbcfg_addr); + usbcfg.b.force_host_mode = 0; + dwc_write_reg32(gusbcfg_addr, usbcfg.d32); + + return 0; + +fail: + dwc_otg_driver_remove(ofdev); + return retval; +} + +/* + * 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. + */ +static const struct of_device_id dwc_otg_match[] = { + { .compatible = "amcc,dwc-otg", }, + {} +}; +MODULE_DEVICE_TABLE(of, dwc_otg_match); + +static struct of_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, + }, +}; + +/** + * This function is called when the dwc_otg_driver is installed with the + * insmod command. It registers the dwc_otg_driver structure with the + * appropriate bus driver. This will cause the dwc_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 dwc_otg_driver_init(void) +{ + int retval = 0; + + printk(KERN_INFO "%s: version %s\n", dwc_driver_name, + DWC_DRIVER_VERSION); + retval = of_register_platform_driver(&dwc_otg_driver); + if (retval < 0) + printk(KERN_ERR "%s registration failed. retval=%d\n", + dwc_driver_name, retval); + return retval; +} +module_init(dwc_otg_driver_init); + +/** + * 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 dwc_otg_driver_cleanup(void) +{ + of_unregister_platform_driver(&dwc_otg_driver); + printk(KERN_INFO "%s module removed\n", dwc_driver_name); +} +module_exit(dwc_otg_driver_cleanup); + +MODULE_DESCRIPTION(DWC_DRIVER_DESC); +MODULE_AUTHOR("Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * 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. + * + * The CIL manages the memory map for the core so that the HCD and PCD + * don't have to do this separately. It also handles basic tasks like + * reading/writing the registers and data FIFOs in the controller. + * Some of the data access functions provide encapsulation of several + * operations required to perform a task, such as writing multiple + * registers to start a transfer. Finally, the CIL performs basic + * services that are not specific to either the host or device modes + * of operation. These services include management of the OTG Host + * Negotiation Protocol (HNP) and Session Request Protocol (SRP). A + * Diagnostic API is also provided to allow testing of the controller + * hardware. + * + * The Core Interface Layer has the following requirements: + * - Provides basic controller operations. + * - Minimal use of OS services. + * - The OS services used will be abstracted by using inline functions + * or macros. + */ +#include + +#include "dwc_otg_cil.h" + +const char *op_state_str(enum usb_otg_state state) +{ + switch (state) { + case OTG_STATE_A_IDLE: return "a_idle"; + case OTG_STATE_A_WAIT_VRISE: return "a_wait_vrise"; + case OTG_STATE_A_WAIT_BCON: return "a_wait_bcon"; + case OTG_STATE_A_HOST: return "a_host"; + case OTG_STATE_A_SUSPEND: return "a_suspend"; + case OTG_STATE_A_PERIPHERAL: return "a_peripheral"; + case OTG_STATE_A_WAIT_VFALL: return "a_wait_vfall"; + case OTG_STATE_A_VBUS_ERR: return "a_vbus_err"; + case OTG_STATE_B_IDLE: return "b_idle"; + case OTG_STATE_B_SRP_INIT: return "b_srp_init"; + case OTG_STATE_B_PERIPHERAL: return "b_peripheral"; + case OTG_STATE_B_WAIT_ACON: return "b_wait_acon"; + case OTG_STATE_B_HOST: return "b_host"; + default: return "UNDEFINED"; + } +} + +/** + * This function enables the controller's Global Interrupt in the AHB Config + * register. + */ +void dwc_otg_enable_global_interrupts(struct core_if *core_if) +{ + union gahbcfg_data ahbcfg = {.d32 = 0}; + + ahbcfg.b.glblintrmsk = 1; + dwc_modify_reg32(&core_if->core_global_regs->gahbcfg, 0, ahbcfg.d32); +} + +/** + * This function disables the controller's Global Interrupt in the AHB Config + * register. + */ +void dwc_otg_disable_global_interrupts(struct core_if *core_if) +{ + union gahbcfg_data ahbcfg = {.d32 = 0}; + + ahbcfg.b.glblintrmsk = 1; + dwc_modify_reg32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0); +} + +/** + * Tests if the current hardware is using a full speed phy. + */ +static inline int full_speed_phy(struct core_if *core_if) +{ + if ((core_if->hwcfg2.b.hs_phy_type == 2 && + core_if->hwcfg2.b.fs_phy_type == 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; + union hcfg_data hcfg; + + 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.d32 = dwc_read_reg32(&core_if->host_if->host_global_regs->hcfg); + hcfg.b.fslspclksel = val; + dwc_write_reg32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32); +} + +/** + * 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; + union dcfg_data 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.d32 = dwc_read_reg32(&core_if->dev_if->dev_global_regs->dcfg); + dcfg.b.devspd = val; + dwc_write_reg32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32); +} + +/** + * 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 = core_if->hwcfg2.b.num_dev_ep; + u32 hwcfg1 = core_if->hwcfg1.d32 >> 2; + u32 num_tx_fifos = core_if->hwcfg4.b.num_in_eps; + u32 i; + + for (i = 0; i < num_eps; ++i) { + if (!(hwcfg1 & 0x1)) + num_in_eps++; + hwcfg1 >>= 2; + } + + if (core_if->hwcfg4.b.ded_fifo_en) + 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 = core_if->hwcfg2.b.num_dev_ep; + u32 hwcfg1 = core_if->hwcfg1.d32 >> 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. + */ +static void dwc_otg_core_reset(struct core_if *core_if) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union grstctl_data greset = {.d32 = 0}; + int count = 0; + + /* Wait for AHB master IDLE state. */ + do { + udelay(10); + greset.d32 = dwc_read_reg32(&global_regs->grstctl); + if (++count > 100000) { + printk(KERN_WARNING + "%s() HANG! AHB Idle GRSTCTL=%0x\n", + __func__, greset.d32); + return; + } + } while (!greset.b.ahbidle); + + /* Core Soft Reset */ + count = 0; + greset.b.csftrst = 1; + dwc_write_reg32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = dwc_read_reg32(&global_regs->grstctl); + if (++count > 10000) { + printk(KERN_WARNING "%s() HANG! Soft Reset " + "GRSTCTL=%0x\n", __func__, greset.d32); + break; + } + udelay(1); + } while (greset.b.csftrst); + + /* 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) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union gintmsk_data intr_mask = {.d32 = 0}; + + /* Clear any pending OTG Interrupts */ + dwc_write_reg32(&global_regs->gotgint, 0xFFFFFFFF); + + /* Clear any pending interrupts */ + dwc_write_reg32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the interrupts in the GINTMSK. */ + intr_mask.b.modemismatch = 1; + intr_mask.b.otgintr = 1; + intr_mask.b.conidstschng = 1; + intr_mask.b.wkupintr = 1; + intr_mask.b.disconnect = 1; + intr_mask.b.usbsuspend = 1; + intr_mask.b.sessreqintr = 1; + if (!core_if->dma_enable) + intr_mask.b.rxstsqlvl = 1; + dwc_write_reg32(&global_regs->gintmsk, intr_mask.d32); +} + +/** + * 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; + struct core_global_regs *global_regs = core_if->core_global_regs; + struct device_if *dev_if = core_if->dev_if; + union gahbcfg_data ahbcfg = {.d32 = 0}; + union gusbcfg_data usbcfg = {.d32 = 0}; + union gi2cctl_data i2cctl = {.d32 = 0}; + + /* Common Initialization */ + usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); + + /* Program the ULPI External VBUS bit if needed */ + usbcfg.b.ulpi_ext_vbus_drv = 1; + + /* Set external TS Dline pulsing */ + usbcfg.b.term_sel_dl_pulse = core_if->core_params->ts_dline == 1 ? + 1 : 0; + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + + /* 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 < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + dev_if->perio_tx_fifo_size[i] = + dwc_read_reg32(&global_regs->dptxfsiz_dieptxf[i]) >> 16; + } + for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) { + dev_if->tx_fifo_size[i] = + dwc_read_reg32(&global_regs->dptxfsiz_dieptxf[i]) >> 16; + } + + core_if->total_fifo_size = core_if->hwcfg3.b.dfifo_depth; + core_if->rx_fifo_size = dwc_read_reg32(&global_regs->grxfsiz); + core_if->nperio_tx_fifo_size = + dwc_read_reg32(&global_regs->gnptxfsiz) >> 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; + usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); + usbcfg.b.physel = 1; + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + + /* 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 */ + usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); + usbcfg.b.otgutmifssel = 1; + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + + /* Program GI2CCTL.I2CEn */ + i2cctl.d32 = dwc_read_reg32(&global_regs->gi2cctl); + i2cctl.b.i2cdevaddr = 1; + i2cctl.b.i2cen = 0; + dwc_write_reg32(&global_regs->gi2cctl, i2cctl.d32); + i2cctl.b.i2cen = 1; + dwc_write_reg32(&global_regs->gi2cctl, i2cctl.d32); + } + } else if (!core_if->phy_init_done) { + /* + * High speed PHY. These parameters are preserved during soft + * reset so only program them the first time. Do a soft reset + * immediately after setting phyif. + */ + core_if->phy_init_done = 1; + usbcfg.b.ulpi_utmi_sel = core_if->core_params->phy_type; + if (usbcfg.b.ulpi_utmi_sel == 1) { + /* ULPI interface */ + usbcfg.b.phyif = 0; + usbcfg.b.ddrsel = core_if->core_params->phy_ulpi_ddr; + } else { + /* UTMI+ interface */ + if (core_if->core_params->phy_utmi_width == 16) + usbcfg.b.phyif = 1; + else + usbcfg.b.phyif = 0; + } + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + + /* Reset after setting the PHY parameters */ + dwc_otg_core_reset(core_if); + } + + if (core_if->hwcfg2.b.hs_phy_type == 2 && + core_if->hwcfg2.b.fs_phy_type == 1 && + core_if->core_params->ulpi_fs_ls) { + usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); + usbcfg.b.ulpi_fsls = 1; + usbcfg.b.ulpi_clk_sus_m = 1; + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + } else { + usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); + usbcfg.b.ulpi_fsls = 0; + usbcfg.b.ulpi_clk_sus_m = 0; + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + } + + /* Program the GAHBCFG Register. */ + switch (core_if->hwcfg2.b.architecture) { + case DWC_SLAVE_ONLY_ARCH: + ahbcfg.b.nptxfemplvl_txfemplvl = + DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; + ahbcfg.b.ptxfemplvl = DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY; + core_if->dma_enable = 0; + break; + case DWC_EXT_DMA_ARCH: + ahbcfg.b.hburstlen = 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.b.hburstlen = DWC_GAHBCFG_INT_DMA_BURST_INCR; + core_if->dma_enable = (core_if->core_params->dma_enable != 0); + break; + } + + ahbcfg.b.dmaenable = core_if->dma_enable; + dwc_write_reg32(&global_regs->gahbcfg, ahbcfg.d32); + core_if->en_multiple_tx_fifo = core_if->hwcfg4.b.ded_fifo_en; + + /* Program the GUSBCFG register. */ + usbcfg.d32 = dwc_read_reg32(&global_regs->gusbcfg); + switch (core_if->hwcfg2.b.op_mode) { + case DWC_MODE_HNP_SRP_CAPABLE: + usbcfg.b.hnpcap = (core_if->core_params->otg_cap == + DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE); + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + case DWC_MODE_SRP_ONLY_CAPABLE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + case DWC_MODE_NO_HNP_SRP_CAPABLE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + case DWC_MODE_SRP_CAPABLE_DEVICE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + case DWC_MODE_NO_SRP_CAPABLE_DEVICE: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + case DWC_MODE_SRP_CAPABLE_HOST: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = (core_if->core_params->otg_cap != + DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE); + break; + case DWC_MODE_NO_SRP_CAPABLE_HOST: + usbcfg.b.hnpcap = 0; + usbcfg.b.srpcap = 0; + break; + } + dwc_write_reg32(&global_regs->gusbcfg, usbcfg.d32); + + /* 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) +{ + union gintmsk_data intr_mask = {.d32 = 0}; + union diepint_data msk = {.d32 = 0}; + struct core_global_regs *global_regs = core_if->core_global_regs; + + /* Disable all interrupts. */ + dwc_write_reg32(&global_regs->gintmsk, 0); + + /* Clear any pending interrupts */ + dwc_write_reg32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* Enable interrupts */ + intr_mask.b.usbreset = 1; + intr_mask.b.enumdone = 1; + intr_mask.b.inepintr = 1; + intr_mask.b.outepintr = 1; + intr_mask.b.erlysuspend = 1; + if (!core_if->en_multiple_tx_fifo) + intr_mask.b.epmismatch = 1; + + /* Periodic EP */ + intr_mask.b.isooutdrop = 1; + intr_mask.b.eopframe = 1; + intr_mask.b.incomplisoin = 1; + intr_mask.b.incomplisoout = 1; + + dwc_modify_reg32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); + + msk.b.txfifoundrn = 1; + dwc_modify_reg32(&core_if->dev_if->dev_global_regs->diepmsk, + msk.d32, msk.d32); +} + +/** + * Configures the device data fifo sizes when dynamic sizing is enabled. + */ +static void config_dev_dynamic_fifos(struct core_if *core_if) +{ + u32 i; + struct core_global_regs *regs = core_if->core_global_regs; + struct core_params *params = core_if->core_params; + union fifosize_data txsize; + union fifosize_data nptxsize; + union fifosize_data ptxsize; + + /* Rx FIFO */ + dwc_write_reg32(®s->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.b.depth = params->dev_nperio_tx_fifo_size; + nptxsize.b.startaddr = params->dev_rx_fifo_size; + dwc_write_reg32(®s->gnptxfsiz, nptxsize.d32); + + /* + * Periodic Tx FIFOs These FIFOs are numbered from 1 to + * 15. Indexes of the FIFO size module parameters in the + * dev_perio_tx_fifo_size array and the FIFO size + * registers in the dptxfsiz array run from 0 to 14. + */ + ptxsize.b.startaddr = nptxsize.b.startaddr + nptxsize.b.depth; + for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + ptxsize.b.depth = params->dev_perio_tx_fifo_size[i]; + dwc_write_reg32(®s->dptxfsiz_dieptxf[i], + ptxsize.d32); + ptxsize.b.startaddr += ptxsize.b.depth; + } + } else { + /* + * Non-periodic Tx FIFOs These FIFOs are numbered from + * 1 to 15. Indexes of the FIFO size module parameters + * in the dev_tx_fifo_size array and the FIFO size + * registers in the dptxfsiz_dieptxf array run from 0 to + * 14. + */ + nptxsize.b.depth = params->dev_nperio_tx_fifo_size; + nptxsize.b.startaddr = params->dev_rx_fifo_size; + dwc_write_reg32(®s->gnptxfsiz, nptxsize.d32); + + txsize.b.startaddr = nptxsize.b.startaddr + nptxsize.b.depth; + for (i = 1; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) { + txsize.b.depth = params->dev_tx_fifo_size[i]; + dwc_write_reg32(®s->dptxfsiz_dieptxf[i - 1], + txsize.d32); + txsize.b.startaddr += txsize.b.depth; + } + } +} + +/** + * 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; + union dcfg_data dcfg = {.d32 = 0}; + union grstctl_data resetctl = {.d32 = 0}; + union dthrctl_data dthrctl; + + /* Restart the Phy Clock */ + dwc_write_reg32(c_if->pcgcctl, 0); + + /* Device configuration register */ + init_devspd(c_if); + dcfg.d32 = dwc_read_reg32(&d_if->dev_global_regs->dcfg); + dcfg.b.perfrint = DWC_DCFG_FRAME_INTERVAL_80; + dwc_write_reg32(&d_if->dev_global_regs->dcfg, dcfg.d32); + + /* If needed configure data FIFO sizes */ + if (c_if->hwcfg2.b.dynamic_fifo && 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.b.intknqflsh = 1; + dwc_write_reg32(&c_if->core_global_regs->grstctl, resetctl.d32); + + /* Clear all pending Device Interrupts */ + dwc_write_reg32(&d_if->dev_global_regs->diepmsk, 0); + dwc_write_reg32(&d_if->dev_global_regs->doepmsk, 0); + dwc_write_reg32(&d_if->dev_global_regs->daint, 0xFFFFFFFF); + dwc_write_reg32(&d_if->dev_global_regs->daintmsk, 0); + + for (i = 0; i <= d_if->num_in_eps; i++) { + union depctl_data depctl; + + depctl.d32 = dwc_read_reg32(&d_if->in_ep_regs[i]->diepctl); + if (depctl.b.epena) { + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + } else { + depctl.d32 = 0; + } + + dwc_write_reg32(&d_if->in_ep_regs[i]->diepctl, depctl.d32); + dwc_write_reg32(&d_if->in_ep_regs[i]->dieptsiz, 0); + dwc_write_reg32(&d_if->in_ep_regs[i]->diepdma, 0); + dwc_write_reg32(&d_if->in_ep_regs[i]->diepint, 0xFF); + } + + for (i = 0; i <= d_if->num_out_eps; i++) { + union depctl_data depctl; + depctl.d32 = dwc_read_reg32(&d_if->out_ep_regs[i]->doepctl); + if (depctl.b.epena) { + depctl.d32 = 0; + depctl.b.epdis = 1; + depctl.b.snak = 1; + } else { + depctl.d32 = 0; + } + dwc_write_reg32(&d_if->out_ep_regs[i]->doepctl, depctl.d32); + dwc_write_reg32(&d_if->out_ep_regs[i]->doeptsiz, 0); + dwc_write_reg32(&d_if->out_ep_regs[i]->doepdma, 0); + dwc_write_reg32(&d_if->out_ep_regs[i]->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.d32 = 0; + dthrctl.b.non_iso_thr_en = d_if->non_iso_tx_thr_en; + dthrctl.b.iso_thr_en = d_if->iso_tx_thr_en; + dthrctl.b.tx_thr_len = d_if->tx_thr_length; + dthrctl.b.rx_thr_en = d_if->rx_thr_en; + dthrctl.b.rx_thr_len = d_if->rx_thr_length; + dwc_write_reg32(&d_if->dev_global_regs->dtknqr3_dthrctl, + dthrctl.d32); + + } + + 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_datafifo32(fifo); +} + +/** + * Flush a Tx FIFO. + */ +void dwc_otg_flush_tx_fifo(struct core_if *core_if, const int num) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union grstctl_data greset = {.d32 = 0 }; + int count = 0; + + greset.b.txfflsh = 1; + greset.b.txfnum = num; + dwc_write_reg32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = dwc_read_reg32(&global_regs->grstctl); + if (++count > 10000) { + printk(KERN_WARNING "%s() HANG! GRSTCTL=%0x " + "GNPTXSTS=0x%08x\n", __func__, greset.d32, + dwc_read_reg32(&global_regs->gnptxsts)); + break; + } + udelay(1); + } while (greset.b.txfflsh == 1); + + /* Wait for 3 PHY Clocks */ + udelay(1); +} + +/** + * Flush Rx FIFO. + */ +void dwc_otg_flush_rx_fifo(struct core_if *core_if) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union grstctl_data greset = {.d32 = 0 }; + int count = 0; + + greset.b.rxfflsh = 1; + dwc_write_reg32(&global_regs->grstctl, greset.d32); + + do { + greset.d32 = dwc_read_reg32(&global_regs->grstctl); + if (++count > 10000) { + printk(KERN_WARNING "%s() HANG! GRSTCTL=%0x\n", + __func__, greset.d32); + break; + } + udelay(1); + } while (greset.b.rxfflsh); + + /* Wait for 3 PHY Clocks */ + udelay(1); +} + +/** + * Register HCD callbacks. + * The callbacks are used to start and stop the HCD for interrupt processing. + */ +void __devinit 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 __devinit 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 __devinit *dwc_otg_cil_init(const u32 *base, + struct core_params *params) +{ + struct core_if *core_if = NULL; + struct device_if *dev_if = NULL; + struct dwc_host_if *host_if = NULL; + u8 *reg_base = (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 = (struct core_global_regs *) 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 = (struct device_global_regs *) (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] = (struct device_in_ep_regs *) + (reg_base + DWC_DEV_IN_EP_REG_OFFSET + offset); + + dev_if->out_ep_regs[i] = (struct device_out_ep_regs *) + (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 = (struct host_global_regs *) + (reg_base + DWC_OTG_HOST_GLOBAL_REG_OFFSET); + + host_if->hprt0 = (u32 *) (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] = (struct dwc_hc_regs *) + (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] = + (u32 *) (reg_base + DWC_OTG_DATA_FIFO_OFFSET + + (i * DWC_OTG_DATA_FIFO_SIZE)); + } + core_if->pcgcctl = (u32 *) (reg_base + DWC_OTG_PCGCCTL_OFFSET); + + /* + * Store the contents of the hardware configuration registers here for + * easy access later. + */ + core_if->hwcfg1.d32 = + dwc_read_reg32(&core_if->core_global_regs->ghwcfg1); + core_if->hwcfg2.d32 = + dwc_read_reg32(&core_if->core_global_regs->ghwcfg2); + + core_if->hwcfg2.b.architecture = DWC_ARCH; + + core_if->hwcfg3.d32 = + dwc_read_reg32(&core_if->core_global_regs->ghwcfg3); + core_if->hwcfg4.d32 = + dwc_read_reg32(&core_if->core_global_regs->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_modify_reg32(&core_if->core_global_regs->gahbcfg, 1, 0); + dwc_write_reg32(&core_if->core_global_regs->gintmsk, 0); + + if (core_if) { + kfree(core_if->dev_if); + kfree(core_if->host_if); + } + kfree(core_if); +} diff --git a/drivers/usb/dwc_otg/dwc_otg_cil.h b/drivers/usb/dwc_otg/dwc_otg_cil.h new file mode 100644 index 0000000..46981a2 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_cil.h @@ -0,0 +1,1194 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(__DWC_CIL_H__) +#define __DWC_CIL_H__ +#include +#include +#include +#include +#include +#include +#include + +#include "dwc_otg_regs.h" + +#ifdef CONFIG_DWC_SLAVE +#define DWC_ARCH DWC_SLAVE_ONLY_ARCH +#else +#define DWC_ARCH DWC_INT_DMA_ARCH +#endif + +#ifdef CONFIG_DWC_DEBUG +#define DEBUG +#endif + +/** + * Reads the content of a register. + */ +static inline u32 dwc_read_reg32(u32 *reg) +{ +#ifdef CONFIG_DWC_OTG_REG_LE + return in_le32(reg); +#else + return in_be32(reg); +#endif +}; + +/** + * Writes a register with a 32 bit value. + */ +static inline void dwc_write_reg32(u32 *reg, const u32 value) +{ +#ifdef CONFIG_DWC_OTG_REG_LE + out_le32(reg, value); +#else + out_be32(reg, value); +#endif +}; + +/** + * This function modifies bit values in a register. Using the + * algorithm: (reg_contents & ~clear_mask) | set_mask. + */ +static inline +void dwc_modify_reg32(u32 *_reg, const u32 _clear_mask, + const u32 _set_mask) +{ +#ifdef CONFIG_DWC_OTG_REG_LE + out_le32(_reg, (in_le32(_reg) & ~_clear_mask) | _set_mask); +#else + out_be32(_reg, (in_be32(_reg) & ~_clear_mask) | _set_mask); +#endif +}; + +static inline void dwc_write_datafifo32(u32 *reg, const u32 _value) +{ +#ifdef CONFIG_DWC_OTG_FIFO_LE + out_le32(reg, _value); +#else + out_be32(reg, _value); +#endif +}; + +static inline u32 dwc_read_datafifo32(u32 *_reg) +{ +#ifdef CONFIG_DWC_OTG_FIFO_LE + return in_le32(_reg); +#else + return in_be32(_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; + + u32 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; + + /* 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; +}; + +/* + * 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 { + int opt; +#define dwc_param_opt_default 1 + + /* + * 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; +#define dwc_param_dma_enable_default 1 + + /* + * 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 + + /* + * Total number of 4-byte words in the data FIFO memory. This + * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic + * Tx FIFOs. 32 to 32768 (default 8192) + * + * Note: The total FIFO memory depth in the FPGA configuration is 8192. + */ + int data_fifo_size; +#define dwc_param_data_fifo_size_default 8192 + + /* + * 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. */ + struct core_global_regs *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 */ + u32 *pcgcctl; +#define DWC_OTG_PCGCCTL_OFFSET 0xE00 + + /* Push/pop addresses for endpoints or host channels.*/ + u32 *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.*/ + union hwcfg1_data hwcfg1; + union hwcfg2_data hwcfg2; + union hwcfg3_data hwcfg3; + union hwcfg4_data 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 const char *op_state_str(enum usb_otg_state state); +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) +{ + union hprt0_data hprt0; + hprt0.d32 = dwc_read_reg32(core_if->host_if->hprt0); + hprt0.b.prtena = 0; + hprt0.b.prtconndet = 0; + hprt0.b.prtenchng = 0; + hprt0.b.prtovrcurrchng = 0; + return hprt0.d32; +} + +/* + * 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) +{ + return dwc_read_reg32(&core_if->core_global_regs->gintsts) & + dwc_read_reg32(&core_if->core_global_regs->gintmsk); +} + +/** + * This function returns the mode of the operation, host or device. + */ +static inline u32 dwc_otg_mode(struct core_if *core_if) +{ + return dwc_read_reg32(&core_if->core_global_regs->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 CONFIG_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 __init 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 __init dwc_otg_pcd_init(struct device *dev); +#endif + +extern void dwc_otg_cil_remove(struct core_if *core_if); +extern struct core_if __devinit *dwc_otg_cil_init(const 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; +} +#endif diff --git a/drivers/usb/dwc_otg/dwc_otg_cil_intr.c b/drivers/usb/dwc_otg/dwc_otg_cil_intr.c new file mode 100644 index 0000000..bb11834 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_cil_intr.c @@ -0,0 +1,631 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * 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 + +#include "dwc_otg_cil.h" + +/** + * This function will log a debug message + */ +static int dwc_otg_handle_mode_mismatch_intr(struct core_if *core_if) +{ + union gintsts_data gintsts; + + printk(KERN_WARNING "Mode Mismatch Interrupt: currently in %s mode\n", + dwc_otg_mode(core_if) ? "Host" : "Device"); + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.modemismatch = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, gintsts.d32); + + 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) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union gotgint_data gotgint; + union gotgctl_data gotgctl; + union gintmsk_data gintmsk; + + gotgint.d32 = dwc_read_reg32(&global_regs->gotgint); + gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl); + + if (gotgint.b.sesenddet) { + gotgctl.d32 = dwc_read_reg32(&global_regs->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.b.devhnpen) + printk(KERN_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.d32 = 0; + gotgctl.b.devhnpen = 1; + dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 0); + } + if (gotgint.b.sesreqsucstschng) { + gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl); + if (gotgctl.b.sesreqscs) { + 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.d32 = 0; + gotgctl.b.sesreq = 1; + dwc_modify_reg32(&global_regs->gotgctl, + gotgctl.d32, 0); + } + } + } + if (gotgint.b.hstnegsucstschng) { + /* + * Print statements during the HNP interrupt handling can cause + * it to fail. + */ + gotgctl.d32 = dwc_read_reg32(&global_regs->gotgctl); + if (gotgctl.b.hstnegscs) { + 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.d32 = 0; + gintmsk.b.sofintr = 1; + dwc_modify_reg32(&global_regs->gintmsk, + gintmsk.d32, 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.d32 = 0; + gotgctl.b.hnpreq = 1; + gotgctl.b.devhnpen = 1; + dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 0); + + printk(KERN_ERR "Device Not Connected / Responding\n"); + } + } + if (gotgint.b.hstnegdet) { + /* + * 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.d32 = 0; + gintmsk.b.sofintr = 1; + dwc_modify_reg32(&global_regs->gintmsk, gintmsk.d32, 0); + pcd_stop(core_if); + hcd_start(core_if); + core_if->xceiv->state = OTG_STATE_A_HOST; + } + } + if (gotgint.b.adevtoutchng) + printk(KERN_INFO " ++OTG Interrupt: A-Device Timeout " + "Change++\n"); + if (gotgint.b.debdone) + printk(KERN_INFO " ++OTG Interrupt: Debounce Done++\n"); + + /* Clear GOTGINT */ + dwc_write_reg32(&core_if->core_global_regs->gotgint, gotgint.d32); + 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); + u32 count = 0; + union gotgctl_data gotgctl = {.d32 = 0}; + + printk(KERN_INFO "%s\n", __func__); + gotgctl.d32 = dwc_read_reg32(&core_if->core_global_regs->gotgctl); + if (gotgctl.b.conidsts) { + /* + * B-Device connector (device mode) wait for switch to device + * mode. + */ + while (!dwc_otg_is_device_mode(core_if) && ++count <= 10000) { + printk(KERN_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) { + printk(KERN_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) +{ + union gintsts_data gintsts = {.d32 = 0}; + union gintmsk_data gintmsk = {.d32 = 0}; + + /* + * 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.b.sofintr = 1; + dwc_modify_reg32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0); + + INIT_WORK(&core_if->usb_port_otg, port_otg_wqfunc); + schedule_work(&core_if->usb_port_otg); + + /* Set flag and clear interrupt */ + gintsts.b.conidstschng = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, gintsts.d32); + 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) +{ + union gintsts_data gintsts; + + if (!dwc_has_feature(core_if, DWC_HOST_ONLY)) { + union hprt0_data hprt0; + + if (dwc_otg_is_device_mode(core_if)) { + printk(KERN_INFO "SRP: Device mode\n"); + } else { + printk(KERN_INFO "SRP: Host mode\n"); + + /* Turn on the port power bit. */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + + /* + * 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.d32 = 0; + gintsts.b.sessreqintr = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, gintsts.d32); + 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) +{ + union gintsts_data gintsts; + struct device_if *dev_if = core_if->dev_if; + + if (dwc_otg_is_device_mode(core_if)) { + union dctl_data dctl = {.d32 = 0}; + + /* Clear the Remote Wakeup Signalling */ + dctl.b.rmtwkupsig = 1; + dwc_modify_reg32(&dev_if->dev_global_regs->dctl, dctl.d32, 0); + + if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) + core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p); + } else { + union pcgcctl_data pcgcctl = {.d32 = 0}; + + /* Restart the Phy Clock */ + pcgcctl.b.stoppclk = 1; + dwc_modify_reg32(core_if->pcgcctl, pcgcctl.d32, 0); + schedule_delayed_work(&core_if->usb_port_wakeup, 10); + } + + /* Clear interrupt */ + gintsts.d32 = 0; + gintsts.b.wkupintr = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, gintsts.d32); + 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) +{ + union gintsts_data gintsts; + struct core_global_regs *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)) { + union gotgctl_data gotgctl = {.d32 = 0}; + + gotgctl.d32 = + dwc_read_reg32(&global_regs->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.b.devhnpen) { + 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.d32 = 0; + gintsts.b.disconnect = 1; + dwc_write_reg32(&global_regs->gintsts, gintsts.d32); + 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) +{ + union dsts_data dsts; + union gintsts_data gintsts; + struct device_if *dev_if = core_if->dev_if; + + if (dwc_otg_is_device_mode(core_if)) { + struct dwc_pcd *pcd; + /* + * Check the Device status register to determine if the Suspend + * state is active. + */ + dsts.d32 = dwc_read_reg32(&dev_if->dev_global_regs->dsts); + /* PCD callback for suspend. */ + pcd = (struct dwc_pcd *)core_if->pcd_cb->p; + 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.d32 = 0; + gintsts.b.usbsuspend = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, gintsts.d32); + 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) +{ + union gintsts_data gintsts; + union gintmsk_data gintmsk; + union gintmsk_data gintmsk_common = {.d32 = 0}; + + gintmsk_common.b.wkupintr = 1; + gintmsk_common.b.sessreqintr = 1; + gintmsk_common.b.conidstschng = 1; + gintmsk_common.b.otgintr = 1; + gintmsk_common.b.modemismatch = 1; + gintmsk_common.b.disconnect = 1; + gintmsk_common.b.usbsuspend = 1; + gintmsk_common.b.portintr = 1; + + gintsts.d32 = dwc_read_reg32(&core_if->core_global_regs->gintsts); + gintmsk.d32 = dwc_read_reg32(&core_if->core_global_regs->gintmsk); + + return (gintsts.d32 & gintmsk.d32) & gintmsk_common.d32; +} + +/** + * 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; + union gintsts_data gintsts; + + gintsts.d32 = dwc_otg_read_common_intr(core_if); + + if (gintsts.b.modemismatch) + retval |= dwc_otg_handle_mode_mismatch_intr(core_if); + if (gintsts.b.otgintr) + retval |= dwc_otg_handle_otg_intr(core_if); + if (gintsts.b.conidstschng) + retval |= dwc_otg_handle_conn_id_status_change_intr(core_if); + if (gintsts.b.disconnect) + retval |= dwc_otg_handle_disconnect_intr(core_if); + if (gintsts.b.sessreqintr) + retval |= dwc_otg_handle_session_req_intr(core_if); + if (gintsts.b.wkupintr) + retval |= dwc_otg_handle_wakeup_detected_intr(core_if); + if (gintsts.b.usbsuspend) + retval |= dwc_otg_handle_usb_suspend_intr(core_if); + + if (gintsts.b.portintr && dwc_otg_is_device_mode(core_if)) { + gintsts.d32 = 0; + gintsts.b.portintr = 1; + dwc_write_reg32(&core_if->core_global_regs->gintsts, + gintsts.d32); + retval |= 1; + printk(KERN_INFO "RECEIVED PORTINT while in Device mode\n"); + } + + return retval; +} diff --git a/drivers/usb/dwc_otg/dwc_otg_driver.h b/drivers/usb/dwc_otg/dwc_otg_driver.h new file mode 100644 index 0000000..65c7c87 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_driver.h @@ -0,0 +1,94 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(__DWC_OTG_DRIVER_H__) +#define __DWC_OTG_DRIVER_H__ + +/* + * This file contains the interface to the Linux driver. + */ +#include "dwc_otg_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() */ + void *base; + + /* Pointer to the core interface structure. */ + struct core_if *core_if; + + /* Register offset for Diagnostic API.*/ + u32 reg_offset; + + /* 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; +}; +extern struct core_params dwc_otg_module_params; +extern int __devinit check_parameters(struct core_if *core_if); +#endif diff --git a/drivers/usb/dwc_otg/dwc_otg_hcd.c b/drivers/usb/dwc_otg/dwc_otg_hcd.c new file mode 100644 index 0000000..2579e3a --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_hcd.c @@ -0,0 +1,2413 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * Modified by Chuck Meade + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file contains the implementation of the HCD. In Linux, the HCD + * implements the hc_driver API. + */ + +#include +#include + +#include "dwc_otg_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) +{ + struct dwc_hc_regs *regs; + + hc->xfer_started = 0; + regs = core_if->host_if->hc_regs[hc->hc_num]; + dwc_write_reg32(®s->hcintmsk, 0); + dwc_write_reg32(®s->hcint, 0xFFFFFFFF); +} + +/** + * This function enables the Host mode interrupts. + */ +static void dwc_otg_enable_host_interrupts(struct core_if *core_if) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union gintmsk_data intr_mask = {.d32 = 0}; + + /* Disable all interrupts. */ + dwc_write_reg32(&global_regs->gintmsk, 0); + + /* Clear any pending interrupts. */ + dwc_write_reg32(&global_regs->gintsts, 0xFFFFFFFF); + + /* Enable the common interrupts */ + dwc_otg_enable_common_interrupts(core_if); + + /* + * Enable host mode interrupts without disturbing common + * interrupts. + */ + intr_mask.b.sofintr = 1; + intr_mask.b.portintr = 1; + intr_mask.b.hcintr = 1; + dwc_modify_reg32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32); +} + +/** + * 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) +{ + struct core_global_regs *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; + union hprt0_data hprt0 = {.d32 = 0}; + union fifosize_data nptxfifosize; + union fifosize_data ptxfifosize; + u32 i; + union hcchar_data hcchar; + union hcfg_data hcfg; + struct dwc_hc_regs *hc_regs; + int num_channels; + union gotgctl_data gotgctl = {.d32 = 0}; + + /* Restart the Phy Clock */ + dwc_write_reg32(core_if->pcgcctl, 0); + + /* Initialize Host Configuration Register */ + init_fslspclksel(core_if); + if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) { + hcfg.d32 = dwc_read_reg32(&host_if->host_global_regs->hcfg); + hcfg.b.fslssupp = 1; + dwc_write_reg32(&host_if->host_global_regs->hcfg, hcfg.d32); + } + + /* Configure data FIFO sizes */ + if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) { + /* Rx FIFO */ + dwc_write_reg32(&global_regs->grxfsiz, + params->host_rx_fifo_size); + + /* Non-periodic Tx FIFO */ + nptxfifosize.b.depth = params->host_nperio_tx_fifo_size; + nptxfifosize.b.startaddr = params->host_rx_fifo_size; + dwc_write_reg32(&global_regs->gnptxfsiz, nptxfifosize.d32); + + /* Periodic Tx FIFO */ + ptxfifosize.b.depth = params->host_perio_tx_fifo_size; + ptxfifosize.b.startaddr = nptxfifosize.b.startaddr + + nptxfifosize.b.depth; + dwc_write_reg32(&global_regs->hptxfsiz, ptxfifosize.d32); + } + + /* Clear Host Set HNP Enable in the OTG Control Register */ + gotgctl.b.hstsethnpen = 1; + dwc_modify_reg32(&global_regs->gotgctl, gotgctl.d32, 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.d32 = dwc_read_reg32(&hc_regs->hcchar); + hcchar.b.chen = 0; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); + } + + /* 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.d32 = dwc_read_reg32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); + + do { + hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); + if (++count > 200) { + printk(KERN_ERR "%s: Unable to clear halt on " + "channel %d\n", __func__, i); + break; + } + udelay(100); + } while (hcchar.b.chen); + } + + /* Turn on the vbus power. */ + printk(KERN_INFO "Init: Port Power? op_state=%s\n", + op_state_str(core_if->xceiv->state)); + + if (core_if->xceiv->state == OTG_STATE_A_HOST) { + hprt0.d32 = dwc_otg_read_hprt0(core_if); + printk(KERN_INFO "Init: Power Port (%d)\n", hprt0.b.prtpwr); + if (hprt0.b.prtpwr == 0) { + hprt0.b.prtpwr = 1; + dwc_write_reg32(host_if->hprt0, hprt0.d32); + } + } + 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; + union hprt0_data 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.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtrst = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + ((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) +{ + struct core_global_regs *global_regs = core_if->core_global_regs; + union gintmsk_data intr_mask = {.d32 = 0}; + + /* + * Disable host mode interrupts without disturbing common + * interrupts. + */ + intr_mask.b.sofintr = 1; + intr_mask.b.portintr = 1; + intr_mask.b.hcintr = 1; + intr_mask.b.ptxfempty = 1; + intr_mask.b.nptxfempty = 1; + dwc_modify_reg32(&global_regs->gintmsk, intr_mask.d32, 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); + union hprt0_data hprt0 = {.d32 = 0}; + + /* Turn off all host-specific interrupts. */ + dwc_otg_disable_host_interrupts(dwc_hcd->core_if); + + /* + * 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 */ + printk(KERN_INFO "PortPower off\n"); + hprt0.b.prtpwr = 0; + dwc_write_reg32(dwc_hcd->core_if->host_if->hprt0, hprt0.d32); +} + +/** + * 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) +{ + union gintsts_data 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.d32 = 0; + intr.b.nptxfempty = 1; + intr.b.ptxfempty = 1; + intr.b.hcintr = 1; + dwc_modify_reg32(gintmsk_reg(hcd), intr.d32, 0); + dwc_modify_reg32(gintsts_reg(hcd), intr.d32, 0); + + 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) { + union hprt0_data hprt0 = {.d32 = 0}; + + printk(KERN_INFO "Disconnect: PortPower off\n"); + hprt0.b.prtpwr = 0; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + } + 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; + struct dwc_hc_regs *regs; + union hcchar_data 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.d32 = dwc_read_reg32( + ®s->hcchar); + + if (hcchar.b.chen) { + hcchar.b.chen = 0; + hcchar.b.chdis = 1; + hcchar.b.epdir = 0; + dwc_write_reg32(®s->hcchar, + hcchar.d32); + } + } + } + } + + 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.d32 = dwc_read_reg32(®s->hcchar); + + if (hcchar.b.chen) { + /* Halt the channel. */ + hcchar.b.chdis = 1; + dwc_write_reg32(®s->hcchar, + hcchar.d32); + } + 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) +{ + printk(KERN_INFO "Connect Timeout\n"); + printk(KERN_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 = 0, +}; + +/* + * 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; + union hprt0_data hprt0; + unsigned long flags; + + printk(KERN_INFO "%s\n", __func__); + spin_lock_irqsave(&hcd->lock, flags); + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtrst = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + spin_unlock_irqrestore(&hcd->lock, flags); + msleep(60); + spin_lock_irqsave(&hcd->lock, flags); + hprt0.b.prtrst = 0; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + 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); + union hprt0_data hprt0; + + printk(KERN_INFO "%s\n", __func__); + /* Now wait for 70 ms. */ + hprt0.d32 = dwc_otg_read_hprt0(core_if); + msleep(70); + hprt0.b.prtres = 0; /* Resume */ + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); +} + +/** + * 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) { + printk(KERN_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) { + printk(KERN_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) +{ + union gnptxsts_data nptxsts; + union hptxsts_data hptxsts; + union hcchar_data hcchar; + struct dwc_hc_regs *hc_regs; + struct core_global_regs *global_regs; + struct host_global_regs *host_global_regs; + + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + global_regs = core_if->core_global_regs; + 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. + */ + union hcintmsk_data hcintmsk; + hcintmsk.d32 = 0; + hcintmsk.b.chhltd = 1; + dwc_write_reg32(&hc_regs->hcintmsk, hcintmsk.d32); + + /* + * 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_write_reg32(&hc_regs->hcint, ~hcintmsk.d32); + + /* + * 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.d32 = dwc_read_reg32(&hc_regs->hcchar); + if (!hcchar.b.chen) + 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.d32 = dwc_read_reg32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 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.d32 = dwc_read_reg32(&global_regs->gnptxsts); + + if (!nptxsts.b.nptxqspcavail) + hcchar.b.chen = 0; + } else { + hptxsts.d32 = + dwc_read_reg32(&host_global_regs->hptxsts); + + if (!hptxsts.b.ptxqspcavail || + core_if->queuing_high_bandwidth) + hcchar.b.chen = 0; + } + } + dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); + + hc->halt_status = hlt_sts; + if (hcchar.b.chen) { + 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; + union hprt0_data hprt0; + unsigned long flags; + + spin_lock_irqsave(&hcd->lock, flags); + switch (val) { + case USB_PORT_FEAT_ENABLE: + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtena = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + break; + case USB_PORT_FEAT_SUSPEND: + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtres = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + + /* Clear Resume bit */ + spin_unlock_irqrestore(&hcd->lock, flags); + msleep(100); + spin_lock_irqsave(&hcd->lock, flags); + hprt0.b.prtres = 0; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + break; + case USB_PORT_FEAT_POWER: + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 0; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + 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: + printk(KERN_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; + union hprt0_data hprt0 = {.d32 = 0}; + struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd); + unsigned long flags; + union pcgcctl_data pcgcctl = {.d32 = 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) { + union gotgctl_data gotgctl = {.d32 = 0}; + gotgctl.b.hstsethnpen = 1; + dwc_modify_reg32(&core_if->core_global_regs->gotgctl, + 0, gotgctl.d32); + core_if->xceiv->state = OTG_STATE_A_SUSPEND; + } + + hprt0.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtsusp = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + + /* Suspend the Phy Clock */ + pcgcctl.b.stoppclk = 1; + dwc_write_reg32(core_if->pcgcctl, pcgcctl.d32); + + /* 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.d32 = dwc_otg_read_hprt0(core_if); + hprt0.b.prtpwr = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + break; + case USB_PORT_FEAT_RESET: + hprt0.d32 = 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.b.prtrst = 1; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + } + + /* 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.b.prtrst = 0; + dwc_write_reg32(core_if->host_if->hprt0, hprt0.d32); + break; + case USB_PORT_FEAT_INDICATOR: + /* Not supported */ + break; + default: + printk(KERN_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; + union hprt0_data hprt0 = {.d32 = 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; + printk(KERN_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; + desc->bitmap[0] = 0; + desc->bitmap[1] = 0xff; + 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) { + printk(KERN_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.d32 = dwc_read_reg32(core_if->host_if->hprt0); + + if (hprt0.b.prtconnsts) + port_status |= USB_PORT_STAT_CONNECTION; + if (hprt0.b.prtena) + port_status |= USB_PORT_STAT_ENABLE; + if (hprt0.b.prtsusp) + port_status |= USB_PORT_STAT_SUSPEND; + if (hprt0.b.prtovrcurract) + port_status |= USB_PORT_STAT_OVERCURRENT; + if (hprt0.b.prtrst) + port_status |= USB_PORT_STAT_RESET; + if (hprt0.b.prtpwr) + port_status |= USB_PORT_STAT_POWER; + + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) + port_status |= USB_PORT_STAT_HIGH_SPEED; + else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED) + port_status |= USB_PORT_STAT_LOW_SPEED; + + if (hprt0.b.prttstctl) + 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; + printk(KERN_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 __devinit dwc_otg_hcd_init(struct device *_dev, + struct dwc_otg_device *dwc_otg_device) +{ + struct usb_hcd *hcd = NULL; + struct dwc_hcd *dwc_hcd = NULL; + 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->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; + printk(KERN_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); + + printk(KERN_INFO "Using DMA mode\n"); + _dev->dma_mask = (void *) &dummy_mask; + _dev->coherent_dma_mask = ~0; + } else { + printk(KERN_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. + */ + retval = usb_add_hcd(hcd, otg_dev->irq, IRQF_SHARED); + if (retval < 0) + goto error2; + + /* + * 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; + printk(KERN_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_write_reg32(gintmsk_reg(dwc_hcd), 0); + dwc_modify_reg32(gahbcfg_reg(dwc_hcd), 1, 0); + + 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); + cancel_rearming_delayed_work(&dwc_hcd->core_if->usb_port_wakeup); + + 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); + union hfnum_data hfnum; + + hfnum.d32 = dwc_read_reg32(&dwc_hcd->core_if->host_if-> + host_global_regs->hfnum); + + return hfnum.b.frnum; +} + +/** + * 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; + union hcintmsk_data hc_intr_mask; + union gintmsk_data gintmsk = {.d32 = 0}; + union hcchar_data hcchar; + union hcsplt_data hcsplt; + u8 hc_num = hc->hc_num; + struct dwc_host_if *host_if = core_if->host_if; + struct dwc_hc_regs *hc_regs = host_if->hc_regs[hc_num]; + + /* Clear old interrupt conditions for this host channel. */ + hc_intr_mask.d32 = 0xFFFFFFFF; + hc_intr_mask.b.reserved = 0; + dwc_write_reg32(&hc_regs->hcint, hc_intr_mask.d32); + + /* Enable channel interrupts required for this transfer. */ + hc_intr_mask.d32 = 0; + hc_intr_mask.b.chhltd = 1; + if (core_if->dma_enable) { + hc_intr_mask.b.ahberr = 1; + + if (hc->error_state && !hc->do_split && + hc->ep_type != DWC_OTG_EP_TYPE_ISOC) { + hc_intr_mask.b.ack = 1; + if (hc->ep_is_in) { + hc_intr_mask.b.datatglerr = 1; + if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) + hc_intr_mask.b.nak = 1; + } + } + } else { + switch (hc->ep_type) { + case DWC_OTG_EP_TYPE_CONTROL: + case DWC_OTG_EP_TYPE_BULK: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.stall = 1; + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.datatglerr = 1; + + if (hc->ep_is_in) { + hc_intr_mask.b.bblerr = 1; + } else { + hc_intr_mask.b.nak = 1; + hc_intr_mask.b.nyet = 1; + if (hc->do_ping) + hc_intr_mask.b.ack = 1; + } + + if (hc->do_split) { + hc_intr_mask.b.nak = 1; + if (hc->complete_split) + hc_intr_mask.b.nyet = 1; + else + hc_intr_mask.b.ack = 1; + } + + if (hc->error_state) + hc_intr_mask.b.ack = 1; + break; + case DWC_OTG_EP_TYPE_INTR: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.nak = 1; + hc_intr_mask.b.stall = 1; + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.datatglerr = 1; + hc_intr_mask.b.frmovrun = 1; + + if (hc->ep_is_in) + hc_intr_mask.b.bblerr = 1; + if (hc->error_state) + hc_intr_mask.b.ack = 1; + + if (hc->do_split) { + if (hc->complete_split) + hc_intr_mask.b.nyet = 1; + else + hc_intr_mask.b.ack = 1; + } + break; + case DWC_OTG_EP_TYPE_ISOC: + hc_intr_mask.b.xfercompl = 1; + hc_intr_mask.b.frmovrun = 1; + hc_intr_mask.b.ack = 1; + + if (hc->ep_is_in) { + hc_intr_mask.b.xacterr = 1; + hc_intr_mask.b.bblerr = 1; + } + break; + } + } + dwc_write_reg32(&hc_regs->hcintmsk, hc_intr_mask.d32); + + /* Enable the top level host channel interrupt. */ + intr_enable = (1 << hc_num); + dwc_modify_reg32(&host_if->host_global_regs->haintmsk, 0, intr_enable); + + /* Make sure host channel interrupts are enabled. */ + gintmsk.b.hcintr = 1; + dwc_modify_reg32(&core_if->core_global_regs->gintmsk, 0, gintmsk.d32); + + /* + * Program the HCCHARn register with the endpoint characteristics for + * the current transfer. + */ + hcchar.d32 = 0; + hcchar.b.devaddr = hc->dev_addr; + hcchar.b.epnum = hc->ep_num; + hcchar.b.epdir = hc->ep_is_in; + hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW); + hcchar.b.eptype = hc->ep_type; + hcchar.b.mps = hc->max_packet; + dwc_write_reg32(&host_if->hc_regs[hc_num]->hcchar, hcchar.d32); + + /* Program the HCSPLIT register for SPLITs */ + hcsplt.d32 = 0; + if (hc->do_split) { + hcsplt.b.compsplt = hc->complete_split; + hcsplt.b.xactpos = hc->xact_pos; + hcsplt.b.hubaddr = hc->hub_addr; + hcsplt.b.prtaddr = hc->port_addr; + } + dwc_write_reg32(&host_if->hc_regs[hc_num]->hcsplt, hcsplt.d32); +} + +/** + * 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 = (u8 *) urb->transfer_dma + + 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 *) 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 *) 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 *) 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, union hcchar_data *hcchar) +{ + if (hc->ep_type == DWC_OTG_EP_TYPE_INTR || + hc->ep_type == DWC_OTG_EP_TYPE_ISOC) { + union hfnum_data hfnum; + + hfnum.d32 = dwc_read_reg32( + &core_if->host_if->host_global_regs->hfnum); + + /* 1 if _next_ frame is odd, 0 if it's even */ + hcchar->b.oddfrm = (hfnum.b.frnum & 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. + */ +void dwc_otg_hc_do_ping(struct core_if *core_if, struct dwc_hc *hc) +{ + union hcchar_data hcchar; + union hctsiz_data hctsiz; + struct dwc_hc_regs *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + + hctsiz.d32 = 0; + hctsiz.b.dopng = 1; + hctsiz.b.pktcnt = 1; + dwc_write_reg32(&hc_regs->hctsiz, hctsiz.d32); + + hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); +} + +/** + * 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_datafifo32(data_fifo, + get_unaligned(data_buff)); + else + /* xfer_buff is DWORD aligned. */ + for (i = 0; i < dword_count; i++, data_buff++) + dwc_write_datafifo32(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) +{ + union hcchar_data hcchar; + union hctsiz_data hctsiz; + 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; + struct dwc_hc_regs *hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hctsiz.d32 = 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.b.dopng = 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.b.xfersize = 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.b.xfersize = hc->xfer_len; + } + + hc->start_pkt_count = num_packets; + hctsiz.b.pktcnt = num_packets; + hctsiz.b.pid = hc->data_pid_start; + dwc_write_reg32(&hc_regs->hctsiz, hctsiz.d32); + + if (core_if->dma_enable) + dwc_write_reg32(&hc_regs->hcdma, (u32) hc->xfer_buff); + + /* Start the split */ + if (hc->do_split) { + union hcsplt_data hcsplt; + + hcsplt.d32 = dwc_read_reg32(&hc_regs->hcsplt); + hcsplt.b.spltena = 1; + dwc_write_reg32(&hc_regs->hcsplt, hcsplt.d32); + } + + hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); + hcchar.b.multicnt = hc->multi_count; + hc_set_even_odd_frame(core_if, hc, &hcchar); + + /* Set host channel enable after all other setup is complete. */ + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); + + 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. 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. + */ + union hcchar_data hcchar; + struct dwc_hc_regs *hc_regs = + core_if->host_if->hc_regs[hc->hc_num]; + + hcchar.d32 = dwc_read_reg32(&hc_regs->hcchar); + hc_set_even_odd_frame(core_if, hc, &hcchar); + + hcchar.b.chen = 1; + hcchar.b.chdis = 0; + dwc_write_reg32(&hc_regs->hcchar, hcchar.d32); + + 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) { + union hcchar_data hcchar; + struct dwc_hc_regs *hc_regs; + + hc_regs = core_if->host_if->hc_regs[hc->hc_num]; + hcchar.d32 = dwc_read_reg32(&hc_regs->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) +{ + union gnptxsts_data tx_status; + 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; + struct core_global_regs *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.d32 = dwc_read_reg32(®s->gnptxsts); + if (!hcd->core_if->dma_enable && + tx_status.b.nptxqspcavail == 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, + tx_status.b.nptxfspcavail); + + 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) { + union gintmsk_data intr_mask = {.d32 = 0}; + + intr_mask.b.nptxfempty = 1; + 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_modify_reg32(gintmsk_reg(hcd), 0, intr_mask.d32); + } 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_modify_reg32(gintmsk_reg(hcd), intr_mask.d32, 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) +{ + union hptxsts_data tx_status; + struct list_head *qh_ptr; + struct dwc_qh *qh; + int status; + int no_queue_space = 0; + int no_fifo_space = 0; + struct host_global_regs *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.d32 = dwc_read_reg32(&host_regs->hptxsts); + if (tx_status.b.ptxqspcavail == 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, + tx_status.b.ptxfspcavail); + 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) { + union gintmsk_data intr_mask = {.d32 = 0}; + + intr_mask.b.ptxfempty = 1; + + 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_modify_reg32(gintmsk_reg(hcd), 0, intr_mask.d32); + 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_modify_reg32(gintmsk_reg(hcd), intr_mask.d32, 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. + */ + union gintmsk_data gintmsk = {.d32 = 0}; + gintmsk.b.nptxfempty = 1; + dwc_modify_reg32(gintmsk_reg(hcd), gintmsk.d32, 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_otg/dwc_otg_hcd.h b/drivers/usb/dwc_otg/dwc_otg_hcd.h new file mode 100644 index 0000000..dfe165e --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_hcd.h @@ -0,0 +1,426 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * Modified by Chuck Meade + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(__DWC_HCD_H__) +#define __DWC_HCD_H__ + +#include +#include + +#include "dwc_otg_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 __init 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 u32 *gintsts_reg(struct dwc_hcd *hcd) +{ + return (u32 *) &hcd->core_if->core_global_regs->gintsts; +} + +static inline u32 *gintmsk_reg(struct dwc_hcd *hcd) +{ + return (u32 *) &hcd->core_if->core_global_regs->gintmsk; +} + +static inline u32 *gahbcfg_reg(struct dwc_hcd *hcd) +{ + return (u32 *) &hcd->core_if->core_global_regs->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_otg/dwc_otg_hcd_intr.c b/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c new file mode 100644 index 0000000..613d9e6 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_hcd_intr.c @@ -0,0 +1,1478 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * Modified by Chuck Meade + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dwc_otg_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) +{ + union hfnum_data hfnum; + struct list_head *qh_entry; + struct dwc_qh *qh; + enum dwc_transaction_type tr_type; + union gintsts_data gintsts = {.d32 = 0}; + + hfnum.d32 = + dwc_read_reg32(&hcd->core_if->host_if->host_global_regs->hfnum); + + hcd->frame_number = hfnum.b.frnum; + + /* 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.b.sofintr = 1; + dwc_write_reg32(gintsts_reg(hcd), gintsts.d32); + 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) +{ + union host_grxsts_data grxsts; + struct dwc_hc *hc = NULL; + + grxsts.d32 = dwc_read_reg32(&hcd->core_if->core_global_regs->grxstsp); + hc = hcd->hc_ptr_array[grxsts.b.chnum]; + + /* Packet Status */ + switch (grxsts.b.pktsts) { + case DWC_GRXSTS_PKTSTS_IN: + /* Read the data into the host buffer. */ + if (grxsts.b.bcnt > 0) { + dwc_otg_read_packet(hcd->core_if, hc->xfer_buff, + grxsts.b.bcnt); + /* Update the HC fields for the next packet received. */ + hc->xfer_count += grxsts.b.bcnt; + hc->xfer_buff += grxsts.b.bcnt; + } + 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: + printk(KERN_ERR "RX_STS_Q Interrupt: Unknown status %d\n", + grxsts.b.pktsts); + 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, union hprt0_data hprt0) +{ + int adjusted = 0; + union gusbcfg_data usbcfg; + struct core_params *params = hcd->core_if->core_params; + struct core_global_regs *g_regs = hcd->core_if->core_global_regs; + struct host_global_regs *h_regs = + hcd->core_if->host_if->host_global_regs; + + usbcfg.d32 = dwc_read_reg32(&g_regs->gusbcfg); + + if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED || + hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) { + /* Low power */ + union hcfg_data hcfg; + + if (usbcfg.b.phylpwrclksel == 0) { + /* Set PHY low power clock select for FS/LS devices */ + usbcfg.b.phylpwrclksel = 1; + dwc_write_reg32(&g_regs->gusbcfg, usbcfg.d32); + adjusted = 1; + } + + hcfg.d32 = dwc_read_reg32(&h_regs->hcfg); + if (hprt0.b.prtspd == 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 (hcfg.b.fslspclksel != DWC_HCFG_6_MHZ) { + hcfg.b.fslspclksel = DWC_HCFG_6_MHZ; + dwc_write_reg32(&h_regs->hcfg, hcfg.d32); + adjusted = 1; + } + } else if (hcfg.b.fslspclksel != DWC_HCFG_48_MHZ) { + /* 48 MHZ and clock select is not 48 MHZ */ + hcfg.b.fslspclksel = DWC_HCFG_48_MHZ; + dwc_write_reg32(&h_regs->hcfg, hcfg.d32); + adjusted = 1; + } + } else if (usbcfg.b.phylpwrclksel == 1) { + usbcfg.b.phylpwrclksel = 0; + dwc_write_reg32(&g_regs->gusbcfg, usbcfg.d32); + 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, union hprt0_data 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; + union hprt0_data hprt0; + union hprt0_data hprt0_modify; + + hprt0.d32 = dwc_read_reg32(hcd->core_if->host_if->hprt0); + hprt0_modify.d32 = dwc_read_reg32(hcd->core_if->host_if->hprt0); + + /* + * Clear appropriate bits in HPRT0 to clear the interrupt bit in + * GINTSTS + */ + hprt0_modify.b.prtena = 0; + hprt0_modify.b.prtconndet = 0; + hprt0_modify.b.prtenchng = 0; + hprt0_modify.b.prtovrcurrchng = 0; + + /* Port connect detected interrupt */ + if (hprt0.b.prtconndet) { + /* Set the status flags and clear interrupt*/ + hcd->flags.b.port_connect_status_change = 1; + hcd->flags.b.port_connect_status = 1; + hprt0_modify.b.prtconndet = 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 (hprt0.b.prtenchng) { + /* Set the internal flag if the port was disabled */ + if (hprt0.b.prtena) + port_enabled(hcd, hprt0); + else + hcd->flags.b.port_enable_change = 1; + + /* Clear the interrupt */ + hprt0_modify.b.prtenchng = 1; + retval |= 1; + } + + /* Overcurrent change interrupt */ + if (hprt0.b.prtovrcurrchng) { + hcd->flags.b.port_over_current_change = 1; + hprt0_modify.b.prtovrcurrchng = 1; + retval |= 1; + } + + /* Clear the port interrupts */ + dwc_write_reg32(hcd->core_if->host_if->hprt0, hprt0_modify.d32); + 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, struct dwc_hc_regs *regs, + struct dwc_qtd *qtd, enum dwc_halt_status halt_status, + int *_short_read) +{ + union hctsiz_data hctsiz; + u32 length; + + if (_short_read) + *_short_read = 0; + + hctsiz.d32 = dwc_read_reg32(®s->hctsiz); + if (halt_status == DWC_OTG_HC_XFER_COMPLETE) { + if (hc->ep_is_in) { + length = hc->xfer_len - hctsiz.b.xfersize; + if (_short_read) + *_short_read = (hctsiz.b.xfersize != 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 - hctsiz.b.pktcnt) * + 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, + struct dwc_hc_regs *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, struct dwc_hc_regs *regs, + struct dwc_qtd *qtd) +{ + union hctsiz_data hctsiz; + hctsiz.d32 = dwc_read_reg32(®s->hctsiz); + + if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) { + struct dwc_qh *qh = hc->qh; + if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) + qh->data_toggle = DWC_OTG_HC_PID_DATA0; + else + qh->data_toggle = DWC_OTG_HC_PID_DATA1; + } else { + if (hctsiz.b.pid == 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, struct dwc_hc_regs *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: + printk(KERN_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: + printk(KERN_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) { + union gintmsk_data gintmsk = {.d32 = 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.b.nptxfempty = 1; + dwc_modify_reg32(gintmsk_reg(hcd), 0, gintmsk.d32); + } 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.b.ptxfempty = 1; + dwc_modify_reg32(gintmsk_reg(hcd), 0, gintmsk.d32); + } + } +} + +/** + * 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, + enum dwc_halt_status halt_status, int *must_free) +{ + union hcint_data hcint; + + qtd->error_count = 0; + hcint.d32 = dwc_read_reg32(®s->hcint); + if (hcint.b.nyet) { + union hcint_data hcint_clear = { .d32 = 0}; + + hcint_clear.b.nyet = 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_write_reg32(&(regs->hcint), hcint_clear.d32); + } + + /* + * 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, + enum dwc_halt_status halt_status, int *must_free) +{ + union hctsiz_data hctsiz; + + hctsiz.d32 = dwc_read_reg32(®s->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 || hctsiz.b.pktcnt == 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, + struct dwc_hc_regs *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; + union hcintmsk_data hcintmsk = {.d32 = 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.b.xfercompl = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + struct urb *urb = qtd->urb; + int pipe_type = usb_pipetype(urb->pipe); + union hcintmsk_data hcintmsk = {.d32 = 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.b.stall = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + union hcintmsk_data hcintmsk = {.d32 = 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.b.nak = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, + int *must_free) +{ + union hcintmsk_data hcintmsk = {.d32 = 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.b.ack = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + union hcintmsk_data hcintmsk = {.d32 = 0}; + union hcint_data hcint_clear = {.d32 = 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.b.nyet = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 0); + /* clear nyet */ + hcint_clear.b.nyet = 1; + dwc_write_reg32(&(regs->hcint), hcint_clear.d32); + 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + union hcintmsk_data hcintmsk = {.d32 = 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.b.bblerr = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd) +{ + union hcchar_data hcchar; + union hcsplt_data hcsplt; + union hctsiz_data hctsiz; + u32 hcdma; + struct urb *urb = qtd->urb; + union hcintmsk_data hcintmsk = {.d32 = 0}; + + hcchar.d32 = dwc_read_reg32(®s->hcchar); + hcsplt.d32 = dwc_read_reg32(®s->hcsplt); + hctsiz.d32 = dwc_read_reg32(®s->hctsiz); + hcdma = dwc_read_reg32(®s->hcdma); + + printk(KERN_ERR "AHB ERROR, Channel %d\n", hc->hc_num); + printk(KERN_ERR " hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, + hcsplt.d32); + printk(KERN_ERR " hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, hcdma); + + printk(KERN_ERR " Device address: %d\n", usb_pipedevice(urb->pipe)); + printk(KERN_ERR " Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe), + (usb_pipein(urb->pipe) ? "IN" : "OUT")); + + printk(KERN_ERR " Endpoint type: %s\n", pipetype_str(urb->pipe)); + printk(KERN_ERR " Speed: %s\n", dev_speed_str(urb->dev->speed)); + printk(KERN_ERR " Max packet size: %d\n", + usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); + printk(KERN_ERR " Data buffer length: %d\n", + urb->transfer_buffer_length); + printk(KERN_ERR " Transfer buffer: %p, Transfer DMA: %p\n", + urb->transfer_buffer, (void *) urb->transfer_dma); + printk(KERN_ERR " Setup buffer: %p, Setup DMA: %p\n", + urb->setup_packet, (void *) urb->setup_dma); + printk(KERN_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.b.ahberr = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + enum dwc_halt_status status = DWC_OTG_HC_XFER_XACT_ERR; + union hcintmsk_data hcintmsk = {.d32 = 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.b.xacterr = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + enum dwc_halt_status status = DWC_OTG_HC_XFER_FRAME_OVERRUN; + union hcintmsk_data hcintmsk = {.d32 = 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.b.frmovrun = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd) +{ + union hcintmsk_data hcintmsk = {.d32 = 0}; + + if (hc->ep_is_in) + qtd->error_count = 0; + else + printk(KERN_ERR "Data Toggle Error on OUT transfer, channel " + "%d\n", hc->hc_num); + + /* disable datatglerr */ + hcintmsk.b.datatglerr = 1; + dwc_modify_reg32(®s->hcintmsk, hcintmsk.d32, 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, + struct dwc_hc_regs *regs, struct dwc_qtd *qtd, int *must_free) +{ + union hcint_data hcint; + union hcintmsk_data hcintmsk; + + 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.d32 = dwc_read_reg32(®s->hcint); + hcintmsk.d32 = dwc_read_reg32(®s->hcintmsk); + if (hcint.b.xfercomp) { + /* + * 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 (hcint.b.stall) { + handle_hc_stall_intr(hcd, hc, regs, qtd, must_free); + } else if (hcint.b.xacterr) { + /* + * 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 (hcint.b.nyet) { + /* + * 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 (hcint.b.bblerr) { + handle_hc_babble_intr(hcd, hc, regs, qtd, must_free); + } else if (hcint.b.frmovrun) { + handle_hc_frmovrun_intr(hcd, hc, regs, qtd, must_free); + } else if (hcint.b.datatglerr) { + 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 (hcint.b.nak && !hcintmsk.b.nak) { + /* + * 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 (hcint.b.ack && !hcintmsk.b.ack) { + /* + * 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 { + printk(KERN_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.d32, + dwc_read_reg32(gintsts_reg(hcd))); + } + } +} + +/** + * 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, + struct dwc_hc_regs *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; + union hcint_data hcint; + union hcintmsk_data hcintmsk; + struct dwc_hc *hc; + struct dwc_hc_regs *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.d32 = dwc_read_reg32(&hc_regs->hcint); + hcintmsk.d32 = dwc_read_reg32(&hc_regs->hcintmsk); + + hcint.d32 = hcint.d32 & hcintmsk.d32; + if (!hcd->core_if->dma_enable && hcint.b.chhltd && hcint.d32 != 0x2) + hcint.b.chhltd = 0; + + if (hcint.b.xfercomp) { + 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.b.nyet = 0; + } + + if (hcint.b.chhltd) + retval |= handle_hc_chhltd_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.ahberr) + retval |= handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd); + if (hcint.b.stall) + retval |= handle_hc_stall_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.nak) + retval |= handle_hc_nak_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.ack) + retval |= handle_hc_ack_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.nyet) + retval |= handle_hc_nyet_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.xacterr) + retval |= handle_hc_xacterr_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.bblerr) + retval |= handle_hc_babble_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.frmovrun) + retval |= handle_hc_frmovrun_intr(hcd, hc, hc_regs, + qtd, &must_free); + if (hcint.b.datatglerr) + 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_read_reg32(&core_if->host_if->host_global_regs->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; + union haint_data haint; + + /* + * Clear appropriate bits in HCINTn to clear the interrupt bit in + * GINTSTS + */ + haint.d32 = dwc_otg_read_host_all_channels_intr(hcd->core_if); + for (i = 0; i < hcd->core_if->core_params->host_channels; i++) + if (haint.b2.chint & (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; + union gintsts_data gintsts; + + /* Check if HOST Mode */ + if (dwc_otg_is_host_mode(core_if)) { + spin_lock(&hcd->lock); + gintsts.d32 = dwc_otg_read_core_intr(core_if); + if (!gintsts.d32) { + spin_unlock(&hcd->lock); + return IRQ_NONE; + } + + if (gintsts.b.sofintr) + ret |= dwc_otg_hcd_handle_sof_intr(hcd); + if (gintsts.b.rxstsqlvl) + ret |= dwc_otg_hcd_handle_rx_status_q_level_intr(hcd); + if (gintsts.b.nptxfempty) + ret |= dwc_otg_hcd_handle_np_tx_fifo_empty_intr(hcd); + if (gintsts.b.portintr) + ret |= dwc_otg_hcd_handle_port_intr(hcd); + if (gintsts.b.hcintr) + ret |= dwc_otg_hcd_handle_hc_intr(hcd); + if (gintsts.b.ptxfempty) + ret |= dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(hcd); + + spin_unlock(&hcd->lock); + } + return ret; +} diff --git a/drivers/usb/dwc_otg/dwc_otg_hcd_queue.c b/drivers/usb/dwc_otg/dwc_otg_hcd_queue.c new file mode 100644 index 0000000..a129a14 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_hcd_queue.c @@ -0,0 +1,710 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * Modified by Chuck Meade + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file contains the functions to manage Queue Heads and Queue + * Transfer Descriptors. + */ + +#include "dwc_otg_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. */ + union hprt0_data 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.d32 = dwc_read_reg32(hcd->core_if->host_if->hprt0); + if (hprt.b.prtspd == 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) { + printk(KERN_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) { + printk(KERN_NOTICE "%s: Insufficient periodic bandwidth for " + "periodic transfer.\n", __func__); + return status; + } + status = check_max_xfer_size(hcd, qh); + if (status) { + printk(KERN_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. + */ +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_otg/dwc_otg_param.c b/drivers/usb/dwc_otg/dwc_otg_param.c new file mode 100644 index 0000000..30a23a5 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_param.c @@ -0,0 +1,744 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * Modified by Stefan Roese , DENX Software Engineering + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This file provides dwc_otg driver parameter and parameter checking. + */ + +#include "dwc_otg_cil.h" + +/* Global Debug Level Mask. */ +static u32 g_dbg_lvl = 0x0; /* OFF */ + +/* + * Encapsulate the module parameter settings + */ +struct core_params dwc_otg_module_params = { + .opt = -1, + .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, + .data_fifo_size = -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, +}; + +/** + * Determines if the OTG capability parameter is valid under the current + * hardware configuration. + */ +static int is_valid_otg_cap(struct core_if *core_if) +{ + int valid = 1; + + switch (dwc_otg_module_params.otg_cap) { + case DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE: + if (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG) + valid = 0; + break; + case DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE: + if (core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG && + core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG && + core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE && + core_if->hwcfg2.b.op_mode != + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST) + valid = 0; + break; + case DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE: + /* always valid */ + break; + } + return valid; +} + +/** + * Returns a valid OTG capability setting for the current hardware + * configuration. + */ +static int get_valid_otg_cap(struct core_if *core_if) +{ + if (core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG || + core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG || + core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE || + core_if->hwcfg2.b.op_mode == + DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST) + return DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE; + else + return DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE; +} + +/** + * Checks that, if the user set a periodic Tx FIFO size parameter, the size is + * within the valid range. If not, the parameter is set to the default. + */ +static int chk_param_perio_tx_fifo_sizes(int retval) +{ + u32 *s = &dwc_otg_module_params.dev_perio_tx_fifo_size[0]; + u32 i; + + for (i = 0; i < MAX_PERIO_FIFOS; i++, s++) { + if (*s != -1 && (*s < 4 || *s > 768)) { + printk(KERN_ERR "`%d' invalid for parameter " + "`dev_perio_tx_fifo_size_%d'\n", *s, i); + + *s = dwc_param_dev_perio_tx_fifo_size_default; + retval++; + } + } + return retval; +} + +/** + * Checks that, if the user set a Tx FIFO size parameter, the size is within the + * valid range. If not, the parameter is set to the default. + */ +static int chk_param_tx_fifo_sizes(int retval) +{ + u32 *s = &dwc_otg_module_params.dev_tx_fifo_size[0]; + u32 i; + + for (i = 0; i < MAX_TX_FIFOS; i++, s++) { + if (*s != -1 && (*s < 4 || *s > 768)) { + printk(KERN_ERR "`%d' invalid for parameter " + "`dev_perio_tx_fifo_size_%d'\n", *s, i); + + *s = dwc_param_dev_tx_fifo_size_default; + retval++; + } + } + return retval; +} + +/** + * 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 chk_valid_perio_tx_fifo_sizes(struct core_if *core_if, int retval) +{ + struct core_global_regs *regs = core_if->core_global_regs; + u32 *param_size = &dwc_otg_module_params.dev_perio_tx_fifo_size[0]; + u32 i; + + for (i = 0; i < MAX_PERIO_FIFOS; i++, param_size++) { + int changed = 1; + int error = 0; + u32 size; + + if (*param_size == -1) { + changed = 0; + *param_size = dwc_param_dev_perio_tx_fifo_size_default; + } + + size = dwc_read_reg32(®s->dptxfsiz_dieptxf[i]); + if (*param_size > size) { + if (changed) { + printk(KERN_ERR "%d' invalid for parameter " + "`dev_perio_tx_fifo_size_%d'. Check HW " + "configuration.\n", *param_size, i); + error = 1; + } + *param_size = size; + } + retval += error; + } + return retval; +} + +/** + * 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 chk_valid_tx_fifo_sizes(struct core_if *core_if, int retval) +{ + struct core_global_regs *regs = core_if->core_global_regs; + u32 *param_size = &dwc_otg_module_params.dev_tx_fifo_size[0]; + u32 i; + + for (i = 0; i < MAX_TX_FIFOS; i++, param_size) { + int changed = 1; + int error = 0; + u32 size; + + if (*param_size == -1) { + changed = 0; + *param_size = dwc_param_dev_tx_fifo_size_default; + } + + size = dwc_read_reg32(®s->dptxfsiz_dieptxf[i]); + if (*param_size > size) { + if (changed) { + printk(KERN_ERR "%d' invalid for parameter " + "`dev_tx_fifo_size_%d'. Check HW " + "configuration.\n", *param_size, i); + error = 1; + } + *param_size = size; + } + retval += error; + } + return retval; +} + +/** + * This function is called during module intialization to verify that + * the module parameters are in a valid state. + */ +int __devinit check_parameters(struct core_if *core_if) +{ + int retval = 0; + + /* Checks if the parameter is outside of its valid range of values */ +#define DWC_OTG_PARAM_TEST(_param_, _low_, _high_) \ + ((dwc_otg_module_params._param_ < (_low_)) || \ + (dwc_otg_module_params._param_ > (_high_))) + + /* + * If the parameter has been set by the user, check that the parameter + * value is within the value range of values. If not, report a module + * error. + */ +#define DWC_OTG_PARAM_ERR(_param_, _low_, _high_, _string_) \ + do { \ + if (dwc_otg_module_params._param_ != -1) { \ + if (DWC_OTG_PARAM_TEST(_param_, (_low_), (_high_))) { \ + printk(KERN_ERR "`%d' invalid for parameter " \ + "`%s'\n", \ + dwc_otg_module_params._param_, \ + _string_); \ + dwc_otg_module_params._param_ = \ + dwc_param_##_param_##_default; \ + retval++; \ + } \ + } \ + } while (0) + + DWC_OTG_PARAM_ERR(opt, 0, 1, "opt"); + DWC_OTG_PARAM_ERR(otg_cap, 0, 2, "otg_cap"); + DWC_OTG_PARAM_ERR(dma_enable, 0, 1, "dma_enable"); + DWC_OTG_PARAM_ERR(speed, 0, 1, "speed"); + + DWC_OTG_PARAM_ERR(host_support_fs_ls_low_power, 0, 1, + "host_support_fs_ls_low_power"); + DWC_OTG_PARAM_ERR(host_ls_low_power_phy_clk, 0, 1, + "host_ls_low_power_phy_clk"); + + DWC_OTG_PARAM_ERR(enable_dynamic_fifo, 0, 1, "enable_dynamic_fifo"); + DWC_OTG_PARAM_ERR(data_fifo_size, 32, 32768, "data_fifo_size"); + DWC_OTG_PARAM_ERR(dev_rx_fifo_size, 16, 32768, "dev_rx_fifo_size"); + DWC_OTG_PARAM_ERR(dev_nperio_tx_fifo_size, 16, 32768, + "dev_nperio_tx_fifo_size"); + DWC_OTG_PARAM_ERR(host_rx_fifo_size, 16, 32768, "host_rx_fifo_size"); + DWC_OTG_PARAM_ERR(host_nperio_tx_fifo_size, 16, 32768, + "host_nperio_tx_fifo_size"); + DWC_OTG_PARAM_ERR(host_perio_tx_fifo_size, 16, 32768, + "host_perio_tx_fifo_size"); + + DWC_OTG_PARAM_ERR(max_transfer_size, 2047, 524288, + "max_transfer_size"); + DWC_OTG_PARAM_ERR(max_packet_count, 15, 511, "max_packet_count"); + + DWC_OTG_PARAM_ERR(host_channels, 1, 16, "host_channels"); + DWC_OTG_PARAM_ERR(dev_endpoints, 1, 15, "dev_endpoints"); + + DWC_OTG_PARAM_ERR(phy_type, 0, 2, "phy_type"); + DWC_OTG_PARAM_ERR(phy_ulpi_ddr, 0, 1, "phy_ulpi_ddr"); + DWC_OTG_PARAM_ERR(phy_ulpi_ext_vbus, 0, 1, "phy_ulpi_ext_vbus"); + DWC_OTG_PARAM_ERR(i2c_enable, 0, 1, "i2c_enable"); + DWC_OTG_PARAM_ERR(ulpi_fs_ls, 0, 1, "ulpi_fs_ls"); + DWC_OTG_PARAM_ERR(ts_dline, 0, 1, "ts_dline"); + + if (dwc_otg_module_params.dma_burst_size != -1) { + if (DWC_OTG_PARAM_TEST(dma_burst_size, 1, 1) && + DWC_OTG_PARAM_TEST(dma_burst_size, 4, 4) && + DWC_OTG_PARAM_TEST(dma_burst_size, 8, 8) && + DWC_OTG_PARAM_TEST(dma_burst_size, 16, 16) && + DWC_OTG_PARAM_TEST(dma_burst_size, 32, 32) && + DWC_OTG_PARAM_TEST(dma_burst_size, 64, 64) && + DWC_OTG_PARAM_TEST(dma_burst_size, 128, 128) && + DWC_OTG_PARAM_TEST(dma_burst_size, 256, 256)) { + printk(KERN_ERR "`%d' invalid for parameter " + "`dma_burst_size'\n", + dwc_otg_module_params.dma_burst_size); + dwc_otg_module_params.dma_burst_size = 32; + retval++; + } + } + + if (dwc_otg_module_params.phy_utmi_width != -1) { + if (DWC_OTG_PARAM_TEST(phy_utmi_width, 8, 8) && + DWC_OTG_PARAM_TEST(phy_utmi_width, 16, 16)) { + printk(KERN_ERR "`%d'invalid for parameter " + "`phy_utmi_width'\n", + dwc_otg_module_params.phy_utmi_width); + dwc_otg_module_params.phy_utmi_width = 8; + retval++; + } + } + + DWC_OTG_PARAM_ERR(en_multiple_tx_fifo, 0, 1, "en_multiple_tx_fifo"); + retval += chk_param_perio_tx_fifo_sizes(retval); + retval += chk_param_tx_fifo_sizes(retval); + + DWC_OTG_PARAM_ERR(thr_ctl, 0, 7, "thr_ctl"); + DWC_OTG_PARAM_ERR(tx_thr_length, 8, 128, "tx_thr_length"); + DWC_OTG_PARAM_ERR(rx_thr_length, 8, 128, "rx_thr_length"); + + /* + * At this point, all module parameters that have been set by the user + * are valid, and those that have not are left unset. Now set their + * default values and/or check the parameters against the hardware + * configurations of the OTG core. + */ + + /* + * This sets the parameter to the default value if it has not been set + * by the user + */ +#define DWC_OTG_PARAM_SET_DEFAULT(_param_) ({ \ + int changed = 1; \ + if (dwc_otg_module_params._param_ == -1) { \ + changed = 0; \ + dwc_otg_module_params._param_ = \ + dwc_param_##_param_##_default; \ + } \ + changed; \ + }) + + /* + * This checks the macro against the hardware configuration to see if it + * is valid. It is possible that the default value could be invalid. + * In this case, it will report a module error if the user touched the + * parameter. Otherwise it will adjust the value without any error. + */ +#define DWC_OTG_PARAM_CHECK_VALID(_param_, _str_, _is_valid_ , _set_valid_) ({ \ + int changed = DWC_OTG_PARAM_SET_DEFAULT(_param_); \ + int error = 0; \ + if (!(_is_valid_)) { \ + if (changed) { \ + printk(KERN_ERR "`%d' invalid for parameter " \ + "`%s' Check HW configuration.\n", \ + dwc_otg_module_params._param_, \ + _str_); \ + error = 1; \ + } \ + dwc_otg_module_params._param_ = (_set_valid_); \ + } \ + error; \ + }) + + /* OTG Cap */ + retval += DWC_OTG_PARAM_CHECK_VALID(otg_cap, "otg_cap", + is_valid_otg_cap(core_if), get_valid_otg_cap(core_if)); + + retval += DWC_OTG_PARAM_CHECK_VALID(dma_enable, "dma_enable", + ((dwc_otg_module_params.dma_enable == 1) && + (core_if->hwcfg2.b.architecture == 0)) ? 0 : 1, 0); + retval += DWC_OTG_PARAM_CHECK_VALID(opt, "opt", 1, 0); + DWC_OTG_PARAM_SET_DEFAULT(dma_burst_size); + retval += DWC_OTG_PARAM_CHECK_VALID(host_support_fs_ls_low_power, + "host_support_fs_ls_low_power", 1, 0); + retval += DWC_OTG_PARAM_CHECK_VALID(enable_dynamic_fifo, + "enable_dynamic_fifo", + ((dwc_otg_module_params.enable_dynamic_fifo == 0) || + (core_if->hwcfg2.b.dynamic_fifo == 1)), 0); + retval += DWC_OTG_PARAM_CHECK_VALID(data_fifo_size, "data_fifo_size", + (dwc_otg_module_params.data_fifo_size <= + core_if->hwcfg3.b.dfifo_depth), + core_if->hwcfg3.b.dfifo_depth); + retval += DWC_OTG_PARAM_CHECK_VALID(dev_rx_fifo_size, + "dev_rx_fifo_size", + (dwc_otg_module_params.dev_rx_fifo_size <= + dwc_read_reg32(&core_if->core_global_regs->grxfsiz)), + dwc_read_reg32(&core_if->core_global_regs->grxfsiz)); + retval += DWC_OTG_PARAM_CHECK_VALID(dev_nperio_tx_fifo_size, + "dev_nperio_tx_fifo_size", + (dwc_otg_module_params.dev_nperio_tx_fifo_size <= + (dwc_read_reg32(&core_if->core_global_regs->gnptxfsiz) + >> 16)), + (dwc_read_reg32(&core_if->core_global_regs->gnptxfsiz) + >> 16)); + retval += DWC_OTG_PARAM_CHECK_VALID(host_rx_fifo_size, + "host_rx_fifo_size", + (dwc_otg_module_params.host_rx_fifo_size <= + dwc_read_reg32(&core_if->core_global_regs->grxfsiz)), + dwc_read_reg32(&core_if->core_global_regs->grxfsiz)); + retval += DWC_OTG_PARAM_CHECK_VALID(host_nperio_tx_fifo_size, + "host_nperio_tx_fifo_size", + (dwc_otg_module_params.host_nperio_tx_fifo_size <= + (dwc_read_reg32(&core_if->core_global_regs->gnptxfsiz) + >> 16)), + (dwc_read_reg32(&core_if->core_global_regs->gnptxfsiz) + >> 16)); + retval += DWC_OTG_PARAM_CHECK_VALID(host_perio_tx_fifo_size, + "host_perio_tx_fifo_size", + (dwc_otg_module_params.host_perio_tx_fifo_size <= + ((dwc_read_reg32(&core_if->core_global_regs->hptxfsiz) + >> 16))), + ((dwc_read_reg32(&core_if->core_global_regs->hptxfsiz) + >> 16))); + retval += DWC_OTG_PARAM_CHECK_VALID(max_transfer_size, + "max_transfer_size", + (dwc_otg_module_params.max_transfer_size < + (1 << (core_if->hwcfg3.b.xfer_size_cntr_width + 11))), + ((1 << (core_if->hwcfg3.b.xfer_size_cntr_width + 11)) + - 1)); + retval += DWC_OTG_PARAM_CHECK_VALID(max_packet_count, + "max_packet_count", + (dwc_otg_module_params.max_packet_count < + (1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4))), + ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4)) + - 1)); + retval += DWC_OTG_PARAM_CHECK_VALID(host_channels, "host_channels", + (dwc_otg_module_params.host_channels <= + (core_if->hwcfg2.b.num_host_chan + 1)), + (core_if->hwcfg2.b.num_host_chan + 1)); + retval += DWC_OTG_PARAM_CHECK_VALID(dev_endpoints, "dev_endpoints", + (dwc_otg_module_params.dev_endpoints <= + (core_if->hwcfg2.b.num_dev_ep)), + core_if->hwcfg2.b.num_dev_ep); + + retval += DWC_OTG_PARAM_CHECK_VALID(phy_type, "phy_type", 1, 0); + retval += DWC_OTG_PARAM_CHECK_VALID(speed, "speed", + (dwc_otg_module_params.speed == 0) && + (dwc_otg_module_params.phy_type == + DWC_PHY_TYPE_PARAM_FS) ? 0 : 1, + dwc_otg_module_params.phy_type == + DWC_PHY_TYPE_PARAM_FS ? 1 : 0); + retval += DWC_OTG_PARAM_CHECK_VALID(host_ls_low_power_phy_clk, + "host_ls_low_power_phy_clk", + ((dwc_otg_module_params.host_ls_low_power_phy_clk == + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ) && + (dwc_otg_module_params.phy_type == + DWC_PHY_TYPE_PARAM_FS) ? 0 : 1), + ((dwc_otg_module_params.phy_type == + DWC_PHY_TYPE_PARAM_FS) ? + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ : + DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ)); + + DWC_OTG_PARAM_SET_DEFAULT(phy_ulpi_ddr); + DWC_OTG_PARAM_SET_DEFAULT(phy_ulpi_ext_vbus); + DWC_OTG_PARAM_SET_DEFAULT(phy_utmi_width); + DWC_OTG_PARAM_SET_DEFAULT(ulpi_fs_ls); + DWC_OTG_PARAM_SET_DEFAULT(ts_dline); + + retval += DWC_OTG_PARAM_CHECK_VALID(i2c_enable, "i2c_enable", 1, 0); + + retval += DWC_OTG_PARAM_CHECK_VALID(en_multiple_tx_fifo, + "en_multiple_tx_fifo", + ((dwc_otg_module_params.en_multiple_tx_fifo == 1) && + (core_if->hwcfg4.b.ded_fifo_en == 0)) ? 0 : 1, 0); + + retval += chk_valid_perio_tx_fifo_sizes(core_if, retval); + retval += chk_valid_tx_fifo_sizes(core_if, retval); + + DWC_OTG_PARAM_SET_DEFAULT(thr_ctl); + DWC_OTG_PARAM_SET_DEFAULT(tx_thr_length); + DWC_OTG_PARAM_SET_DEFAULT(rx_thr_length); + + return retval; +} + +module_param_named(otg_cap, dwc_otg_module_params.otg_cap, int, 0444); +MODULE_PARM_DESC(otg_cap, "OTG Capabilities 0=HNP&SRP 1=SRP Only 2=None"); +module_param_named(opt, dwc_otg_module_params.opt, int, 0444); +MODULE_PARM_DESC(opt, "OPT Mode"); +module_param_named(dma_enable, dwc_otg_module_params.dma_enable, int, 0444); +MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled"); +module_param_named(dma_burst_size, dwc_otg_module_params.dma_burst_size, + int, 0444); +MODULE_PARM_DESC(dma_burst_size, "DMA Burst Size 1, 4, 8, 16, 32, 64, " + "128, 256"); +module_param_named(speed, dwc_otg_module_params.speed, int, 0444); +MODULE_PARM_DESC(speed, "Speed 0=High Speed 1=Full Speed"); +module_param_named(host_support_fs_ls_low_power, + dwc_otg_module_params.host_support_fs_ls_low_power, + int, 0444); +MODULE_PARM_DESC(host_support_fs_ls_low_power, "Support Low Power w/FS or LS " + "0=Support 1=Don't Support"); +module_param_named(host_ls_low_power_phy_clk, + dwc_otg_module_params.host_ls_low_power_phy_clk, + int, 0444); +MODULE_PARM_DESC(host_ls_low_power_phy_clk, "Low Speed Low Power Clock " + "0=48Mhz 1=6Mhz"); +module_param_named(enable_dynamic_fifo, + dwc_otg_module_params.enable_dynamic_fifo, int, 0444); +MODULE_PARM_DESC(enable_dynamic_fifo, "0=cC Setting 1=Allow Dynamic Sizing"); +module_param_named(data_fifo_size, + dwc_otg_module_params.data_fifo_size, int, 0444); +MODULE_PARM_DESC(data_fifo_size, "Total number of words in the data FIFO " + "memory 32-32768"); +module_param_named(dev_rx_fifo_size, dwc_otg_module_params.dev_rx_fifo_size, + int, 0444); +MODULE_PARM_DESC(dev_rx_fifo_size, "Number of words in the Rx FIFO 16-32768"); +module_param_named(dev_nperio_tx_fifo_size, + dwc_otg_module_params.dev_nperio_tx_fifo_size, + int, 0444); +MODULE_PARM_DESC(dev_nperio_tx_fifo_size, "Number of words in the non-periodic " + "Tx FIFO 16-32768"); +module_param_named(dev_perio_tx_fifo_size_1, + dwc_otg_module_params.dev_perio_tx_fifo_size[0], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_1, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_2, + dwc_otg_module_params.dev_perio_tx_fifo_size[1], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_2, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_3, + dwc_otg_module_params.dev_perio_tx_fifo_size[2], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_3, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_4, + dwc_otg_module_params.dev_perio_tx_fifo_size[3], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_4, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_5, + dwc_otg_module_params.dev_perio_tx_fifo_size[4], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_5, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_6, + dwc_otg_module_params.dev_perio_tx_fifo_size[5], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_6, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_7, + dwc_otg_module_params.dev_perio_tx_fifo_size[6], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_7, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_8, + dwc_otg_module_params.dev_perio_tx_fifo_size[7], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_8, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_9, + dwc_otg_module_params.dev_perio_tx_fifo_size[8], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_9, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_10, + dwc_otg_module_params.dev_perio_tx_fifo_size[9], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_10, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_11, + dwc_otg_module_params.dev_perio_tx_fifo_size[10], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_11, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_12, + dwc_otg_module_params.dev_perio_tx_fifo_size[11], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_12, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_13, + dwc_otg_module_params.dev_perio_tx_fifo_size[12], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_13, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_14, + dwc_otg_module_params.dev_perio_tx_fifo_size[13], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_14, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(dev_perio_tx_fifo_size_15, + dwc_otg_module_params.dev_perio_tx_fifo_size[14], + int, 0444); +MODULE_PARM_DESC(dev_perio_tx_fifo_size_15, "Number of words in the periodic " + "Tx FIFO 4-768"); +module_param_named(host_rx_fifo_size, dwc_otg_module_params.host_rx_fifo_size, + int, 0444); +MODULE_PARM_DESC(host_rx_fifo_size, "Number of words in the Rx FIFO 16-32768"); +module_param_named(host_nperio_tx_fifo_size, + dwc_otg_module_params.host_nperio_tx_fifo_size, + int, 0444); +MODULE_PARM_DESC(host_nperio_tx_fifo_size, "Number of words in the " + "non-periodic Tx FIFO 16-32768"); +module_param_named(host_perio_tx_fifo_size, + dwc_otg_module_params.host_perio_tx_fifo_size, + int, 0444); +MODULE_PARM_DESC(host_perio_tx_fifo_size, "Number of words in the host " + "periodic Tx FIFO 16-32768"); +module_param_named(max_transfer_size, dwc_otg_module_params.max_transfer_size, + int, 0444); + +MODULE_PARM_DESC(max_transfer_size, "The maximum transfer size supported in " + "bytes 2047-65535"); +module_param_named(max_packet_count, dwc_otg_module_params.max_packet_count, + int, 0444); +MODULE_PARM_DESC(max_packet_count, "The maximum number of packets in a " + "transfer 15-511"); +module_param_named(host_channels, dwc_otg_module_params.host_channels, + int, 0444); +MODULE_PARM_DESC(host_channels, "The number of host channel registers to " + "use 1-16"); +module_param_named(dev_endpoints, dwc_otg_module_params.dev_endpoints, + int, 0444); +MODULE_PARM_DESC(dev_endpoints, "The number of endpoints in addition to EP0 " + "available for device mode 1-15"); +module_param_named(phy_type, dwc_otg_module_params.phy_type, int, 0444); +MODULE_PARM_DESC(phy_type, "0=Reserved 1=UTMI+ 2=ULPI"); +module_param_named(phy_utmi_width, dwc_otg_module_params.phy_utmi_width, + int, 0444); +MODULE_PARM_DESC(phy_utmi_width, "Specifies the UTMI+ Data Width 8 or 16 bits"); +module_param_named(phy_ulpi_ddr, dwc_otg_module_params.phy_ulpi_ddr, + int, 0444); +MODULE_PARM_DESC(phy_ulpi_ddr, "0"); +module_param_named(phy_ulpi_ext_vbus, dwc_otg_module_params.phy_ulpi_ext_vbus, + int, 0444); +MODULE_PARM_DESC(phy_ulpi_ext_vbus, + "ULPI PHY using internal or external vbus 0=Internal"); +module_param_named(i2c_enable, dwc_otg_module_params.i2c_enable, int, 0444); +MODULE_PARM_DESC(i2c_enable, "FS PHY Interface"); +module_param_named(ulpi_fs_ls, dwc_otg_module_params.ulpi_fs_ls, int, 0444); +MODULE_PARM_DESC(ulpi_fs_ls, "ULPI PHY FS/LS mode only"); +module_param_named(ts_dline, dwc_otg_module_params.ts_dline, int, 0444); +MODULE_PARM_DESC(ts_dline, "Term select Dline pulsing for all PHYs"); +module_param_named(debug, g_dbg_lvl, int, 0444); +MODULE_PARM_DESC(debug, "0"); +module_param_named(en_multiple_tx_fifo, + dwc_otg_module_params.en_multiple_tx_fifo, int, 0444); +MODULE_PARM_DESC(en_multiple_tx_fifo, "Dedicated Non Periodic Tx FIFOs " + "0=disabled 1=enabled"); +module_param_named(dev_tx_fifo_size_1, + dwc_otg_module_params.dev_tx_fifo_size[0], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_1, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_2, + dwc_otg_module_params.dev_tx_fifo_size[1], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_2, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_3, + dwc_otg_module_params.dev_tx_fifo_size[2], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_3, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_4, + dwc_otg_module_params.dev_tx_fifo_size[3], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_4, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_5, + dwc_otg_module_params.dev_tx_fifo_size[4], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_5, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_6, + dwc_otg_module_params.dev_tx_fifo_size[5], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_6, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_7, + dwc_otg_module_params.dev_tx_fifo_size[6], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_7, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_8, + dwc_otg_module_params.dev_tx_fifo_size[7], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_8, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_9, + dwc_otg_module_params.dev_tx_fifo_size[8], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_9, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_10, + dwc_otg_module_params.dev_tx_fifo_size[9], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_10, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_11, + dwc_otg_module_params.dev_tx_fifo_size[10], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_11, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_12, + dwc_otg_module_params.dev_tx_fifo_size[11], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_12, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_13, + dwc_otg_module_params.dev_tx_fifo_size[12], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_13, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_14, + dwc_otg_module_params.dev_tx_fifo_size[13], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_14, "Number of words in the Tx FIFO 4-768"); +module_param_named(dev_tx_fifo_size_15, + dwc_otg_module_params.dev_tx_fifo_size[14], int, 0444); +MODULE_PARM_DESC(dev_tx_fifo_size_15, "Number of words in the Tx FIFO 4-768"); +module_param_named(thr_ctl, dwc_otg_module_params.thr_ctl, int, 0444); +MODULE_PARM_DESC(thr_ctl, "Thresholding enable flag bit 0 - non ISO Tx thr., " + "1 - ISO Tx thr., 2 - Rx thr.- bit " + "0=disabled 1=enabled"); +module_param_named(tx_thr_length, dwc_otg_module_params.tx_thr_length, + int, 0444); +MODULE_PARM_DESC(tx_thr_length, "Tx Threshold length in 32 bit DWORDs"); +module_param_named(rx_thr_length, dwc_otg_module_params.rx_thr_length, + int, 0444); +MODULE_PARM_DESC(rx_thr_length, "Rx Threshold length in 32 bit DWORDs"); diff --git a/drivers/usb/dwc_otg/dwc_otg_regs.h b/drivers/usb/dwc_otg/dwc_otg_regs.h new file mode 100644 index 0000000..ba2b1d8 --- /dev/null +++ b/drivers/usb/dwc_otg/dwc_otg_regs.h @@ -0,0 +1,3282 @@ +/* + * DesignWare HS OTG controller driver + * + * Author: Mark Miesfeld + * + * Based on versions provided by APM and Synopsis which are: + * Copyright (C) 2009-2010 AppliedMicro(www.apm.com) + * + * 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. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __DWC_OTG_REGS_H__ +#define __DWC_OTG_REGS_H__ + +#include + +/* + * This file contains the data structures 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. + */ +struct core_global_regs { + /* OTG Control and Status Register. Offset: 000h */ + u32 gotgctl; + /* OTG Interrupt Register. Offset: 004h */ + u32 gotgint; + /* Core AHB Configuration Register. Offset: 008h */ + u32 gahbcfg; + +#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 + + /* Core USB Configuration Register. Offset: 00Ch */ + u32 gusbcfg; + /* Core Reset Register. Offset: 010h */ + u32 grstctl; + /* Core Interrupt Register. Offset: 014h */ + u32 gintsts; + /* Core Interrupt Mask Register. Offset: 018h */ + u32 gintmsk; + /* + * Receive Status Queue Read Register + * (Read Only) Offset: 01Ch + */ + u32 grxstsr; + /* + * Receive Status Queue Read & POP Register + * (Read Only) Offset: 020h + */ + u32 grxstsp; + /* Receive FIFO Size Register. Offset: 024h */ + u32 grxfsiz; + /* Non Periodic Transmit FIFO Size Register. Offset: 028h */ + u32 gnptxfsiz; + /* + * Non Periodic Transmit FIFO/Queue Status Register + * (Read Only). Offset: 02Ch + */ + u32 gnptxsts; + /* I2C Access Register. Offset: 030h */ + u32 gi2cctl; + /* PHY Vendor Control Register. Offset: 034h */ + u32 gpvndctl; + /* General Purpose Input/Output Register. Offset: 038h */ + u32 ggpio; + /* User ID Register. Offset: 03Ch */ + u32 guid; + /* Synopsys ID Register (Read Only). Offset: 040h */ + u32 gsnpsid; + /* User HW Config1 Register (Read Only). Offset: 044h */ + u32 ghwcfg1; + /* User HW Config2 Register (Read Only). Offset: 048h */ + u32 ghwcfg2; +#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 + + /* User HW Config3 Register (Read Only). Offset: 04Ch */ + u32 ghwcfg3; + /* User HW Config4 Register (Read Only). Offset: 050h */ + u32 ghwcfg4; + /* Reserved Offset: 054h-0FFh */ + u32 reserved[43]; + /* Host Periodic Transmit FIFO Size Register. Offset: 100h */ + u32 hptxfsiz; + + /* + * Device Periodic Transmit FIFO#n Register, if dedicated fifos are + * disabled. Otherwise Device Transmit FIFO#n Register. + * + * Offset: 104h + (FIFO_Number-1)*04h, 1 <= FIFO Number <= 15 (1<=n<=15) + */ + u32 dptxfsiz_dieptxf[15]; +}; + + +#if defined(CONFIG_DWC_OTG_REG_LE) +/* + * This union represents the bit fields of the Core OTG Controland Status + * Register (GOTGCTL). Set the bits using the bit fields then write the d32 + * value to the register. + */ +union gotgctl_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_21:11; + unsigned currmod:1; + unsigned bsesvld:1; + unsigned asesvld:1; + unsigned reserved17:1; + unsigned conidsts:1; + unsigned reserved1_12:4; + unsigned devhnpen:1; + unsigned hstsethnpen:1; + unsigned hnpreq:1; + unsigned hstnegscs:1; + unsigned reserved07_02:6; + unsigned sesreq:1; + unsigned sesreqscs:1; + } b; +}; + +/* + * This union represents the bit fields of the Core OTG Interrupt Register + * (GOTGINT). Set/clear the bits using the bit fields then write the d32 + * value to the register. + */ +union gotgint_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* Current Mode */ + unsigned reserved31_20:12; + /* Debounce Done */ + unsigned debdone:1; + /* A-Device Timeout Change */ + unsigned adevtoutchng:1; + /* Host Negotiation Detected */ + unsigned hstnegdet:1; + unsigned reserver16_10:7; + /* Host Negotiation Success Status Change */ + unsigned hstnegsucstschng:1; + /* Session Request Success Status Change */ + unsigned sesreqsucstschng:1; + unsigned reserved3_7:5; + /* Session End Detected */ + unsigned sesenddet:1; + unsigned reserved01_00:2; + } b; +}; + +/* + * This union represents the bit fields of the Core AHB Configuration Register + * (GAHBCFG). Set/clear the bits using the bit fields then write the d32 value + * to the register. + */ +union gahbcfg_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved9_31:23; + unsigned ptxfemplvl:1; +#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + + unsigned nptxfemplvl_txfemplvl:1; + unsigned reserved:1; + unsigned dmaenable:1; +#define DWC_GAHBCFG_DMAENABLE 1 + + unsigned hburstlen:4; +#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 + + unsigned glblintrmsk:1; +#define DWC_GAHBCFG_GLBINT_ENABLE 1 + } b; +}; + +/* + * This union represents the bit fields of the Core USB Configuration Register + * (GUSBCFG). Set the bits using the bit fields then write the d32 value to the + * register. + */ +union gusbcfg_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned corrupt_tx_packet:1; + unsigned force_device_mode:1; + unsigned force_host_mode:1; + unsigned reserved23_28:6; + unsigned term_sel_dl_pulse:1; + unsigned ulpi_int_vbus_indicator:1; + unsigned ulpi_ext_vbus_drv:1; + unsigned ulpi_clk_sus_m:1; + unsigned ulpi_auto_res:1; + unsigned ulpi_fsls:1; + + unsigned otgutmifssel:1; + unsigned phylpwrclksel:1; + unsigned nptxfrwnden:1; + unsigned usbtrdtim:4; + unsigned hnpcap:1; + unsigned srpcap:1; + unsigned ddrsel:1; + unsigned physel:1; + unsigned fsintf:1; + unsigned ulpi_utmi_sel:1; + unsigned phyif:1; + unsigned toutcal:3; + } b; +}; + +/* + * This union represents the bit fields of the Core Reset Register (GRSTCTL). + * Set/clear the bits using the bit fields then write the d32 value to the + * register. + */ +union grstctl_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* + * AHB Master Idle. Indicates the AHB Master State Machine is + * in IDLE condition. + */ + unsigned ahbidle:1; + + /* + * DMA Request Signal. Indicated DMA request is in probress. + Used for debug purpose. + */ + unsigned dmareq:1; + + /* Reserved */ + unsigned reserved29_11:19; + + /* + * TxFIFO Number (TxFNum) (Device and Host). + * + * This is the FIFO number which needs to be flushed, + * using the TxFIFO Flush bit. This field should not + * be changed until the TxFIFO Flush bit is cleared by + * the core. + * - 0x0:Non Periodic TxFIFO Flush + * - 0x1 : Periodic TxFIFO #1 Flush in device mode + * or Periodic TxFIFO in host mode + * - 0x2 : Periodic TxFIFO #2 Flush in device mode. + * - ... + * - 0xF : Periodic TxFIFO #15 Flush in device mode + * - 0x10: Flush all the Transmit NonPeriodic and + * Transmit Periodic FIFOs in the core + */ + unsigned txfnum:5; +#define DWC_GRSTCTL_TXFNUM_ALL 0x10 + + /* + * TxFIFO Flush (TxFFlsh) (Device and Host). + * + * This bit is used to selectively flush a single or all + * transmit FIFOs. The application must first ensure that the + * core is not in the middle of a transaction. + * + * The application should write into this bit, only after + * making sure that neither the DMA engine is writing into the + * TxFIFO nor the MAC is reading the data out of the FIFO. + * + * The application should wait until the core clears this bit, + * before performing any operations. This bit will takes 8 + * clocks (slowest of PHY or AHB clock) to clear. + */ + unsigned txfflsh:1; + + /* + * RxFIFO Flush (RxFFlsh) (Device and Host) + * + * The application can flush the entire Receive FIFO using this + * bit. + * + * The application must first ensure that the core is not in the + * middle of a transaction. + * + * The application should write into this bit, only after making + * sure that neither the DMA engine is reading from the RxFIFO + * nor the MAC is writing the data in to the FIFO. + * + * The application should wait until the bit is cleared before + * performing any other operations. This bit will takes 8 clocks + * (slowest of PHY or AHB clock) to clear. + */ + unsigned rxfflsh:1; + + /* + * In Token Sequence Learning Queue Flush + * (INTknQFlsh) (Device Only) + */ + unsigned intknqflsh:1; + + /* + * Host Frame Counter Reset (Host Only)
+ * + * The application can reset the (micro)frame number + * counter inside the core, using this bit. When the + * (micro)frame counter is reset, the subsequent SOF + * sent out by the core, will have a (micro)frame + * number of 0. + */ + unsigned hstfrm:1; + + /* + * Hclk Soft Reset + * + * The application uses this bit to reset the control logic in + * the AHB clock domain. Only AHB clock domain pipelines are + * reset. + */ + unsigned hsftrst:1; + + /* + * Core Soft Reset (CSftRst) (Device and Host) + * + * The application can flush the control logic in the + * entire core using this bit. This bit resets the + * pipelines in the AHB Clock domain as well as the + * PHY Clock domain. + * + * The state machines are reset to an IDLE state, the + * control bits in the CSRs are cleared, all the + * transmit FIFOs and the receive FIFO are flushed. + * + * The status mask bits that control the generation of + * the interrupt, are cleared, to clear the + * interrupt. The interrupt status bits are not + * cleared, so the application can get the status of + * any events that occurred in the core after it has + * set this bit. + * + * Any transactions on the AHB are terminated as soon + * as possible following the protocol. Any + * transactions on the USB are terminated immediately. + * + * The configuration settings in the CSRs are + * unchanged, so the software doesn't have to + * reprogram these registers (Device + * Configuration/Host Configuration/Core System + * Configuration/Core PHY Configuration). + * + * The application can write to this bit, any time it + * wants to reset the core. This is a self clearing + * bit and the core clears this bit after all the + * necessary logic is reset in the core, which may + * take several clocks, depending on the current state + * of the core. + */ + unsigned csftrst:1; + } b; +}; + +/* + * This union represents the bit fields of the Core Interrupt Mask Register + * (GINTMSK). Set/clear the bits using the bit fields then write the d32 value + * to the register. + */ +union gintmsk_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned wkupintr:1; + unsigned sessreqintr:1; + unsigned disconnect:1; + unsigned conidstschng:1; + unsigned reserved27:1; + unsigned ptxfempty:1; + unsigned hcintr:1; + unsigned portintr:1; + unsigned reserved23_22:2; + unsigned incomplisoout:1; + unsigned incomplisoin:1; + unsigned outepintr:1; + unsigned inepintr:1; + unsigned epmismatch:1; + unsigned reserved16:1; + unsigned eopframe:1; + unsigned isooutdrop:1; + unsigned enumdone:1; + unsigned usbreset:1; + unsigned usbsuspend:1; + unsigned erlysuspend:1; + unsigned i2cintr:1; + unsigned reserved08:1; + unsigned goutnakeff:1; + unsigned ginnakeff:1; + unsigned nptxfempty:1; + unsigned rxstsqlvl:1; + unsigned sofintr:1; + unsigned otgintr:1; + unsigned modemismatch:1; + unsigned reserved00:1; + } b; +}; + +/* + * This union represents the bit fields of the Core Interrupt Register + * (GINTSTS). Set/clear the bits using the bit fields then write the d32 value + * to the register. + */ +union gintsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; +#define DWC_SOF_INTR_MASK 0x0008 + struct { +#define DWC_HOST_MODE 1 + unsigned wkupintr:1; + unsigned sessreqintr:1; + unsigned disconnect:1; + unsigned conidstschng:1; + unsigned reserved27:1; + unsigned ptxfempty:1; + unsigned hcintr:1; + unsigned portintr:1; + unsigned reserved22_23:2; + unsigned incomplisoout:1; + unsigned incomplisoin:1; + unsigned outepintr:1; + unsigned inepint:1; + unsigned epmismatch:1; + unsigned intokenrx:1; + unsigned eopframe:1; + unsigned isooutdrop:1; + unsigned enumdone:1; + unsigned usbreset:1; + unsigned usbsuspend:1; + unsigned erlysuspend:1; + unsigned i2cintr:1; + unsigned reserved8:1; + unsigned goutnakeff:1; + unsigned ginnakeff:1; + unsigned nptxfempty:1; + unsigned rxstsqlvl:1; + unsigned sofintr:1; + unsigned otgintr:1; + unsigned modemismatch:1; + unsigned curmode:1; + } b; +}; + +/* + * This union represents the bit fields in the Device Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the d32 + * element then read out the bits using the bit elements. + */ +union device_grxsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:7; + unsigned fn:4; + unsigned pktsts:4; +#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 */ + + unsigned dpid:2; + unsigned bcnt:11; + unsigned epnum:4; + } b; +}; + +/* + * This union represents the bit fields in the Host Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the d32 + * element then read out the bits using the bit elements. + */ +union host_grxsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_21:11; + unsigned pktsts:4; +#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 + + unsigned dpid:2; + unsigned bcnt:11; + unsigned chnum:4; + } b; +}; + +/* + * This union represents the bit fields in the FIFO Size Registers (HPTXFSIZ, + * GNPTXFSIZ, DPTXFSIZn). Read the register into the d32 element then + * read out the bits using the bit elements. + */ +union fifosize_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned depth:16; + unsigned startaddr:16; + } b; +}; + +/* + * This union represents the bit fields in the Non-Periodic Transmit FIFO/Queue + * Status Register (GNPTXSTS). Read the register into the d32 element then read + * out the bits using the bit elements. + */ +union gnptxsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:1; + /* Top of the Non-Periodic Transmit Request Queue + * - bits 30:27 - Channel/EP Number + * - bits 26:25 - Token Type + * - 2'b00 - IN/OUT + * - 2'b01 - Zero Length OUT + * - 2'b10 - PING/Complete Split + * - 2'b11 - Channel Halt + * - bit 24 - Terminate (Last entry for the selected + * channel/EP) + */ + unsigned nptxqtop_chnep:4; + unsigned nptxqtop_token:2; + unsigned nptxqtop_terminate:1; + unsigned nptxqspcavail:8; + unsigned nptxfspcavail:16; + } b; +}; + +/* + * This union represents the bit fields in the Transmit FIFO Status Register + * (DTXFSTS). Read the register into the d32 element then read out the bits + * using the bit elements. + */ +union dtxfsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:16; + unsigned txfspcavail:16; + } b; +}; + +/* + * This union represents the bit fields in the I2C Control Register (I2CCTL). + * Read the register into the d32 element then read out the bits using the bit + * elements. + */ +union gi2cctl_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned bsydne:1; + unsigned rw:1; + unsigned reserved:2; + unsigned i2cdevaddr:2; + unsigned i2csuspctl:1; + unsigned ack:1; + unsigned i2cen:1; + unsigned addr:7; + unsigned regaddr:8; + unsigned rwdata:8; + } b; +}; + +/* + * This union represents the bit fields in the User HW Config1 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg1_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned ep_dir15:2; + unsigned ep_dir14:2; + unsigned ep_dir13:2; + unsigned ep_dir12:2; + unsigned ep_dir11:2; + unsigned ep_dir10:2; + unsigned ep_dir9:2; + unsigned ep_dir8:2; + unsigned ep_dir7:2; + unsigned ep_dir6:2; + unsigned ep_dir5:2; + unsigned ep_dir4:2; + unsigned ep_dir3:2; + unsigned ep_dir2:2; + unsigned ep_dir1:2; + unsigned ep_dir0:2; + } b; +}; + +/* + * This union represents the bit fields in the User HW Config2 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg2_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* GHWCFG2 */ + unsigned reserved31:1; + unsigned dev_token_q_depth:5; + unsigned host_perio_tx_q_depth:2; + unsigned nonperio_tx_q_depth:2; + unsigned rx_status_q_depth:2; + unsigned dynamic_fifo:1; + unsigned perio_ep_supported:1; + unsigned num_host_chan:4; + unsigned num_dev_ep:4; + unsigned fs_phy_type:2; + unsigned hs_phy_type:2; +#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 + + unsigned point2point:1; + unsigned architecture:2; + unsigned op_mode: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 + } b; +}; + +/* + * This union represents the bit fields in the User HW Config3 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg3_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* GHWCFG3 */ + unsigned dfifo_depth:16; + unsigned reserved15_13:3; + unsigned ahb_phy_clock_synch:1; + unsigned synch_reset_type:1; + unsigned optional_features:1; + unsigned vendor_ctrl_if:1; + unsigned i2c:1; + unsigned otg_func:1; + unsigned packet_size_cntr_width:3; + unsigned xfer_size_cntr_width:4; + } b; +}; + +/* + * This union represents the bit fields in the User HW Config4 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg4_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_30:2; + unsigned num_in_eps:4; + unsigned ded_fifo_en:1; + + unsigned session_end_filt_en:1; + unsigned b_valid_filt_en:1; + unsigned a_valid_filt_en:1; + unsigned vbus_valid_filt_en:1; + unsigned iddig_filt_en:1; + unsigned num_dev_mode_ctrl_ep:4; + unsigned utmi_phy_data_width:2; + unsigned min_ahb_freq:9; + unsigned power_optimiz:1; + unsigned num_dev_perio_in_ep:4; + } b; +}; + +/* + * 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. + */ +struct device_global_regs { /* CONFIG_DWC_OTG_REG_LE */ + /* Device Configuration Register. Offset: 800h */ + u32 dcfg; + /* Device Control Register. Offset: 804h */ + u32 dctl; + /* Device Status Register (Read Only). Offset: 808h */ + u32 dsts; + /* Reserved. Offset: 80Ch */ + u32 unused; + /* Device IN Endpoint Common Interrupt Mask Register. Offset: 810h */ + u32 diepmsk; + /* Device OUT Endpoint Common Interrupt Mask Register. Offset: 814h */ + u32 doepmsk; + /* Device All Endpoints Interrupt Register. Offset: 818h */ + u32 daint; + /* Device All Endpoints Interrupt Mask Register. Offset: 81Ch */ + u32 daintmsk; + /* Device IN Token Queue Read Register-1 (Read Only). Offset: 820h */ + u32 dtknqr1; + /* Device IN Token Queue Read Register-2 (Read Only). Offset: 824h */ + u32 dtknqr2; + /* Device VBUS discharge Register. Offset: 828h */ + u32 dvbusdis; + /* Device VBUS Pulse Register. Offset: 82Ch */ + u32 dvbuspulse; + /* Device IN Token Queue Read Register-3 (Read Only). Offset: 830h */ + u32 dtknqr3_dthrctl; + /* Device IN Token Queue Read Register-4 (Read Only). Offset: 834h */ + u32 dtknqr4_fifoemptymsk; +}; + +/* + * This union represents the bit fields in the Device Configuration + * Register. Read the register into the d32 member then + * set/clear the bits using the bit elements. Write the + * d32 member to the dcfg register. + */ +union dcfg_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved0_8:9; + unsigned epmscnt:5; + /* In Endpoint Mis-match count */ + unsigned reserved17_13:5; + /* Periodic Frame Interval */ + unsigned perfrint:2; +#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 + + /* Device Addresses */ + unsigned devaddr:7; + unsigned reserved3:1; + /* Non Zero Length Status OUT Handshake */ + unsigned nzstsouthshk:1; +#define DWC_DCFG_SEND_STALL 1 + + /* Device Speed */ + unsigned devspd:2; + } b; +}; + +/* + * This union represents the bit fields in the Device Control Register. Read + * the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union dctl_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_12:21; + /* Clear Global OUT NAK */ + unsigned cgoutnak:1; + /* Set Global OUT NAK */ + unsigned sgoutnak:1; + /* Clear Global Non-Periodic IN NAK */ + unsigned cgnpinnak:1; + /* Set Global Non-Periodic IN NAK */ + unsigned sgnpinnak:1; + /* Test Control */ + unsigned tstctl:3; + /* Global OUT NAK Status */ + unsigned goutnaksts:1; + /* Global Non-Periodic IN NAK Status */ + unsigned gnpinnaksts:1; + /* Soft Disconnect */ + unsigned sftdiscon:1; + /* Remote Wakeup */ + unsigned rmtwkupsig:1; + } b; +}; + +/* + * This union represents the bit fields in the Device Status Register. Read the + * register into the d32 member then set/clear the bits using the bit elements. + */ +union dsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_22:10; + /* Frame or Microframe Number of the received SOF */ + unsigned soffn:14; + unsigned reserved07_04:4; + /* Erratic Error */ + unsigned errticerr:1; + /* Enumerated Speed */ + unsigned enumspd:2; +#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 + /* Suspend Status */ + unsigned suspsts:1; + } b; +}; + +/* + * This union represents the bit fields in the Device IN EP Interrupt Register + * and the Device IN EP Common Mask Register. + * + * Read the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union diepint_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_08:23; + unsigned txfifoundrn:1; + /* IN Endpoint HAK Effective mask */ + unsigned emptyintr:1; + /* IN Endpoint NAK Effective mask */ + unsigned inepnakeff:1; + /* IN Token Received with EP mismatch mask */ + unsigned intknepmis:1; + /* IN Token received with TxF Empty mask */ + unsigned intktxfemp:1; + /* TimeOUT Handshake mask (non-ISOC EPs) */ + unsigned timeout:1; + /* AHB Error mask */ + unsigned ahberr:1; + /* Endpoint disable mask */ + unsigned epdisabled:1; + /* Transfer complete mask */ + unsigned xfercompl:1; + } b; +}; + +/* + * This union represents the bit fields in the Device OUT EP Interrupt Register + * and Device OUT EP Common Interrupt Mask Register. + * + * Read the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union doepint_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31_04:28; /* Docs say reserved is 27 bits */ + + /* There is 1 bit missing here, not used? */ + + /* Setup Phase Done (control EPs) */ + unsigned setup:1; + /* AHB Error */ + unsigned ahberr:1; + /* Endpoint disable */ + unsigned epdisabled:1; + /* Transfer complete */ + unsigned xfercompl:1; + } b; +}; + +/* + * This union represents the bit fields in the Device All EP Interrupt and Mask + * Registers. Read the register into the d32 member then set/clear the bits + * using the bit elements. + */ +union daint_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* OUT Endpoint bits */ + unsigned out:16; + /* IN Endpoint bits */ + unsigned in:16; + } ep; + struct { + /* OUT Endpoint bits */ + unsigned outep15:1; + unsigned outep14:1; + unsigned outep13:1; + unsigned outep12:1; + unsigned outep11:1; + unsigned outep10:1; + unsigned outep9:1; + unsigned outep8:1; + unsigned outep7:1; + unsigned outep6:1; + unsigned outep5:1; + unsigned outep4:1; + unsigned outep3:1; + unsigned outep2:1; + unsigned outep1:1; + unsigned outep0:1; + /* IN Endpoint bits */ + unsigned inep15:1; + unsigned inep14:1; + unsigned inep13:1; + unsigned inep12:1; + unsigned inep11:1; + unsigned inep10:1; + unsigned inep9:1; + unsigned inep8:1; + unsigned inep7:1; + unsigned inep6:1; + unsigned inep5:1; + unsigned inep4:1; + unsigned inep3:1; + unsigned inep2:1; + unsigned inep1:1; + unsigned inep0:1; + } b; +}; + +/* + * This union represents the bit fields in the Device IN Token Queue Read + * Registers. Read the register into the d32 member. READ-ONLY Register + */ +union dtknq1_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* EP Numbers of IN Tokens 0 ... 4 */ + unsigned epnums0_5:24; + /* write pointer has wrapped. */ + unsigned wrap_bit:1; + /* Reserved */ + unsigned reserved05_06:2; + /* In Token Queue Write Pointer */ + unsigned intknwptr:5; + } b; +}; + +/* + * This union represents Threshold control Register. Read and write the register + * into the d32 member. READ-WRITABLE Register + */ +union dthrctl_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* Reserved */ + unsigned reserved26_31:6; + /* Rx Thr. Length */ + unsigned rx_thr_len:9; + /* Rx Thr. Enable */ + unsigned rx_thr_en:1; + /* Reserved */ + unsigned reserved11_15:5; + /* Tx Thr. Length */ + unsigned tx_thr_len:9; + /* ISO Tx Thr. Enable */ + unsigned iso_thr_en:1; + /* non ISO Tx Thr. Enable */ + unsigned non_iso_thr_en:1; + } b; +}; + +/* + * 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. + */ + struct device_in_ep_regs { + /* + * Device IN Endpoint Control Register. + * Offset:900h + (ep_num * 20h) + 00h + */ + u32 diepctl; + /* Reserved. Offset:900h + (ep_num * 20h) + 04h */ + u32 reserved04; + /* + * Device IN Endpoint Interrupt Register. + * Offset:900h + (ep_num * 20h) + 08h + */ + u32 diepint; + /* Reserved. Offset:900h + (ep_num * 20h) + 0Ch */ + u32 reserved0C; + /* Device IN Endpoint Transfer Size Register. + * Offset:900h + (ep_num * 20h) + 10h + */ + u32 dieptsiz; + /* + * Device IN Endpoint DMA Address Register. + * Offset:900h + (ep_num * 20h) + 14h + */ + u32 diepdma; + /* Reserved. + * Offset:900h + (ep_num * 20h) + 18h - 900h + (ep_num * 20h) + 1Ch + */ + u32 dtxfsts; + /* + * Reserved. + * Offset:900h + (ep_num * 20h) + 1Ch - 900h + (ep_num * 20h) + 1Ch + */ + u32 reserved18; +}; + +/* + * 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. + */ +struct device_out_ep_regs { + /* + * Device OUT Endpoint Control Register. + * Offset:B00h + (ep_num * 20h) + 00h + */ + u32 doepctl; + /* + * Device OUT Endpoint Frame number Register. + * Offset: B00h + (ep_num * 20h) + 04h + */ + u32 doepfn; + /* + * Device OUT Endpoint Interrupt Register. + * Offset:B00h + (ep_num * 20h) + 08h + */ + u32 doepint; + /* Reserved. Offset:B00h + (ep_num * 20h) + 0Ch */ + u32 reserved0C; + /* + * Device OUT Endpoint Transfer Size Register. + * Offset: B00h + (ep_num * 20h) + 10h + */ + u32 doeptsiz; + /* + * Device OUT Endpoint DMA Address Register. + * Offset:B00h + (ep_num * 20h) + 14h + */ + u32 doepdma; + /* + * Reserved. + * Offset:B00h + (ep_num * 20h) + 18h - B00h + (ep_num * 20h) + 1Ch + */ + u32 unused[2]; +}; + +/* + * This union represents the bit fields in the Device EP Control Register. Read + * the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union depctl_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* Endpoint Enable */ + unsigned epena:1; + /* Endpoint Disable */ + unsigned epdis:1; + + /* + * Set DATA1 PID (INTR/Bulk IN and OUT endpoints) Writing to + * this field sets the Endpoint DPID (DPID) field in this + * register to DATA1 Set Odd (micro)frame (SetOddFr) (ISO IN and + * OUT Endpoints) Writing to this field sets the Even/Odd + * (micro)frame (EO_FrNum) field to odd (micro) frame. + */ + unsigned setd1pid:1; + /* + * Set DATA0 PID (INTR/Bulk IN and OUT endpoints) Writing to + * this field sets the Endpoint DPID (DPID) field in this + * register to DATA0. Set Even (micro)frame (SetEvenFr) (ISO IN + * and OUT Endpoints) Writing to this field sets the Even/Odd + * (micro)frame (EO_FrNum) field to even (micro) frame. + */ + unsigned setd0pid:1; + + /* Set NAK */ + unsigned snak:1; + /* Clear NAK */ + unsigned cnak:1; + + /* + * Tx Fifo Number + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved + */ + unsigned txfnum:4; + + /* Stall Handshake */ + unsigned stall:1; + + /* Snoop Mode + * OUT EPn/OUT EP0 + * IN EPn/IN EP0 - reserved + */ + unsigned snp:1; + + /* Endpoint Type + * 2'b00: Control + * 2'b01: Isochronous + * 2'b10: Bulk + * 2'b11: Interrupt + */ + unsigned eptype:2; + + /* NAK Status */ + unsigned naksts:1; + + /* + * Endpoint DPID (INTR/Bulk IN and OUT endpoints) This field + * contains the PID of the packet going to be received or + * transmitted on this endpoint. The application should program + * the PID of the first packet going to be received or + * transmitted on this endpoint, after the endpoint is + * activated. Applications use the SetD1PID and SetD0PID fields + * of this register to program either D0 or D1 PID. + * + * The encoding for this field is + * - 0: D0 + * - 1: D1 + */ + unsigned dpid:1; + + /* USB Active Endpoint */ + unsigned usbactep:1; + + /* + * Next Endpoint + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved + */ + unsigned nextep:4; + + /* + * Maximum Packet Size + * IN/OUT EPn + * IN/OUT EP0 - 2 bits + * 2'b00: 64 Bytes + * 2'b01: 32 + * 2'b10: 16 + * 2'b11: 8 + */ + unsigned mps:11; +#define DWC_DEP0CTL_MPS_64 0 +#define DWC_DEP0CTL_MPS_32 1 +#define DWC_DEP0CTL_MPS_16 2 +#define DWC_DEP0CTL_MPS_8 3 + } b; +}; + +/* + * This union represents the bit fields in the Device EP Transfer Size Register. + * Read the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union deptsiz_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + + /* + * Added-sr: 2007-07-26 + * + * Correct ther register layout for the 405EZ Ultra + * USB device implementation. + */ +#ifdef CONFIG_DWC_LIMITED_XFER_SIZE + struct { + unsigned reserved:1; + /* Multi Count - Periodic IN endpoints */ + unsigned mc:2; + unsigned reserved1:5; + /* Packet Count */ + unsigned pktcnt:5; + unsigned reserved2:8; + /* Transfer size */ + unsigned xfersize:11; + } b; +#else + struct { + unsigned reserved:1; + /* Multi Count - Periodic IN endpoints */ + unsigned mc:2; + /* Packet Count */ + unsigned pktcnt:10; + /* Transfer size */ + unsigned xfersize:19; + } b; +#endif +}; + +/* + * This union represents the bit fields in the Device EP 0 Transfer Size + * Register. Read the register into the d32 member then set/clear the bits + * using the bit elements. + */ +union deptsiz0_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved31:1; /* device*/ + /*Setup Packet Count (DOEPTSIZ0 Only) */ + unsigned supcnt:2; + /* Reserved */ + unsigned reserved28_20:9; + /* Packet Count */ + unsigned pktcnt:1; + /* Reserved */ + unsigned reserved18_7:12; + /* Transfer size */ + unsigned xfersize:7; + } b; +}; + +#define MAX_PERIO_FIFOS 15 /* Max periodic FIFOs */ +#define MAX_TX_FIFOS 15 /* Max non-periodic FIFOs */ + +/* Maximum number of Endpoints/HostChannels */ +#if defined(CONFIG_460EX) +#define MAX_EPS_CHANNELS 12 +#else +#define MAX_EPS_CHANNELS 4 +#endif + +/* + * 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 */ + struct device_global_regs *dev_global_regs; +#define DWC_DEV_GLOBAL_REG_OFFSET 0x800 + + /* Device Logical IN Endpoint-Specific Registers 900h-AFCh */ + struct device_in_ep_regs *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 */ + struct device_out_ep_regs *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; +}; + +/* + * This union represents the bit fields in the Power and Clock Gating Control + * Register. Read the register into the d32 member then set/clear the + * bits using the bit elements. + */ +union pcgcctl_data { + u32 d32; + struct { + unsigned reserved31_05:27; + /* PHY Suspended */ + unsigned physuspended:1; + /* Reset Power Down Modules */ + unsigned rstpdwnmodule:1; + /* Power Clamp */ + unsigned pwrclmp:1; + /* Gate Hclk */ + unsigned gatehclk:1; + /* Stop Pclk */ + unsigned stoppclk:1; + } b; +}; + +/* + * 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. +*/ +struct host_global_regs { + /* Host Configuration Register. Offset: 400h */ + u32 hcfg; + /* Host Frame Interval Register. Offset: 404h */ + u32 hfir; + /* Host Frame Number / Frame Remaining Register. Offset: 408h */ + u32 hfnum; + /* Reserved. Offset: 40Ch */ + u32 reserved40C; + /* Host Periodic Transmit FIFO/ Queue Status Register. Offset: 410h */ + u32 hptxsts; + /* Host All Channels Interrupt Register. Offset: 414h */ + u32 haint; + /* Host All Channels Interrupt Mask Register. Offset: 418h */ + u32 haintmsk; +}; + +/* + * This union represents the bit fields in the Host Configuration Register. Read + * the register into the d32 member then set/clear the bits using the bit + * elements. Write the d32 member to the hcfg register. + */ +union hcfg_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { +#define DWC_HCFG_30_60_MHZ 0 +#define DWC_HCFG_48_MHZ 1 +#define DWC_HCFG_6_MHZ 2 + /* FS/LS Only Support */ + unsigned fslssupp:1; + /* FS/LS Phy Clock Select */ + unsigned fslspclksel:2; + } b; +}; + +/* + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +union hfir_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:16; + unsigned frint:16; + } b; +}; + +/* + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +union hfnum_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { +#define DWC_HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem:16; + unsigned frnum:16; + } b; +}; + +union hptxsts_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned ptxqtop_odd:1; + unsigned ptxqtop_chnum:4; + unsigned ptxqtop_token:2; + unsigned ptxqtop_terminate:1; + unsigned ptxqspcavail:8; + unsigned ptxfspcavail:16; + /* + * Top of the Periodic Transmit Request Queue + * - bit 24 - Terminate (last entry for the selected channel) + * - bits 26:25 - Token Type + * - 2'b00 - Zero length + * - 2'b01 - Ping + * - 2'b10 - Disable + * - bits 30:27 - Channel Number + * - bit 31 - Odd/even microframe + */ + } b; +}; + +/* + * This union represents the bit fields in the Host Port Control and Status + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hprt0 register. + */ +union hprt0_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { +#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0 +#define DWC_HPRT0_PRTSPD_FULL_SPEED 1 +#define DWC_HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31:13; + unsigned prtspd:2; + unsigned prttstctl:4; + unsigned prtpwr:1; + unsigned prtlnsts:2; + unsigned reserved9:1; + unsigned prtrst:1; + unsigned prtsusp:1; + unsigned prtres:1; + unsigned prtovrcurrchng:1; + unsigned prtovrcurract:1; + unsigned prtenchng:1; + unsigned prtena:1; + unsigned prtconndet:1; + unsigned prtconnsts:1; + } b; +}; + +/* + * This union represents the bit fields in the Host All Interrupt Register. + */ +union haint_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:16; + unsigned ch15:1; + unsigned ch14:1; + unsigned ch13:1; + unsigned ch12:1; + unsigned ch11:1; + unsigned ch10:1; + unsigned ch9:1; + unsigned ch8:1; + unsigned ch7:1; + unsigned ch6:1; + unsigned ch5:1; + unsigned ch4:1; + unsigned ch3:1; + unsigned ch2:1; + unsigned ch1:1; + unsigned ch0:1; + } b; + struct { + unsigned reserved:16; + unsigned chint:16; + } b2; +}; + +/* + * This union represents the bit fields in the Host All Interrupt Register. + */ +union haintmsk_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:16; + unsigned ch15:1; + unsigned ch14:1; + unsigned ch13:1; + unsigned ch12:1; + unsigned ch11:1; + unsigned ch10:1; + unsigned ch9:1; + unsigned ch8:1; + unsigned ch7:1; + unsigned ch6:1; + unsigned ch5:1; + unsigned ch4:1; + unsigned ch3:1; + unsigned ch2:1; + unsigned ch1:1; + unsigned ch0:1; + } b; + struct { + unsigned reserved:16; + unsigned chint:16; + } b2; +}; + +/* + * Host Channel Specific Registers. 500h-5FCh + */ +struct dwc_hc_regs { /* CONFIG_DWC_OTG_REG_LE */ + /* + * Host Channel 0 Characteristic Register. + * Offset: 500h + (chan_num * 20h) + 00h + */ + u32 hcchar; + /* + * Host Channel 0 Split Control Register. + * Offset: 500h + (chan_num * 20h) + 04h + */ + u32 hcsplt; + /* + * Host Channel 0 Interrupt Register. + * Offset: 500h + (chan_num * 20h) + 08h + */ + u32 hcint; + /* + * Host Channel 0 Interrupt Mask Register. + * Offset: 500h + (chan_num * 20h) + 0Ch + */ + u32 hcintmsk; + /* + * Host Channel 0 Transfer Size Register. + * Offset: 500h + (chan_num * 20h) + 10h + */ + u32 hctsiz; + /* + * Host Channel 0 DMA Address Register. + * Offset: 500h + (chan_num * 20h) + 14h + */ + u32 hcdma; + /* + * Reserved. + * Offset: 500h + (chan_num * 20h) + 18h - 500h + (chan_num * 20h) + 1Ch + */ + u32 reserved[2]; +}; + +/* + * This union represents the bit fields in the Host Channel Characteristics + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcchar register. + */ +union hcchar_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* Channel enable */ + unsigned chen:1; + /* Channel disable */ + unsigned chdis:1; + /* + * Frame to transmit periodic transaction. + * 0: even, 1: odd + */ + unsigned oddfrm:1; + /* Device address */ + unsigned devaddr:7; + /* Packets per frame for periodic transfers. 0 is reserved. */ + unsigned multicnt:2; + /* 0: Control, 1: Isoc, 2: Bulk, 3: Intr */ + unsigned eptype:2; + /* 0: Full/high speed device, 1: Low speed device */ + unsigned lspddev:1; + unsigned reserved:1; + /* 0: OUT, 1: IN */ + unsigned epdir:1; + /* Endpoint number */ + unsigned epnum:4; + /* Maximum packet size in bytes */ + unsigned mps:11; + } b; +}; + +union hcsplt_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* Split Enble */ + unsigned spltena:1; + /* Reserved */ + unsigned reserved:14; + /* Do Complete Split */ + unsigned compsplt:1; + /* Transaction Position */ + unsigned xactpos:2; +#define DWC_HCSPLIT_XACTPOS_MID 0 +#define DWC_HCSPLIT_XACTPOS_END 1 +#define DWC_HCSPLIT_XACTPOS_BEGIN 2 +#define DWC_HCSPLIT_XACTPOS_ALL 3 + + /* Hub Address */ + unsigned hubaddr:7; + /* Port Address */ + unsigned prtaddr:7; + } b; +}; + +/* + * This union represents the bit fields in the Host All Interrupt + * Register. + */ +union hcint_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + /* Reserved */ + unsigned reserved:21; + /* Data Toggle Error */ + unsigned datatglerr:1; + /* Frame Overrun */ + unsigned frmovrun:1; + /* Babble Error */ + unsigned bblerr:1; + /* Transaction Err */ + unsigned xacterr:1; + /* NYET Response Received */ + unsigned nyet:1; + /* ACK Response Received */ + unsigned ack:1; + /* NAK Response Received */ + unsigned nak:1; + /* STALL Response Received */ + unsigned stall:1; + /* AHB Error */ + unsigned ahberr:1; + /* Channel Halted */ + unsigned chhltd:1; + /* Transfer Complete */ + unsigned xfercomp:1; + } b; +}; + +/* + * This union represents the bit fields in the Host Channel Transfer Size + * Register. Read the register into the d32 member then set/clear the bits + * using the bit elements. Write the d32 member to the hcchar register. + */ +union hctsiz_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { +#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 + + /* Do PING protocol when 1 */ + unsigned dopng:1; + /* + * Packet ID for next data packet + * 0: DATA0 + * 1: DATA2 + * 2: DATA1 + * 3: MDATA (non-Control), SETUP (Control) + */ + unsigned pid:2; + /* Data packets to transfer */ + unsigned pktcnt:10; + /* Total transfer size in bytes */ + unsigned xfersize:19; + } b; +}; + +/* + * This union represents the bit fields in the Host Channel Interrupt Mask + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcintmsk register. + */ +union hcintmsk_data { /* CONFIG_DWC_OTG_REG_LE */ + u32 d32; + struct { + unsigned reserved:21; + unsigned datatglerr:1; + unsigned frmovrun:1; + unsigned bblerr:1; + unsigned xacterr:1; + unsigned nyet:1; + unsigned ack:1; + unsigned nak:1; + unsigned stall:1; + unsigned ahberr:1; + unsigned chhltd:1; + unsigned xfercompl:1; + } b; +}; + +/* + * 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.*/ + struct host_global_regs *host_global_regs; +#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400 + + /* Host Port 0 Control and Status Register */ + u32 *hprt0; +#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440 + + /* Host Channel Specific Registers at offsets 500h-5FCh. */ + struct dwc_hc_regs *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; +}; + +#else /* CONFIG_DWC_OTG_REG_LE not defined */ + +/* + * This union represents the bit fields of the Core OTG Control + * and Status Register (GOTGCTL). Set the bits using the bit + * fields then write the d32 value to the register. + */ +union gotgctl_data { + u32 d32; + struct { + unsigned sesreqscs:1; + unsigned sesreq:1; + unsigned reserved2_7:6; + unsigned hstnegscs:1; + unsigned hnpreq:1; + unsigned hstsethnpen:1; + unsigned devhnpen:1; + unsigned reserved12_15:4; + unsigned conidsts:1; + unsigned reserved17:1; + unsigned asesvld:1; + unsigned bsesvld:1; + unsigned currmod:1; + unsigned reserved21_31:11; + } b; +}; + +/* + * This union represents the bit fields of the Core OTG Interrupt Register + * (GOTGINT). Set/clear the bits using the bit fields then write the d32 + * value to the register. + */ +union gotgint_data { + u32 d32; + struct { + /* Current Mode */ + unsigned reserved0_1:2; + + /* Session End Detected */ + unsigned sesenddet:1; + + unsigned reserved3_7:5; + + /* Session Request Success Status Change */ + unsigned sesreqsucstschng:1; + /* Host Negotiation Success Status Change */ + unsigned hstnegsucstschng:1; + + unsigned reserver10_16:7; + + /* Host Negotiation Detected */ + unsigned hstnegdet:1; + /* A-Device Timeout Change */ + unsigned adevtoutchng:1; + /* Debounce Done */ + unsigned debdone:1; + + unsigned reserved31_20:12; + + } b; +}; + +/* + * This union represents the bit fields of the Core AHB Configuration Register + * (GAHBCFG). Set/clear the bits using the bit fields then write the d32 value + * to the register. + */ +union gahbcfg_data { + u32 d32; + struct { + unsigned glblintrmsk:1; +#define DWC_GAHBCFG_GLBINT_ENABLE 1 + + unsigned hburstlen:4; +#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 + + unsigned dmaenable:1; +#define DWC_GAHBCFG_DMAENABLE 1 + unsigned reserved:1; + unsigned nptxfemplvl_txfemplvl:1; + unsigned ptxfemplvl:1; +#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + unsigned reserved9_31:23; + } b; +}; + +/* + * This union represents the bit fields of the Core USB Configuration Register + * (GUSBCFG). Set the bits using the bit fields then write the d32 value to + * the register. + */ +union gusbcfg_data { + u32 d32; + 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 otgutmifssel:1; + unsigned ulpi_fsls:1; + unsigned ulpi_auto_res:1; + unsigned ulpi_clk_sus_m:1; + unsigned ulpi_ext_vbus_drv:1; + unsigned ulpi_int_vbus_indicator:1; + unsigned term_sel_dl_pulse:1; + unsigned reserved23_28:6; + unsigned force_host_mode:1; + unsigned force_device_mode:1; + unsigned corrupt_tx_packet:1; + } b; +}; + +/* + * This union represents the bit fields of the Core Reset Register (GRSTCTL). + * Set/clear the bits using the bit fields then write the d32 value to the + * register. + */ +union grstctl_data { + u32 d32; + struct { + /* + * Core Soft Reset (CSftRst) (Device and Host) + * + * The application can flush the control logic in the entire + * core using this bit. This bit resets the pipelines in the AHB + * Clock domain as well as the PHY Clock domain. + * + * The state machines are reset to an IDLE state, the control + * bits in the CSRs are cleared, all the transmit FIFOs and the + * receive FIFO are flushed. + * + * The status mask bits that control the generation of the + * interrupt, are cleared, to clear the interrupt. The interrupt + * status bits are not cleared, so the application can get the + * status of any events that occurred in the core after it has + * set this bit. + * + * Any transactions on the AHB are terminated as soon as + * possible following the protocol. Any transactions on the USB + * are terminated immediately. + * + * The configuration settings in the CSRs are unchanged, so the + * software doesn't have to reprogram these registers (Device + * Configuration/Host Configuration/Core System + * Configuration/Core PHY Configuration). + * + * The application can write to this bit, any time it wants to + * reset the core. This is a self clearing bit and the core + * clears this bit after all the necessary logic is reset in the + * core, which may take several clocks, depending on the current + * state of the core. + */ + unsigned csftrst:1; + /* + * Hclk Soft Reset + * + * The application uses this bit to reset the control logic in + * the AHB clock domain. Only AHB clock domain pipelines are + * reset. + */ + unsigned hsftrst:1; + /* + * Host Frame Counter Reset (Host Only)
+ * + * The application can reset the (micro)frame number counter + * inside the core, using this bit. When the (micro)frame + * counter is reset, the subsequent SOF sent out by the core, + * will have a (micro)frame number of 0. + */ + unsigned hstfrm:1; + /* + * In Token Sequence Learning Queue Flush (INTknQFlsh) (Device + * Only) + */ + unsigned intknqflsh:1; + /* + * RxFIFO Flush (RxFFlsh) (Device and Host) + * + * The application can flush the entire Receive FIFO using this + * bit. + * + * The application must first ensure that the core is not in the + * middle of a transaction. + * + * The application should write into this bit, only after making + * sure that neither the DMA engine is reading from the RxFIFO + * nor the MAC is writing the data in to the FIFO. + * + * The application should wait until the bit is cleared before + * performing any other operations. This bit will takes 8 clocks + * (slowest of PHY or AHB clock) to clear. + */ + unsigned rxfflsh:1; + /* + * TxFIFO Flush (TxFFlsh) (Device and Host). + * + * This bit is used to selectively flush a single or all + * transmit FIFOs. The application must first ensure that the + * core is not in the middle of a transaction. + * + * The application should write into this bit, only after making + * sure that neither the DMA engine is writing into the TxFIFO + * nor the MAC is reading the data out of the FIFO. + * + * The application should wait until the core clears this bit, + * before performing any operations. This bit will takes 8 + * clocks (slowest of PHY or AHB clock) to clear. + */ + unsigned txfflsh:1; + + /* + * TxFIFO Number (TxFNum) (Device and Host). + * + * This is the FIFO number which needs to be flushed, using the + * TxFIFO Flush bit. This field should not be changed until the + * TxFIFO Flush bit is cleared by the core. + * - 0x0 : Non Periodic TxFIFO Flush + * - 0x1 : Periodic TxFIFO #1 Flush in device mode + * or Periodic TxFIFO in host mode + * - 0x2 : Periodic TxFIFO #2 Flush in device mode. + * - ... + * - 0xF : Periodic TxFIFO #15 Flush in device mode + * - 0x10: Flush all the Transmit NonPeriodic and + * Transmit Periodic FIFOs in the core + */ + unsigned txfnum:5; +#define DWC_GRSTCTL_TXFNUM_ALL 0x10 + + /* Reserved */ + unsigned reserved11_29:19; + /* + * DMA Request Signal. Indicated DMA request is in progress. + * Used for debug purpose. + */ + unsigned dmareq:1; + /* + * AHB Master Idle. Indicates the AHB Master State Machine is + * in IDLE condition. + */ + unsigned ahbidle:1; + } b; +}; + + +/* + * This union represents the bit fields of the Core Interrupt Mask Register + * (GINTMSK). Set/clear the bits using the bit fields then write the d32 value + * to the register. + */ +union gintmsk_data { + u32 d32; + 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 incomplisoin:1; + unsigned incomplisoout: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; +}; + +/* + * This union represents the bit fields of the Core Interrupt Register + * (GINTSTS). Set/clear the bits using the bit fields then write the d32 value + * to the register. + */ +union gintsts_data { + u32 d32; +#define DWC_SOF_INTR_MASK 0x0008 + + struct { +#define DWC_HOST_MODE 1 + unsigned curmode: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 intokenrx:1; + unsigned epmismatch:1; + unsigned inepint:1; + unsigned outepintr:1; + unsigned incomplisoin:1; + unsigned incomplisoout: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; +}; + +/* + * This union represents the bit fields in the Device Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the d32 element then + * read out the bits using the bit elements. + */ +union device_grxsts_data { + u32 d32; + struct { + unsigned epnum:4; + unsigned bcnt:11; + unsigned dpid:2; + +#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 */ + unsigned pktsts:4; + unsigned fn:4; + unsigned reserved:7; + } b; +}; + +/* + * This union represents the bit fields in the Host Receive Status Read and + * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the d32 element then + * read out the bits using the bit elements. + */ +union host_grxsts_data { + u32 d32; + struct { + unsigned chnum:4; + unsigned bcnt:11; + unsigned dpid:2; + + unsigned pktsts:4; +#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 + + unsigned reserved:11; + } b; +}; + +/* + * This union represents the bit fields in the FIFO Size Registers (HPTXFSIZ, + * GNPTXFSIZ, DPTXFSIZn, DIEPTXFn). Read the register into the d32 element then + * read out the bits using the bit elements. + */ +union fifosize_data { + u32 d32; + struct { + unsigned startaddr:16; + unsigned depth:16; + } b; +}; + +/* + * This union represents the bit fields in the Non-Periodic Transmit FIFO/Queue + * Status Register (GNPTXSTS). Read the register into the d32 element then read + * out the bits using the bit elements. + */ +union gnptxsts_data { + u32 d32; + struct { + unsigned nptxfspcavail:16; + unsigned nptxqspcavail:8; + /* + * Top of the Non-Periodic Transmit Request Queue + * - bit 24 - Terminate (Last entry for the selected + * channel/EP) + * - bits 26:25 - Token Type + * - 2'b00 - IN/OUT + * - 2'b01 - Zero Length OUT + * - 2'b10 - PING/Complete Split + * - 2'b11 - Channel Halt + * - bits 30:27 - Channel/EP Number + */ + unsigned nptxqtop_terminate:1; + unsigned nptxqtop_token:2; + unsigned nptxqtop_chnep:4; + unsigned reserved:1; + } b; +}; + +/* + * This union represents the bit fields in the Transmit FIFO Status Register + * (DTXFSTS). Read the register into the d32 element then read out the bits + * using the bit elements. + */ +union dtxfsts_data { + u32 d32; + struct { + unsigned txfspcavail:16; + unsigned reserved:16; + } b; +}; + +/* + * This union represents the bit fields in the I2C Control Register (I2CCTL). + * Read the register into the d32 element then read out the bits using the bit + * elements. + */ +union gi2cctl_data { + u32 d32; + struct { + unsigned rwdata:8; + unsigned regaddr:8; + unsigned addr:7; + unsigned i2cen:1; + unsigned ack:1; + unsigned i2csuspctl:1; + unsigned i2cdevaddr:2; + unsigned reserved:2; + unsigned rw:1; + unsigned bsydne:1; + } b; +}; + +/* + * This union represents the bit fields in the User HW Config1 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg1_data { + u32 d32; + struct { + unsigned ep_dir0:2; + unsigned ep_dir1:2; + unsigned ep_dir2:2; + unsigned ep_dir3:2; + unsigned ep_dir4:2; + unsigned ep_dir5:2; + unsigned ep_dir6:2; + unsigned ep_dir7:2; + unsigned ep_dir8:2; + unsigned ep_dir9:2; + unsigned ep_dir10:2; + unsigned ep_dir11:2; + unsigned ep_dir12:2; + unsigned ep_dir13:2; + unsigned ep_dir14:2; + unsigned ep_dir15:2; + } b; +}; + +/* + * This union represents the bit fields in the User HW Config2 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg2_data { + u32 d32; + struct { + /* GHWCFG2 */ + unsigned op_mode: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 + + unsigned architecture:2; + unsigned point2point:1; + unsigned hs_phy_type:2; +#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 + + 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; +}; + +/* + * This union represents the bit fields in the User HW Config3 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg3_data { + u32 d32; + struct { + /* GHWCFG3 */ + unsigned xfer_size_cntr_width:4; + unsigned packet_size_cntr_width:3; + unsigned otg_func:1; + unsigned i2c:1; + unsigned vendor_ctrl_if:1; + unsigned optional_features:1; + unsigned synch_reset_type:1; + unsigned reserved15_12:4; + unsigned dfifo_depth:16; + } b; +}; + +/* + * This union represents the bit fields in the User HW Config4 Register. Read + * the register into the d32 element then read out the bits using the bit + * elements. + */ +union hwcfg4_data { + u32 d32; + struct { + unsigned num_dev_perio_in_ep:4; + unsigned power_optimiz:1; + unsigned min_ahb_freq:9; + unsigned utmi_phy_data_width:2; + unsigned num_dev_mode_ctrl_ep:4; + unsigned iddig_filt_en:1; + unsigned vbus_valid_filt_en:1; + unsigned a_valid_filt_en:1; + unsigned b_valid_filt_en:1; + unsigned session_end_filt_en:1; + unsigned ded_fifo_en:1; + unsigned num_in_eps:4; + unsigned reserved31_30:2; + } b; +}; + +/* + * 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. + */ +struct device_global_regs { + /* Device Configuration Register. Offset 800h */ + u32 dcfg; + /* Device Control Register. Offset: 804h */ + u32 dctl; + /* Device Status Register (Read Only). Offset: 808h */ + u32 dsts; + /* Reserved. Offset: 80Ch */ + u32 unused; + /* Device IN Endpoint Common Interrupt Mask Register. Offset: 810h */ + u32 diepmsk; + /* Device OUT Endpoint Common Interrupt MaskRegister. Offset: 814h */ + u32 doepmsk; + /* Device All Endpoints Interrupt Register. Offset: 818h */ + u32 daint; + /* Device All Endpoints Interrupt Mask Register. Offset: 81Ch */ + u32 daintmsk; + /* Device IN Token Queue Read Register-1 (Read Only). Offset: 820h */ + u32 dtknqr1; + /* Device IN Token Queue Read Register-2 (Read Only). Offset: 824h */ + u32 dtknqr2; + /* Device VBUS discharge Register. Offset: 828h */ + u32 dvbusdis; + /* Device VBUS Pulse Register. Offset: 82Ch */ + u32 dvbuspulse; + /* + * Device IN Token Queue Read Register-3 (Read Only). + * Device Thresholding control register (Read/Write) + * Offset: 830h + */ + u32 dtknqr3_dthrctl; + /* + * Device IN Token Queue Read Register-4 (Read Only). + * Device IN EPs empty Inr. Mask Register (Read/Write) + * Offset: 834h + */ + u32 dtknqr4_fifoemptymsk; +}; + +/* + * This union represents the bit fields in the Device Configuration Register. + * Read the register into the d32 member then set/clear the bits using the bit + * elements. Write the d32 member to the dcfg register. + */ +union dcfg_data { + u32 d32; + struct { + /* Device Speed */ + unsigned devspd:2; + /* Non Zero Length Status OUT Handshake */ + unsigned nzstsouthshk:1; +#define DWC_DCFG_SEND_STALL 1 + + unsigned reserved3:1; + /* Device Addresses */ + unsigned devaddr:7; + /* Periodic Frame Interval */ + unsigned perfrint:2; +#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 + + unsigned reserved13_17:5; + /* In Endpoint Mis-match count */ + unsigned epmscnt:4; + } b; +}; + +/* + * This union represents the bit fields in the Device Control Register. Read + * the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union dctl_data { + u32 d32; + struct { + /* Remote Wakeup */ + unsigned rmtwkupsig:1; + /* Soft Disconnect */ + unsigned sftdiscon:1; + /* Global Non-Periodic IN NAK Status */ + unsigned gnpinnaksts:1; + /* Global OUT NAK Status */ + unsigned goutnaksts:1; + /* Test Control */ + unsigned tstctl:3; + /* Set Global Non-Periodic IN NAK */ + unsigned sgnpinnak:1; + /* Clear Global Non-Periodic IN NAK */ + unsigned cgnpinnak:1; + /* Set Global OUT NAK */ + unsigned sgoutnak:1; + /* Clear Global OUT NAK */ + unsigned cgoutnak:1; + unsigned reserved:21; + } b; +}; + +/* + * This union represents the bit fields in the Device Status Register. Read the + * register into the d32 member then set/clear the bits using the bit elements. + */ +union dsts_data { + u32 d32; + struct { + /* Suspend Status */ + unsigned suspsts:1; + /* Enumerated Speed */ + unsigned enumspd:2; +#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 + + /* Erratic Error */ + unsigned errticerr:1; + unsigned reserved4_7:4; + /* Frame or Microframe Number of the received SOF */ + unsigned soffn:14; + unsigned reserved22_31:10; + } b; +}; + +/* + * This union represents the bit fields in the Device IN EP Interrupt Register + * and the Device IN EP Common Mask Register. Read the register into the d32 + * member then set/clear the bits using the bit elements. + */ +union diepint_data { + u32 d32; + struct { + /* Transfer complete mask */ + unsigned xfercompl:1; + /* Endpoint disable mask */ + unsigned epdisabled:1; + /* AHB Error mask */ + unsigned ahberr:1; + /* TimeOUT Handshake mask (non-ISOC EPs) */ + unsigned timeout:1; + /* IN Token received with TxF Empty mask */ + unsigned intktxfemp:1; + /* IN Token Received with EP mismatch mask */ + unsigned intknepmis:1; + /* IN Endpoint HAK Effective mask */ + unsigned inepnakeff:1; + /* IN Endpoint HAK Effective mask */ + unsigned emptyintr:1; + unsigned txfifoundrn:1; + unsigned reserved08_31:23; + } b; +}; + +/* + * This union represents the bit fields in the Device OUT EP Interrupt + * Registerand Device OUT EP Common Interrupt Mask Register. Read the register + * into the d32 member then set/clear the bits using the bit elements. + */ +union doepint_data { + u32 d32; + struct { + /* Transfer complete */ + unsigned xfercompl:1; + /* Endpoint disable */ + unsigned epdisabled:1; + /* AHB Error */ + unsigned ahberr:1; + /* Setup Phase Done (contorl EPs) */ + unsigned setup:1; + unsigned reserved04_31:28; + } b; +}; + +/* + * This union represents the bit fields in the Device All EP Interrupt and Mask + * Registers. Read the register into the d32 member then set/clear the bits + * using the bit elements. + */ +union daint_data { + u32 d32; + struct { + /* IN Endpoint bits */ + unsigned in:16; + /* OUT Endpoint bits */ + unsigned out:16; + } ep; + struct { + /* IN Endpoint bits */ + unsigned inep0:1; + unsigned inep1:1; + unsigned inep2:1; + unsigned inep3:1; + unsigned inep4:1; + unsigned inep5:1; + unsigned inep6:1; + unsigned inep7:1; + unsigned inep8:1; + unsigned inep9:1; + unsigned inep10:1; + unsigned inep11:1; + unsigned inep12:1; + unsigned inep13:1; + unsigned inep14:1; + unsigned inep15:1; + /* OUT Endpoint bits */ + unsigned outep0:1; + unsigned outep1:1; + unsigned outep2:1; + unsigned outep3:1; + unsigned outep4:1; + unsigned outep5:1; + unsigned outep6:1; + unsigned outep7:1; + unsigned outep8:1; + unsigned outep9:1; + unsigned outep10:1; + unsigned outep11:1; + unsigned outep12:1; + unsigned outep13:1; + unsigned outep14:1; + unsigned outep15:1; + } b; +}; + +/* + * This union represents the bit fields in the Device IN Token Queue Read + * Registers. Read the register into the d32 member. READ-ONLY Register + */ +union dtknq1_data { + u32 d32; + struct { + /* In Token Queue Write Pointer */ + unsigned intknwptr:5; + /* Reserved */ + unsigned reserved05_06:2; + /* write pointer has wrapped. */ + unsigned wrap_bit:1; + /* EP Numbers of IN Tokens 0 ... 4 */ + unsigned epnums0_5:24; + } b; +}; + +/* + * This union represents Threshold control Register Read and write the register + * into the d32 member. READ-WRITABLE Register + */ +union dthrctl_data { + u32 d32; + struct { + /* non ISO Tx Thr. Enable */ + unsigned non_iso_thr_en:1; + /* ISO Tx Thr. Enable */ + unsigned iso_thr_en:1; + /* Tx Thr. Length */ + unsigned tx_thr_len:9; + /* Reserved */ + unsigned reserved11_15:5; + /* Rx Thr. Enable */ + unsigned rx_thr_en:1; + /* Rx Thr. Length */ + unsigned rx_thr_len:9; + /* Reserved */ + unsigned reserved26_31:6; + } b; +}; + +/* + * 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. + */ +struct device_in_ep_regs { + /* + * Device IN Endpoint Control Register. + * Offset: 900h + (ep_num * 20h) + 00h + */ + u32 diepctl; + /* Reserved. Offset:900h + (ep_num * 20h) + 04h */ + u32 reserved04; + /* + * Device IN Endpoint Interrupt Register. + * Offset: 900h + (ep_num * 20h) + 08h + */ + u32 diepint; + /* Reserved. Offset:900h + (ep_num * 20h) + 0Ch */ + u32 reserved0C; + /* + * Device IN Endpoint Transfer Size Register. + * Offset: 900h + (ep_num * 20h) + 10h + */ + u32 dieptsiz; + /* + * Device IN Endpoint DMA Address Register. + * Offset: 900h + (ep_num * 20h) + 14h + */ + u32 diepdma; + /* + * Device IN Endpoint Transmit FIFO Status Register. + * Offset: 900h + (ep_num * 20h) + 18h + */ + u32 dtxfsts; + /* + * Reserved. + * Offset: 900h + (ep_num * 20h) + 1Ch - 900h + (ep_num * 20h) + 1Ch + */ + u32 reserved18; +}; + +/* + * 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. + */ +struct device_out_ep_regs { + /* + * Device OUT Endpoint Control Register. + * Offset: B00h + (ep_num * 20h) + 00h + */ + u32 doepctl; + /* + * Device OUT Endpoint Frame number Register. + * Offset: B00h + (ep_num * 20h) + 04h + */ + u32 doepfn; + /* + * Device OUT Endpoint Interrupt Register. + * Offset: B00h + (ep_num * 20h) + 08h + */ + u32 doepint; + /* Reserved. Offset:B00h + (ep_num * 20h) + 0Ch */ + u32 reserved0C; + /* + * Device OUT Endpoint Transfer Size Register. + * Offset: B00h + (ep_num * 20h) + 10h + */ + u32 doeptsiz; + /* + * Device OUT Endpoint DMA Address Register. + * Offset: B00h + (ep_num * 20h) + 14h + */ + u32 doepdma; + /* + * Reserved. + * Offset:B00h + (ep_num * 20h) + 18h - B00h + (ep_num * 20h) + 1Ch + */ + u32 unused[2]; +}; + +/* + * This union represents the bit fields in the Device EP Control Register. Read + * the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union depctl_data { + u32 d32; + struct { + /* Maximum Packet Size + * IN/OUT EPn + * IN/OUT EP0 - 2 bits + * 2'b00: 64 Bytes + * 2'b01: 32 + * 2'b10: 16 + * 2'b11: 8 + */ + unsigned mps:11; +#define DWC_DEP0CTL_MPS_64 0 +#define DWC_DEP0CTL_MPS_32 1 +#define DWC_DEP0CTL_MPS_16 2 +#define DWC_DEP0CTL_MPS_8 3 + + /* + * Next Endpoint + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved + */ + unsigned nextep:4; + /* USB Active Endpoint */ + unsigned usbactep:1; + /* + * Endpoint DPID (INTR/Bulk IN and OUT endpoints) This field + * contains the PID of the packet going to be received or + * transmitted on this endpoint. The application should program + * the PID of the first packet going to be received or + * transmitted on this endpoint , after the endpoint is + * activated. Application use the SetD1PID and SetD0PID fields + * of this register to program either D0 or D1 PID. + * + * The encoding for this field is + * - 0: D0 + * - 1: D1 + */ + unsigned dpid:1; + /* NAK Status */ + unsigned naksts:1; + /* Endpoint Type + * 2'b00: Control + * 2'b01: Isochronous + * 2'b10: Bulk + * 2'b11: Interrupt + */ + unsigned eptype:2; + /* + * Snoop Mode + * OUT EPn/OUT EP0 + * IN EPn/IN EP0 - reserved + */ + unsigned snp:1; + /* Stall Handshake */ + unsigned stall:1; + /* + * Tx Fifo Number + * IN EPn/IN EP0 + * OUT EPn/OUT EP0 - reserved + */ + unsigned txfnum:4; + /* Clear NAK */ + unsigned cnak:1; + /* Set NAK */ + unsigned snak:1; + /* + * Set DATA0 PID (INTR/Bulk IN and OUT endpoints) + * + * Writing to this field sets the Endpoint DPID (DPID) field in + * this register to DATA0. Set Even (micro)frame (SetEvenFr) + * (ISO IN and OUT Endpoints) + * + * Writing to this field sets the Even/Odd (micro)frame + * (EO_FrNum) field to even (micro) frame. + */ + unsigned setd0pid:1; + /* + * Set DATA1 PID (INTR/Bulk IN and OUT endpoints) + * + * Writing to this field sets the Endpoint DPID (DPID) field in + * this register to DATA1 Set Odd (micro)frame (SetOddFr) (ISO + * IN and OUT Endpoints) + * + * Writing to this field sets the Even/Odd (micro)frame + * (EO_FrNum) field to odd (micro) frame. + */ + unsigned setd1pid:1; + /* Endpoint Disable */ + unsigned epdis:1; + /* Endpoint Enable */ + unsigned epena:1; + } b; +}; + +/* + * This union represents the bit fields in the Device EP Transfer Size Register. + * Read the register into the d32 member then set/clear the bits using the bit + * elements. + */ +union deptsiz_data { + u32 d32; + struct { + /* Transfer size */ + unsigned xfersize:19; + /* Packet Count */ + unsigned pktcnt:10; + /* Multi Count - Periodic IN endpoints */ + unsigned mc:2; + unsigned reserved:1; + } b; +}; + +/* + * This union represents the bit fields in the Device EP 0 Transfer Size + * Register. Read the register into the d32 member then set/clear the bits + * using the bit elements. + */ +union deptsiz0_data { + u32 d32; + struct { + /* Transfer size */ + unsigned xfersize:7; + /* Reserved */ + unsigned reserved7_18:12; + /* Packet Count */ + unsigned pktcnt:2; + /* Reserved */ + unsigned reserved21_28:9; + /* Setup Packet Count (DOEPTSIZ0 Only) */ + unsigned supcnt:2; + unsigned reserved31; + } b; +}; + +#define MAX_PERIO_FIFOS 15 /* Max periodic FIFOs */ +#define MAX_TX_FIFOS 15 /* Max non-periodic FIFOs */ +#define MAX_EPS_CHANNELS 4 /* Max Endpoints/HostChannels */ + +/* + * 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 */ + struct device_global_regs *dev_global_regs; +#define DWC_DEV_GLOBAL_REG_OFFSET 0x800 + + /* Device Logical IN Endpoint-Specific Registers 900h-AFCh */ + struct device_in_ep_regs *in_ep_regs[MAX_EPS_CHANNELS/2]; +#define DWC_DEV_IN_EP_REG_OFFSET 0x900 +#define DWC_EP_REG_OFFSET 0x20 + + /* Device Logical OUT Endpoint-Specific Registers B00h-CFCh */ + struct device_out_ep_regs *out_ep_regs[MAX_EPS_CHANNELS/2]; +#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00 + + /* 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 ep0 */ + 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; +}; + +/* + * The Host Global Registers structure defines the size and relative + * field offsets for the Host Mode Global Registers. Host Global + * Registers offsets 400h-7FFh. +*/ +struct host_global_regs { + /* Host Configuration Register. Offset: 400h */ + u32 hcfg; + /* Host Frame Interval Register. Offset: 404h */ + u32 hfir; + /* Host Frame Number / Frame Remaining Register. Offset: 408h */ + u32 hfnum; + /* Reserved. Offset: 40Ch */ + u32 reserved40C; + /* Host Periodic Transmit FIFO/ Queue Status Register. Offset: 410h */ + u32 hptxsts; + /* Host All Channels Interrupt Register. Offset: 414h */ + u32 haint; + /* Host All Channels Interrupt Mask Register. Offset: 418h */ + u32 haintmsk; +}; + +/* + * This union represents the bit fields in the Host Configuration Register. + * Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcfg register. + */ +union hcfg_data { + u32 d32; + struct { + /* FS/LS Phy Clock Select */ + unsigned fslspclksel:2; +#define DWC_HCFG_30_60_MHZ 0 +#define DWC_HCFG_48_MHZ 1 +#define DWC_HCFG_6_MHZ 2 + + /* FS/LS Only Support */ + unsigned fslssupp:1; + } b; +}; + +/* + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +union hfir_data { + u32 d32; + struct { + unsigned frint:16; + unsigned reserved:16; + } b; +}; + +/* + * This union represents the bit fields in the Host Frame Remaing/Number + * Register. + */ +union hfnum_data { + u32 d32; + struct { + unsigned frnum:16; +#define DWC_HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem:16; + } b; +}; + +union hptxsts_data { + u32 d32; + struct { + unsigned ptxfspcavail:16; + unsigned ptxqspcavail:8; + /* + * Top of the Periodic Transmit Request Queue + * - bit 24 - Terminate (last entry of selected channel) + * - bits 26:25 - Token Type + * - 2'b00 - Zero length + * - 2'b01 - Ping + * - 2'b10 - Disable + * - bits 30:27 - Channel Number + * - bit 31 - Odd/even microframe + */ + unsigned ptxqtop_terminate:1; + unsigned ptxqtop_token:2; + unsigned ptxqtop_chnum:4; + unsigned ptxqtop_odd:1; + } b; +}; + +/* + * This union represents the bit fields in the Host Port Control and Status + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hprt0 register. + */ +union hprt0_data { + u32 d32; + 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 DWC_HPRT0_PRTSPD_HIGH_SPEED 0 +#define DWC_HPRT0_PRTSPD_FULL_SPEED 1 +#define DWC_HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31:13; + } b; +}; + +/* + * This union represents the bit fields in the Host All Interrupt Register. + */ +union haint_data { + u32 d32; + struct { + unsigned ch0:1; + unsigned ch1:1; + unsigned ch2:1; + unsigned ch3:1; + unsigned ch4:1; + unsigned ch5:1; + unsigned ch6:1; + unsigned ch7:1; + unsigned ch8:1; + unsigned ch9:1; + unsigned ch10:1; + unsigned ch11:1; + unsigned ch12:1; + unsigned ch13:1; + unsigned ch14:1; + unsigned ch15:1; + unsigned reserved:16; + } b; + + struct { + unsigned chint:16; + unsigned reserved:16; + } b2; +}; + +/* + * This union represents the bit fields in the Host All Interrupt Register. + */ +union haintmsk_data { + u32 d32; + struct { + unsigned ch0:1; + unsigned ch1:1; + unsigned ch2:1; + unsigned ch3:1; + unsigned ch4:1; + unsigned ch5:1; + unsigned ch6:1; + unsigned ch7:1; + unsigned ch8:1; + unsigned ch9:1; + unsigned ch10:1; + unsigned ch11:1; + unsigned ch12:1; + unsigned ch13:1; + unsigned ch14:1; + unsigned ch15:1; + unsigned reserved:16; + } b; + + struct { + unsigned chint:16; + unsigned reserved:16; + } b2; +}; + +/* + * Host Channel Specific Registers. 500h-5FCh + */ +struct dwc_hc_regs { + /* + * Host Channel 0 Characteristic Register. + * Offset: 500h + (chan_num * 20h) + 00h + */ + u32 hcchar; + /* + * Host Channel 0 Split Control Register. + * Offset: 500h + (chan_num * 20h) + 04h + */ + u32 hcsplt; + /* + * Host Channel 0 Interrupt Register. + * Offset: 500h + (chan_num * 20h) + 08h + */ + u32 hcint; + /* + * Host Channel 0 Interrupt Mask Register. + * Offset: 500h + (chan_num * 20h) + 0Ch + */ + u32 hcintmsk; + /* + * Host Channel 0 Transfer Size Register. + * Offset: 500h + (chan_num * 20h) + 10h + */ + u32 hctsiz; + /* + * Host Channel 0 DMA Address Register. + * Offset: 500h + (chan_num * 20h) + 14h + */ + u32 hcdma; + /* Reserved. + * Offset: 500h + (chan_num * 20h) + 18h - 500h + (chan_num * 20h) + 1Ch + */ + u32 reserved[2]; +}; + +/* + * This union represents the bit fields in the Host Channel Characteristics + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcchar register. + */ +union hcchar_data { + u32 d32; + struct { + /* Maximum packet size in bytes */ + unsigned mps:11; + /* Endpoint number */ + unsigned epnum:4; + /* 0: OUT, 1: IN */ + unsigned epdir: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; + /* 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; +}; + +union hcsplt_data { + u32 d32; + struct { + /* Port Address */ + unsigned prtaddr:7; + /* Hub Address */ + unsigned hubaddr:7; + /* Transaction Position */ + unsigned xactpos:2; +#define DWC_HCSPLIT_XACTPOS_MID 0 +#define DWC_HCSPLIT_XACTPOS_END 1 +#define DWC_HCSPLIT_XACTPOS_BEGIN 2 +#define DWC_HCSPLIT_XACTPOS_ALL 3 + + /* Do Complete Split */ + unsigned compsplt:1; + /* Reserved */ + unsigned reserved:14; + /* Split Enble */ + unsigned spltena:1; + } b; +}; + + +/* + * This union represents the bit fields in the Host All Interrupt Register. + */ +union hcint_data { + u32 d32; + struct { + /* Transfer Complete */ + unsigned xfercomp:1; + /* Channel Halted */ + unsigned chhltd:1; + /* AHB Error */ + unsigned ahberr:1; + /* STALL Response Received */ + unsigned stall:1; + /* NAK Response Received */ + unsigned nak:1; + /* ACK Response Received */ + unsigned ack:1; + /* NYET Response Received */ + unsigned nyet:1; + /* Transaction Err */ + unsigned xacterr:1; + /* Babble Error */ + unsigned bblerr:1; + /* Frame Overrun */ + unsigned frmovrun:1; + /* Data Toggle Error */ + unsigned datatglerr:1; + /* Reserved */ + unsigned reserved:21; + } b; +}; + +/* + * This union represents the bit fields in the Host Channel Transfer Size + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcchar register. + */ +union hctsiz_data { + u32 d32; + 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 DWC_HCTSIZ_DATA0 0 +#define DWC_HCTSIZ_DATA1 2 +#define DWC_HCTSIZ_DATA2 1 +#define DWC_HCTSIZ_MDATA 3 +#define DWC_HCTSIZ_SETUP 3 + + /* Do PING protocol when 1 */ + unsigned dopng:1; + } b; +}; + +/* + * This union represents the bit fields in the Host Channel Interrupt Mask + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. Write the d32 member to the hcintmsk register. + */ +union hcintmsk_data { + u32 d32; + 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; +}; + +/* 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 { + /* Host Global Registers starting at offset 400h.*/ + struct host_global_regs *host_global_regs; +#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400 + + /* Host Port 0 Control and Status Register */ + u32 *hprt0; +#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440 + + /* Host Channel Specific Registers at offsets 500h-5FCh. */ + struct dwc_hc_regs *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; +}; + +/* + * This union represents the bit fields in the Power and Clock Gating Control + * Register. Read the register into the d32 member then set/clear the bits using + * the bit elements. + */ +union pcgcctl_data { + u32 d32; + struct { + /* Stop Pclk */ + unsigned stoppclk:1; + /* Gate Hclk */ + unsigned gatehclk:1; + /* Power Clamp */ + unsigned pwrclmp:1; + /* Reset Power Down Modules */ + unsigned rstpdwnmodule:1; + /* PHY Suspended */ + unsigned physuspended:1; + unsigned reserved:27; + } b; +}; +#endif /* CONFIG_DWC_OTG_REG_LE */ +#endif