diff options
Diffstat (limited to 'drivers/usb/dwc2')
| -rw-r--r-- | drivers/usb/dwc2/Kconfig | 86 | ||||
| -rw-r--r-- | drivers/usb/dwc2/Makefile | 28 | ||||
| -rw-r--r-- | drivers/usb/dwc2/core.c | 2845 | ||||
| -rw-r--r-- | drivers/usb/dwc2/core.h | 950 | ||||
| -rw-r--r-- | drivers/usb/dwc2/core_intr.c | 511 | ||||
| -rw-r--r-- | drivers/usb/dwc2/gadget.c | 3673 | ||||
| -rw-r--r-- | drivers/usb/dwc2/hcd.c | 2981 | ||||
| -rw-r--r-- | drivers/usb/dwc2/hcd.h | 769 | ||||
| -rw-r--r-- | drivers/usb/dwc2/hcd_ddma.c | 1212 | ||||
| -rw-r--r-- | drivers/usb/dwc2/hcd_intr.c | 2123 | ||||
| -rw-r--r-- | drivers/usb/dwc2/hcd_queue.c | 835 | ||||
| -rw-r--r-- | drivers/usb/dwc2/hw.h | 811 | ||||
| -rw-r--r-- | drivers/usb/dwc2/pci.c | 178 | ||||
| -rw-r--r-- | drivers/usb/dwc2/platform.c | 196 | 
14 files changed, 17198 insertions, 0 deletions
diff --git a/drivers/usb/dwc2/Kconfig b/drivers/usb/dwc2/Kconfig new file mode 100644 index 00000000000..f93807b3631 --- /dev/null +++ b/drivers/usb/dwc2/Kconfig @@ -0,0 +1,86 @@ +config USB_DWC2 +	bool "DesignWare USB2 DRD Core Support" +	depends on USB +	help +	  Say Y here if your system has a Dual Role Hi-Speed USB +	  controller based on the DesignWare HSOTG IP Core. + +	  For host mode, if you choose to build the driver as dynamically +	  linked modules, the core module will be called dwc2.ko, the PCI +	  bus interface module (if you have a PCI bus system) will be +	  called dwc2_pci.ko, and the platform interface module (for +	  controllers directly connected to the CPU) will be called +	  dwc2_platform.ko. For gadget mode, there will be a single +	  module called dwc2_gadget.ko. + +	  NOTE: The s3c-hsotg driver is now renamed to dwc2_gadget. The +	  host and gadget drivers are still currently separate drivers. +	  There are plans to merge the dwc2_gadget driver with the dwc2 +	  host driver in the near future to create a dual-role driver. + +if USB_DWC2 + +config USB_DWC2_HOST +	tristate "Host only mode" +	depends on USB +	help +	  The Designware USB2.0 high-speed host controller +	  integrated into many SoCs. + +config USB_DWC2_PLATFORM +	bool "DWC2 Platform" +	depends on USB_DWC2_HOST +	default USB_DWC2_HOST +	help +	  The Designware USB2.0 platform interface module for +	  controllers directly connected to the CPU. This is only +	  used for host mode. + +config USB_DWC2_PCI +	bool "DWC2 PCI" +	depends on USB_DWC2_HOST && PCI +	default USB_DWC2_HOST +	help +	  The Designware USB2.0 PCI interface module for controllers +	  connected to a PCI bus. This is only used for host mode. + +comment "Gadget mode requires USB Gadget support to be enabled" + +config USB_DWC2_PERIPHERAL +	tristate "Gadget only mode" +	depends on USB_GADGET +	help +	  The Designware USB2.0 high-speed gadget controller +	  integrated into many SoCs. + +config USB_DWC2_DEBUG +	bool "Enable Debugging Messages" +	help +	  Say Y here to enable debugging messages in the DWC2 Driver. + +config USB_DWC2_VERBOSE +	bool "Enable Verbose Debugging Messages" +	depends on USB_DWC2_DEBUG +	help +	  Say Y here to enable verbose debugging messages in the DWC2 Driver. +	  WARNING: Enabling this will quickly fill your message log. +	  If in doubt, say N. + +config USB_DWC2_TRACK_MISSED_SOFS +	bool "Enable Missed SOF Tracking" +	help +	  Say Y here to enable logging of missed SOF events to the dmesg log. +	  WARNING: This feature is still experimental. +	  If in doubt, say N. + +config USB_DWC2_DEBUG_PERIODIC +	bool "Enable Debugging Messages For Periodic Transfers" +	depends on USB_DWC2_DEBUG || USB_DWC2_VERBOSE +	default y +	help +	  Say N here to disable (verbose) debugging messages to be +	  logged for periodic transfers. This allows better debugging of +	  non-periodic transfers, but of course the debug logs will be +	  incomplete. Note that this also disables some debug messages +	  for which the transfer type cannot be deduced. +endif diff --git a/drivers/usb/dwc2/Makefile b/drivers/usb/dwc2/Makefile new file mode 100644 index 00000000000..b73d2a52797 --- /dev/null +++ b/drivers/usb/dwc2/Makefile @@ -0,0 +1,28 @@ +ccflags-$(CONFIG_USB_DWC2_DEBUG)	+= -DDEBUG +ccflags-$(CONFIG_USB_DWC2_VERBOSE)	+= -DVERBOSE_DEBUG + +obj-$(CONFIG_USB_DWC2_HOST)		+= dwc2.o +dwc2-y					:= core.o core_intr.o +dwc2-y					+= hcd.o hcd_intr.o +dwc2-y					+= hcd_queue.o hcd_ddma.o + +# NOTE: The previous s3c-hsotg peripheral mode only driver has been moved to +# this location and renamed gadget.c. When building for dynamically linked +# modules, dwc2_gadget.ko will get built for peripheral mode. For host mode, +# the core module will be dwc2.ko, the PCI bus interface module will called +# dwc2_pci.ko and the platform interface module will be called dwc2_platform.ko. +# At present the host and gadget driver will be separate drivers, but there +# are plans in the near future to create a dual-role driver. + +ifneq ($(CONFIG_USB_DWC2_PCI),) +	obj-$(CONFIG_USB_DWC2_HOST)	+= dwc2_pci.o +	dwc2_pci-y			:= pci.o +endif + +ifneq ($(CONFIG_USB_DWC2_PLATFORM),) +	obj-$(CONFIG_USB_DWC2_HOST)	+= dwc2_platform.o +	dwc2_platform-y			:= platform.o +endif + +obj-$(CONFIG_USB_DWC2_PERIPHERAL)	+= dwc2_gadget.o +dwc2_gadget-y				:= gadget.o diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c new file mode 100644 index 00000000000..27d2c9b8a03 --- /dev/null +++ b/drivers/usb/dwc2/core.c @@ -0,0 +1,2845 @@ +/* + * core.c - DesignWare HS OTG Controller common routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * The Core code 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. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +/** + * dwc2_enable_common_interrupts() - Initializes the commmon interrupts, + * used in both device and host modes + * + * @hsotg: Programming view of the DWC_otg controller + */ +static void dwc2_enable_common_interrupts(struct dwc2_hsotg *hsotg) +{ +	u32 intmsk; + +	/* Clear any pending OTG Interrupts */ +	writel(0xffffffff, hsotg->regs + GOTGINT); + +	/* Clear any pending interrupts */ +	writel(0xffffffff, hsotg->regs + GINTSTS); + +	/* Enable the interrupts in the GINTMSK */ +	intmsk = GINTSTS_MODEMIS | GINTSTS_OTGINT; + +	if (hsotg->core_params->dma_enable <= 0) +		intmsk |= GINTSTS_RXFLVL; + +	intmsk |= GINTSTS_CONIDSTSCHNG | GINTSTS_WKUPINT | GINTSTS_USBSUSP | +		  GINTSTS_SESSREQINT; + +	writel(intmsk, hsotg->regs + GINTMSK); +} + +/* + * Initializes the FSLSPClkSel field of the HCFG register depending on the + * PHY type + */ +static void dwc2_init_fs_ls_pclk_sel(struct dwc2_hsotg *hsotg) +{ +	u32 hcfg, val; + +	if ((hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && +	     hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && +	     hsotg->core_params->ulpi_fs_ls > 0) || +	    hsotg->core_params->phy_type == DWC2_PHY_TYPE_PARAM_FS) { +		/* Full speed PHY */ +		val = HCFG_FSLSPCLKSEL_48_MHZ; +	} else { +		/* High speed PHY running at full speed or high speed */ +		val = HCFG_FSLSPCLKSEL_30_60_MHZ; +	} + +	dev_dbg(hsotg->dev, "Initializing HCFG.FSLSPClkSel to %08x\n", val); +	hcfg = readl(hsotg->regs + HCFG); +	hcfg &= ~HCFG_FSLSPCLKSEL_MASK; +	hcfg |= val << HCFG_FSLSPCLKSEL_SHIFT; +	writel(hcfg, hsotg->regs + HCFG); +} + +/* + * Do core a soft reset of the core.  Be careful with this because it + * resets all the internal state machines of the core. + */ +static int dwc2_core_reset(struct dwc2_hsotg *hsotg) +{ +	u32 greset; +	int count = 0; + +	dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	/* Wait for AHB master IDLE state */ +	do { +		usleep_range(20000, 40000); +		greset = readl(hsotg->regs + GRSTCTL); +		if (++count > 50) { +			dev_warn(hsotg->dev, +				 "%s() HANG! AHB Idle GRSTCTL=%0x\n", +				 __func__, greset); +			return -EBUSY; +		} +	} while (!(greset & GRSTCTL_AHBIDLE)); + +	/* Core Soft Reset */ +	count = 0; +	greset |= GRSTCTL_CSFTRST; +	writel(greset, hsotg->regs + GRSTCTL); +	do { +		usleep_range(20000, 40000); +		greset = readl(hsotg->regs + GRSTCTL); +		if (++count > 50) { +			dev_warn(hsotg->dev, +				 "%s() HANG! Soft Reset GRSTCTL=%0x\n", +				 __func__, greset); +			return -EBUSY; +		} +	} while (greset & GRSTCTL_CSFTRST); + +	/* +	 * NOTE: This long sleep is _very_ important, otherwise the core will +	 * not stay in host mode after a connector ID change! +	 */ +	usleep_range(150000, 200000); + +	return 0; +} + +static int dwc2_fs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) +{ +	u32 usbcfg, i2cctl; +	int retval = 0; + +	/* +	 * core_init() is now called on every switch so only call the +	 * following for the first time through +	 */ +	if (select_phy) { +		dev_dbg(hsotg->dev, "FS PHY selected\n"); +		usbcfg = readl(hsotg->regs + GUSBCFG); +		usbcfg |= GUSBCFG_PHYSEL; +		writel(usbcfg, hsotg->regs + GUSBCFG); + +		/* Reset after a PHY select */ +		retval = dwc2_core_reset(hsotg); +		if (retval) { +			dev_err(hsotg->dev, "%s() Reset failed, aborting", +					__func__); +			return retval; +		} +	} + +	/* +	 * 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 (dwc2_is_host_mode(hsotg)) +		dwc2_init_fs_ls_pclk_sel(hsotg); + +	if (hsotg->core_params->i2c_enable > 0) { +		dev_dbg(hsotg->dev, "FS PHY enabling I2C\n"); + +		/* Program GUSBCFG.OtgUtmiFsSel to I2C */ +		usbcfg = readl(hsotg->regs + GUSBCFG); +		usbcfg |= GUSBCFG_OTG_UTMI_FS_SEL; +		writel(usbcfg, hsotg->regs + GUSBCFG); + +		/* Program GI2CCTL.I2CEn */ +		i2cctl = readl(hsotg->regs + GI2CCTL); +		i2cctl &= ~GI2CCTL_I2CDEVADDR_MASK; +		i2cctl |= 1 << GI2CCTL_I2CDEVADDR_SHIFT; +		i2cctl &= ~GI2CCTL_I2CEN; +		writel(i2cctl, hsotg->regs + GI2CCTL); +		i2cctl |= GI2CCTL_I2CEN; +		writel(i2cctl, hsotg->regs + GI2CCTL); +	} + +	return retval; +} + +static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) +{ +	u32 usbcfg; +	int retval = 0; + +	if (!select_phy) +		return 0; + +	usbcfg = readl(hsotg->regs + GUSBCFG); + +	/* +	 * HS PHY parameters. These parameters are preserved during soft reset +	 * so only program the first time. Do a soft reset immediately after +	 * setting phyif. +	 */ +	switch (hsotg->core_params->phy_type) { +	case DWC2_PHY_TYPE_PARAM_ULPI: +		/* ULPI interface */ +		dev_dbg(hsotg->dev, "HS ULPI PHY selected\n"); +		usbcfg |= GUSBCFG_ULPI_UTMI_SEL; +		usbcfg &= ~(GUSBCFG_PHYIF16 | GUSBCFG_DDRSEL); +		if (hsotg->core_params->phy_ulpi_ddr > 0) +			usbcfg |= GUSBCFG_DDRSEL; +		break; +	case DWC2_PHY_TYPE_PARAM_UTMI: +		/* UTMI+ interface */ +		dev_dbg(hsotg->dev, "HS UTMI+ PHY selected\n"); +		usbcfg &= ~(GUSBCFG_ULPI_UTMI_SEL | GUSBCFG_PHYIF16); +		if (hsotg->core_params->phy_utmi_width == 16) +			usbcfg |= GUSBCFG_PHYIF16; +		break; +	default: +		dev_err(hsotg->dev, "FS PHY selected at HS!\n"); +		break; +	} + +	writel(usbcfg, hsotg->regs + GUSBCFG); + +	/* Reset after setting the PHY parameters */ +	retval = dwc2_core_reset(hsotg); +	if (retval) { +		dev_err(hsotg->dev, "%s() Reset failed, aborting", +				__func__); +		return retval; +	} + +	return retval; +} + +static int dwc2_phy_init(struct dwc2_hsotg *hsotg, bool select_phy) +{ +	u32 usbcfg; +	int retval = 0; + +	if (hsotg->core_params->speed == DWC2_SPEED_PARAM_FULL && +	    hsotg->core_params->phy_type == DWC2_PHY_TYPE_PARAM_FS) { +		/* If FS mode with FS PHY */ +		retval = dwc2_fs_phy_init(hsotg, select_phy); +		if (retval) +			return retval; +	} else { +		/* High speed PHY */ +		retval = dwc2_hs_phy_init(hsotg, select_phy); +		if (retval) +			return retval; +	} + +	if (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI && +	    hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED && +	    hsotg->core_params->ulpi_fs_ls > 0) { +		dev_dbg(hsotg->dev, "Setting ULPI FSLS\n"); +		usbcfg = readl(hsotg->regs + GUSBCFG); +		usbcfg |= GUSBCFG_ULPI_FS_LS; +		usbcfg |= GUSBCFG_ULPI_CLK_SUSP_M; +		writel(usbcfg, hsotg->regs + GUSBCFG); +	} else { +		usbcfg = readl(hsotg->regs + GUSBCFG); +		usbcfg &= ~GUSBCFG_ULPI_FS_LS; +		usbcfg &= ~GUSBCFG_ULPI_CLK_SUSP_M; +		writel(usbcfg, hsotg->regs + GUSBCFG); +	} + +	return retval; +} + +static int dwc2_gahbcfg_init(struct dwc2_hsotg *hsotg) +{ +	u32 ahbcfg = readl(hsotg->regs + GAHBCFG); + +	switch (hsotg->hw_params.arch) { +	case GHWCFG2_EXT_DMA_ARCH: +		dev_err(hsotg->dev, "External DMA Mode not supported\n"); +		return -EINVAL; + +	case GHWCFG2_INT_DMA_ARCH: +		dev_dbg(hsotg->dev, "Internal DMA Mode\n"); +		if (hsotg->core_params->ahbcfg != -1) { +			ahbcfg &= GAHBCFG_CTRL_MASK; +			ahbcfg |= hsotg->core_params->ahbcfg & +				  ~GAHBCFG_CTRL_MASK; +		} +		break; + +	case GHWCFG2_SLAVE_ONLY_ARCH: +	default: +		dev_dbg(hsotg->dev, "Slave Only Mode\n"); +		break; +	} + +	dev_dbg(hsotg->dev, "dma_enable:%d dma_desc_enable:%d\n", +		hsotg->core_params->dma_enable, +		hsotg->core_params->dma_desc_enable); + +	if (hsotg->core_params->dma_enable > 0) { +		if (hsotg->core_params->dma_desc_enable > 0) +			dev_dbg(hsotg->dev, "Using Descriptor DMA mode\n"); +		else +			dev_dbg(hsotg->dev, "Using Buffer DMA mode\n"); +	} else { +		dev_dbg(hsotg->dev, "Using Slave mode\n"); +		hsotg->core_params->dma_desc_enable = 0; +	} + +	if (hsotg->core_params->dma_enable > 0) +		ahbcfg |= GAHBCFG_DMA_EN; + +	writel(ahbcfg, hsotg->regs + GAHBCFG); + +	return 0; +} + +static void dwc2_gusbcfg_init(struct dwc2_hsotg *hsotg) +{ +	u32 usbcfg; + +	usbcfg = readl(hsotg->regs + GUSBCFG); +	usbcfg &= ~(GUSBCFG_HNPCAP | GUSBCFG_SRPCAP); + +	switch (hsotg->hw_params.op_mode) { +	case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: +		if (hsotg->core_params->otg_cap == +				DWC2_CAP_PARAM_HNP_SRP_CAPABLE) +			usbcfg |= GUSBCFG_HNPCAP; +		if (hsotg->core_params->otg_cap != +				DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE) +			usbcfg |= GUSBCFG_SRPCAP; +		break; + +	case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: +	case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: +	case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: +		if (hsotg->core_params->otg_cap != +				DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE) +			usbcfg |= GUSBCFG_SRPCAP; +		break; + +	case GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE: +	case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE: +	case GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST: +	default: +		break; +	} + +	writel(usbcfg, hsotg->regs + GUSBCFG); +} + +/** + * dwc2_core_init() - Initializes the DWC_otg controller registers and + * prepares the core for device mode or host mode operation + * + * @hsotg:      Programming view of the DWC_otg controller + * @select_phy: If true then also set the Phy type + * @irq:        If >= 0, the irq to register + */ +int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq) +{ +	u32 usbcfg, otgctl; +	int retval; + +	dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + +	usbcfg = readl(hsotg->regs + GUSBCFG); + +	/* Set ULPI External VBUS bit if needed */ +	usbcfg &= ~GUSBCFG_ULPI_EXT_VBUS_DRV; +	if (hsotg->core_params->phy_ulpi_ext_vbus == +				DWC2_PHY_ULPI_EXTERNAL_VBUS) +		usbcfg |= GUSBCFG_ULPI_EXT_VBUS_DRV; + +	/* Set external TS Dline pulsing bit if needed */ +	usbcfg &= ~GUSBCFG_TERMSELDLPULSE; +	if (hsotg->core_params->ts_dline > 0) +		usbcfg |= GUSBCFG_TERMSELDLPULSE; + +	writel(usbcfg, hsotg->regs + GUSBCFG); + +	/* Reset the Controller */ +	retval = dwc2_core_reset(hsotg); +	if (retval) { +		dev_err(hsotg->dev, "%s(): Reset failed, aborting\n", +				__func__); +		return retval; +	} + +	/* +	 * This needs to happen in FS mode before any other programming occurs +	 */ +	retval = dwc2_phy_init(hsotg, select_phy); +	if (retval) +		return retval; + +	/* Program the GAHBCFG Register */ +	retval = dwc2_gahbcfg_init(hsotg); +	if (retval) +		return retval; + +	/* Program the GUSBCFG register */ +	dwc2_gusbcfg_init(hsotg); + +	/* Program the GOTGCTL register */ +	otgctl = readl(hsotg->regs + GOTGCTL); +	otgctl &= ~GOTGCTL_OTGVER; +	if (hsotg->core_params->otg_ver > 0) +		otgctl |= GOTGCTL_OTGVER; +	writel(otgctl, hsotg->regs + GOTGCTL); +	dev_dbg(hsotg->dev, "OTG VER PARAM: %d\n", hsotg->core_params->otg_ver); + +	/* Clear the SRP success bit for FS-I2c */ +	hsotg->srp_success = 0; + +	if (irq >= 0) { +		dev_dbg(hsotg->dev, "registering common handler for irq%d\n", +			irq); +		retval = devm_request_irq(hsotg->dev, irq, +					  dwc2_handle_common_intr, IRQF_SHARED, +					  dev_name(hsotg->dev), hsotg); +		if (retval) +			return retval; +	} + +	/* Enable common interrupts */ +	dwc2_enable_common_interrupts(hsotg); + +	/* +	 * Do device or host intialization based on mode during PCD and +	 * HCD initialization +	 */ +	if (dwc2_is_host_mode(hsotg)) { +		dev_dbg(hsotg->dev, "Host Mode\n"); +		hsotg->op_state = OTG_STATE_A_HOST; +	} else { +		dev_dbg(hsotg->dev, "Device Mode\n"); +		hsotg->op_state = OTG_STATE_B_PERIPHERAL; +	} + +	return 0; +} + +/** + * dwc2_enable_host_interrupts() - Enables the Host mode interrupts + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_enable_host_interrupts(struct dwc2_hsotg *hsotg) +{ +	u32 intmsk; + +	dev_dbg(hsotg->dev, "%s()\n", __func__); + +	/* Disable all interrupts */ +	writel(0, hsotg->regs + GINTMSK); +	writel(0, hsotg->regs + HAINTMSK); + +	/* Enable the common interrupts */ +	dwc2_enable_common_interrupts(hsotg); + +	/* Enable host mode interrupts without disturbing common interrupts */ +	intmsk = readl(hsotg->regs + GINTMSK); +	intmsk |= GINTSTS_DISCONNINT | GINTSTS_PRTINT | GINTSTS_HCHINT; +	writel(intmsk, hsotg->regs + GINTMSK); +} + +/** + * dwc2_disable_host_interrupts() - Disables the Host Mode interrupts + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_disable_host_interrupts(struct dwc2_hsotg *hsotg) +{ +	u32 intmsk = readl(hsotg->regs + GINTMSK); + +	/* Disable host mode interrupts without disturbing common interrupts */ +	intmsk &= ~(GINTSTS_SOF | GINTSTS_PRTINT | GINTSTS_HCHINT | +		    GINTSTS_PTXFEMP | GINTSTS_NPTXFEMP); +	writel(intmsk, hsotg->regs + GINTMSK); +} + +/* + * dwc2_calculate_dynamic_fifo() - Calculates the default fifo size + * For system that have a total fifo depth that is smaller than the default + * RX + TX fifo size. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_calculate_dynamic_fifo(struct dwc2_hsotg *hsotg) +{ +	struct dwc2_core_params *params = hsotg->core_params; +	struct dwc2_hw_params *hw = &hsotg->hw_params; +	u32 rxfsiz, nptxfsiz, ptxfsiz, total_fifo_size; + +	total_fifo_size = hw->total_fifo_size; +	rxfsiz = params->host_rx_fifo_size; +	nptxfsiz = params->host_nperio_tx_fifo_size; +	ptxfsiz = params->host_perio_tx_fifo_size; + +	/* +	 * Will use Method 2 defined in the DWC2 spec: minimum FIFO depth +	 * allocation with support for high bandwidth endpoints. Synopsys +	 * defines MPS(Max Packet size) for a periodic EP=1024, and for +	 * non-periodic as 512. +	 */ +	if (total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz)) { +		/* +		 * For Buffer DMA mode/Scatter Gather DMA mode +		 * 2 * ((Largest Packet size / 4) + 1 + 1) + n +		 * with n = number of host channel. +		 * 2 * ((1024/4) + 2) = 516 +		 */ +		rxfsiz = 516 + hw->host_channels; + +		/* +		 * min non-periodic tx fifo depth +		 * 2 * (largest non-periodic USB packet used / 4) +		 * 2 * (512/4) = 256 +		 */ +		nptxfsiz = 256; + +		/* +		 * min periodic tx fifo depth +		 * (largest packet size*MC)/4 +		 * (1024 * 3)/4 = 768 +		 */ +		ptxfsiz = 768; + +		params->host_rx_fifo_size = rxfsiz; +		params->host_nperio_tx_fifo_size = nptxfsiz; +		params->host_perio_tx_fifo_size = ptxfsiz; +	} + +	/* +	 * If the summation of RX, NPTX and PTX fifo sizes is still +	 * bigger than the total_fifo_size, then we have a problem. +	 * +	 * We won't be able to allocate as many endpoints. Right now, +	 * we're just printing an error message, but ideally this FIFO +	 * allocation algorithm would be improved in the future. +	 * +	 * FIXME improve this FIFO allocation algorithm. +	 */ +	if (unlikely(total_fifo_size < (rxfsiz + nptxfsiz + ptxfsiz))) +		dev_err(hsotg->dev, "invalid fifo sizes\n"); +} + +static void dwc2_config_fifos(struct dwc2_hsotg *hsotg) +{ +	struct dwc2_core_params *params = hsotg->core_params; +	u32 nptxfsiz, hptxfsiz, dfifocfg, grxfsiz; + +	if (!params->enable_dynamic_fifo) +		return; + +	dwc2_calculate_dynamic_fifo(hsotg); + +	/* Rx FIFO */ +	grxfsiz = readl(hsotg->regs + GRXFSIZ); +	dev_dbg(hsotg->dev, "initial grxfsiz=%08x\n", grxfsiz); +	grxfsiz &= ~GRXFSIZ_DEPTH_MASK; +	grxfsiz |= params->host_rx_fifo_size << +		   GRXFSIZ_DEPTH_SHIFT & GRXFSIZ_DEPTH_MASK; +	writel(grxfsiz, hsotg->regs + GRXFSIZ); +	dev_dbg(hsotg->dev, "new grxfsiz=%08x\n", readl(hsotg->regs + GRXFSIZ)); + +	/* Non-periodic Tx FIFO */ +	dev_dbg(hsotg->dev, "initial gnptxfsiz=%08x\n", +		readl(hsotg->regs + GNPTXFSIZ)); +	nptxfsiz = params->host_nperio_tx_fifo_size << +		   FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; +	nptxfsiz |= params->host_rx_fifo_size << +		    FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; +	writel(nptxfsiz, hsotg->regs + GNPTXFSIZ); +	dev_dbg(hsotg->dev, "new gnptxfsiz=%08x\n", +		readl(hsotg->regs + GNPTXFSIZ)); + +	/* Periodic Tx FIFO */ +	dev_dbg(hsotg->dev, "initial hptxfsiz=%08x\n", +		readl(hsotg->regs + HPTXFSIZ)); +	hptxfsiz = params->host_perio_tx_fifo_size << +		   FIFOSIZE_DEPTH_SHIFT & FIFOSIZE_DEPTH_MASK; +	hptxfsiz |= (params->host_rx_fifo_size + +		     params->host_nperio_tx_fifo_size) << +		    FIFOSIZE_STARTADDR_SHIFT & FIFOSIZE_STARTADDR_MASK; +	writel(hptxfsiz, hsotg->regs + HPTXFSIZ); +	dev_dbg(hsotg->dev, "new hptxfsiz=%08x\n", +		readl(hsotg->regs + HPTXFSIZ)); + +	if (hsotg->core_params->en_multiple_tx_fifo > 0 && +	    hsotg->hw_params.snpsid <= DWC2_CORE_REV_2_94a) { +		/* +		 * Global DFIFOCFG calculation for Host mode - +		 * include RxFIFO, NPTXFIFO and HPTXFIFO +		 */ +		dfifocfg = readl(hsotg->regs + GDFIFOCFG); +		dfifocfg &= ~GDFIFOCFG_EPINFOBASE_MASK; +		dfifocfg |= (params->host_rx_fifo_size + +			     params->host_nperio_tx_fifo_size + +			     params->host_perio_tx_fifo_size) << +			    GDFIFOCFG_EPINFOBASE_SHIFT & +			    GDFIFOCFG_EPINFOBASE_MASK; +		writel(dfifocfg, hsotg->regs + GDFIFOCFG); +	} +} + +/** + * dwc2_core_host_init() - Initializes the DWC_otg controller registers for + * Host mode + * + * @hsotg: Programming view of DWC_otg controller + * + * This function flushes the Tx and Rx FIFOs and flushes any entries in the + * request queues. Host channels are reset to ensure that they are ready for + * performing transfers. + */ +void dwc2_core_host_init(struct dwc2_hsotg *hsotg) +{ +	u32 hcfg, hfir, otgctl; + +	dev_dbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + +	/* Restart the Phy Clock */ +	writel(0, hsotg->regs + PCGCTL); + +	/* Initialize Host Configuration Register */ +	dwc2_init_fs_ls_pclk_sel(hsotg); +	if (hsotg->core_params->speed == DWC2_SPEED_PARAM_FULL) { +		hcfg = readl(hsotg->regs + HCFG); +		hcfg |= HCFG_FSLSSUPP; +		writel(hcfg, hsotg->regs + HCFG); +	} + +	/* +	 * This bit allows dynamic reloading of the HFIR register during +	 * runtime. This bit needs to be programmed during initial configuration +	 * and its value must not be changed during runtime. +	 */ +	if (hsotg->core_params->reload_ctl > 0) { +		hfir = readl(hsotg->regs + HFIR); +		hfir |= HFIR_RLDCTRL; +		writel(hfir, hsotg->regs + HFIR); +	} + +	if (hsotg->core_params->dma_desc_enable > 0) { +		u32 op_mode = hsotg->hw_params.op_mode; +		if (hsotg->hw_params.snpsid < DWC2_CORE_REV_2_90a || +		    !hsotg->hw_params.dma_desc_enable || +		    op_mode == GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE || +		    op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE || +		    op_mode == GHWCFG2_OP_MODE_UNDEFINED) { +			dev_err(hsotg->dev, +				"Hardware does not support descriptor DMA mode -\n"); +			dev_err(hsotg->dev, +				"falling back to buffer DMA mode.\n"); +			hsotg->core_params->dma_desc_enable = 0; +		} else { +			hcfg = readl(hsotg->regs + HCFG); +			hcfg |= HCFG_DESCDMA; +			writel(hcfg, hsotg->regs + HCFG); +		} +	} + +	/* Configure data FIFO sizes */ +	dwc2_config_fifos(hsotg); + +	/* TODO - check this */ +	/* Clear Host Set HNP Enable in the OTG Control Register */ +	otgctl = readl(hsotg->regs + GOTGCTL); +	otgctl &= ~GOTGCTL_HSTSETHNPEN; +	writel(otgctl, hsotg->regs + GOTGCTL); + +	/* Make sure the FIFOs are flushed */ +	dwc2_flush_tx_fifo(hsotg, 0x10 /* all TX FIFOs */); +	dwc2_flush_rx_fifo(hsotg); + +	/* Clear Host Set HNP Enable in the OTG Control Register */ +	otgctl = readl(hsotg->regs + GOTGCTL); +	otgctl &= ~GOTGCTL_HSTSETHNPEN; +	writel(otgctl, hsotg->regs + GOTGCTL); + +	if (hsotg->core_params->dma_desc_enable <= 0) { +		int num_channels, i; +		u32 hcchar; + +		/* Flush out any leftover queued requests */ +		num_channels = hsotg->core_params->host_channels; +		for (i = 0; i < num_channels; i++) { +			hcchar = readl(hsotg->regs + HCCHAR(i)); +			hcchar &= ~HCCHAR_CHENA; +			hcchar |= HCCHAR_CHDIS; +			hcchar &= ~HCCHAR_EPDIR; +			writel(hcchar, hsotg->regs + HCCHAR(i)); +		} + +		/* Halt all channels to put them into a known state */ +		for (i = 0; i < num_channels; i++) { +			int count = 0; + +			hcchar = readl(hsotg->regs + HCCHAR(i)); +			hcchar |= HCCHAR_CHENA | HCCHAR_CHDIS; +			hcchar &= ~HCCHAR_EPDIR; +			writel(hcchar, hsotg->regs + HCCHAR(i)); +			dev_dbg(hsotg->dev, "%s: Halt channel %d\n", +				__func__, i); +			do { +				hcchar = readl(hsotg->regs + HCCHAR(i)); +				if (++count > 1000) { +					dev_err(hsotg->dev, +						"Unable to clear enable on channel %d\n", +						i); +					break; +				} +				udelay(1); +			} while (hcchar & HCCHAR_CHENA); +		} +	} + +	/* Turn on the vbus power */ +	dev_dbg(hsotg->dev, "Init: Port Power? op_state=%d\n", hsotg->op_state); +	if (hsotg->op_state == OTG_STATE_A_HOST) { +		u32 hprt0 = dwc2_read_hprt0(hsotg); + +		dev_dbg(hsotg->dev, "Init: Power Port (%d)\n", +			!!(hprt0 & HPRT0_PWR)); +		if (!(hprt0 & HPRT0_PWR)) { +			hprt0 |= HPRT0_PWR; +			writel(hprt0, hsotg->regs + HPRT0); +		} +	} + +	dwc2_enable_host_interrupts(hsotg); +} + +static void dwc2_hc_enable_slave_ints(struct dwc2_hsotg *hsotg, +				      struct dwc2_host_chan *chan) +{ +	u32 hcintmsk = HCINTMSK_CHHLTD; + +	switch (chan->ep_type) { +	case USB_ENDPOINT_XFER_CONTROL: +	case USB_ENDPOINT_XFER_BULK: +		dev_vdbg(hsotg->dev, "control/bulk\n"); +		hcintmsk |= HCINTMSK_XFERCOMPL; +		hcintmsk |= HCINTMSK_STALL; +		hcintmsk |= HCINTMSK_XACTERR; +		hcintmsk |= HCINTMSK_DATATGLERR; +		if (chan->ep_is_in) { +			hcintmsk |= HCINTMSK_BBLERR; +		} else { +			hcintmsk |= HCINTMSK_NAK; +			hcintmsk |= HCINTMSK_NYET; +			if (chan->do_ping) +				hcintmsk |= HCINTMSK_ACK; +		} + +		if (chan->do_split) { +			hcintmsk |= HCINTMSK_NAK; +			if (chan->complete_split) +				hcintmsk |= HCINTMSK_NYET; +			else +				hcintmsk |= HCINTMSK_ACK; +		} + +		if (chan->error_state) +			hcintmsk |= HCINTMSK_ACK; +		break; + +	case USB_ENDPOINT_XFER_INT: +		if (dbg_perio()) +			dev_vdbg(hsotg->dev, "intr\n"); +		hcintmsk |= HCINTMSK_XFERCOMPL; +		hcintmsk |= HCINTMSK_NAK; +		hcintmsk |= HCINTMSK_STALL; +		hcintmsk |= HCINTMSK_XACTERR; +		hcintmsk |= HCINTMSK_DATATGLERR; +		hcintmsk |= HCINTMSK_FRMOVRUN; + +		if (chan->ep_is_in) +			hcintmsk |= HCINTMSK_BBLERR; +		if (chan->error_state) +			hcintmsk |= HCINTMSK_ACK; +		if (chan->do_split) { +			if (chan->complete_split) +				hcintmsk |= HCINTMSK_NYET; +			else +				hcintmsk |= HCINTMSK_ACK; +		} +		break; + +	case USB_ENDPOINT_XFER_ISOC: +		if (dbg_perio()) +			dev_vdbg(hsotg->dev, "isoc\n"); +		hcintmsk |= HCINTMSK_XFERCOMPL; +		hcintmsk |= HCINTMSK_FRMOVRUN; +		hcintmsk |= HCINTMSK_ACK; + +		if (chan->ep_is_in) { +			hcintmsk |= HCINTMSK_XACTERR; +			hcintmsk |= HCINTMSK_BBLERR; +		} +		break; +	default: +		dev_err(hsotg->dev, "## Unknown EP type ##\n"); +		break; +	} + +	writel(hcintmsk, hsotg->regs + HCINTMSK(chan->hc_num)); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); +} + +static void dwc2_hc_enable_dma_ints(struct dwc2_hsotg *hsotg, +				    struct dwc2_host_chan *chan) +{ +	u32 hcintmsk = HCINTMSK_CHHLTD; + +	/* +	 * For Descriptor DMA mode core halts the channel on AHB error. +	 * Interrupt is not required. +	 */ +	if (hsotg->core_params->dma_desc_enable <= 0) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "desc DMA disabled\n"); +		hcintmsk |= HCINTMSK_AHBERR; +	} else { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "desc DMA enabled\n"); +		if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) +			hcintmsk |= HCINTMSK_XFERCOMPL; +	} + +	if (chan->error_state && !chan->do_split && +	    chan->ep_type != USB_ENDPOINT_XFER_ISOC) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "setting ACK\n"); +		hcintmsk |= HCINTMSK_ACK; +		if (chan->ep_is_in) { +			hcintmsk |= HCINTMSK_DATATGLERR; +			if (chan->ep_type != USB_ENDPOINT_XFER_INT) +				hcintmsk |= HCINTMSK_NAK; +		} +	} + +	writel(hcintmsk, hsotg->regs + HCINTMSK(chan->hc_num)); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "set HCINTMSK to %08x\n", hcintmsk); +} + +static void dwc2_hc_enable_ints(struct dwc2_hsotg *hsotg, +				struct dwc2_host_chan *chan) +{ +	u32 intmsk; + +	if (hsotg->core_params->dma_enable > 0) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "DMA enabled\n"); +		dwc2_hc_enable_dma_ints(hsotg, chan); +	} else { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "DMA disabled\n"); +		dwc2_hc_enable_slave_ints(hsotg, chan); +	} + +	/* Enable the top level host channel interrupt */ +	intmsk = readl(hsotg->regs + HAINTMSK); +	intmsk |= 1 << chan->hc_num; +	writel(intmsk, hsotg->regs + HAINTMSK); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "set HAINTMSK to %08x\n", intmsk); + +	/* Make sure host channel interrupts are enabled */ +	intmsk = readl(hsotg->regs + GINTMSK); +	intmsk |= GINTSTS_HCHINT; +	writel(intmsk, hsotg->regs + GINTMSK); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "set GINTMSK to %08x\n", intmsk); +} + +/** + * dwc2_hc_init() - Prepares a host channel for transferring packets to/from + * a specific endpoint + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Information needed to initialize the host channel + * + * The HCCHARn register is set up with the characteristics specified in chan. + * Host channel interrupts that may need to be serviced while this transfer is + * in progress are enabled. + */ +void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) +{ +	u8 hc_num = chan->hc_num; +	u32 hcintmsk; +	u32 hcchar; +	u32 hcsplt = 0; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	/* Clear old interrupt conditions for this host channel */ +	hcintmsk = 0xffffffff; +	hcintmsk &= ~HCINTMSK_RESERVED14_31; +	writel(hcintmsk, hsotg->regs + HCINT(hc_num)); + +	/* Enable channel interrupts required for this transfer */ +	dwc2_hc_enable_ints(hsotg, chan); + +	/* +	 * Program the HCCHARn register with the endpoint characteristics for +	 * the current transfer +	 */ +	hcchar = chan->dev_addr << HCCHAR_DEVADDR_SHIFT & HCCHAR_DEVADDR_MASK; +	hcchar |= chan->ep_num << HCCHAR_EPNUM_SHIFT & HCCHAR_EPNUM_MASK; +	if (chan->ep_is_in) +		hcchar |= HCCHAR_EPDIR; +	if (chan->speed == USB_SPEED_LOW) +		hcchar |= HCCHAR_LSPDDEV; +	hcchar |= chan->ep_type << HCCHAR_EPTYPE_SHIFT & HCCHAR_EPTYPE_MASK; +	hcchar |= chan->max_packet << HCCHAR_MPS_SHIFT & HCCHAR_MPS_MASK; +	writel(hcchar, hsotg->regs + HCCHAR(hc_num)); +	if (dbg_hc(chan)) { +		dev_vdbg(hsotg->dev, "set HCCHAR(%d) to %08x\n", +			 hc_num, hcchar); + +		dev_vdbg(hsotg->dev, "%s: Channel %d\n", +			 __func__, hc_num); +		dev_vdbg(hsotg->dev, "	 Dev Addr: %d\n", +			 chan->dev_addr); +		dev_vdbg(hsotg->dev, "	 Ep Num: %d\n", +			 chan->ep_num); +		dev_vdbg(hsotg->dev, "	 Is In: %d\n", +			 chan->ep_is_in); +		dev_vdbg(hsotg->dev, "	 Is Low Speed: %d\n", +			 chan->speed == USB_SPEED_LOW); +		dev_vdbg(hsotg->dev, "	 Ep Type: %d\n", +			 chan->ep_type); +		dev_vdbg(hsotg->dev, "	 Max Pkt: %d\n", +			 chan->max_packet); +	} + +	/* Program the HCSPLT register for SPLITs */ +	if (chan->do_split) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, +				 "Programming HC %d with split --> %s\n", +				 hc_num, +				 chan->complete_split ? "CSPLIT" : "SSPLIT"); +		if (chan->complete_split) +			hcsplt |= HCSPLT_COMPSPLT; +		hcsplt |= chan->xact_pos << HCSPLT_XACTPOS_SHIFT & +			  HCSPLT_XACTPOS_MASK; +		hcsplt |= chan->hub_addr << HCSPLT_HUBADDR_SHIFT & +			  HCSPLT_HUBADDR_MASK; +		hcsplt |= chan->hub_port << HCSPLT_PRTADDR_SHIFT & +			  HCSPLT_PRTADDR_MASK; +		if (dbg_hc(chan)) { +			dev_vdbg(hsotg->dev, "	  comp split %d\n", +				 chan->complete_split); +			dev_vdbg(hsotg->dev, "	  xact pos %d\n", +				 chan->xact_pos); +			dev_vdbg(hsotg->dev, "	  hub addr %d\n", +				 chan->hub_addr); +			dev_vdbg(hsotg->dev, "	  hub port %d\n", +				 chan->hub_port); +			dev_vdbg(hsotg->dev, "	  is_in %d\n", +				 chan->ep_is_in); +			dev_vdbg(hsotg->dev, "	  Max Pkt %d\n", +				 chan->max_packet); +			dev_vdbg(hsotg->dev, "	  xferlen %d\n", +				 chan->xfer_len); +		} +	} + +	writel(hcsplt, hsotg->regs + HCSPLT(hc_num)); +} + +/** + * dwc2_hc_halt() - Attempts to halt a host channel + * + * @hsotg:       Controller register interface + * @chan:        Host channel to halt + * @halt_status: Reason for halting the 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 dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, +		  enum dwc2_halt_status halt_status) +{ +	u32 nptxsts, hptxsts, hcchar; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); +	if (halt_status == DWC2_HC_XFER_NO_HALT_STATUS) +		dev_err(hsotg->dev, "!!! halt_status = %d !!!\n", halt_status); + +	if (halt_status == DWC2_HC_XFER_URB_DEQUEUE || +	    halt_status == DWC2_HC_XFER_AHB_ERR) { +		/* +		 * Disable all channel interrupts except Ch Halted. The QTD +		 * and QH state associated with this transfer has been cleared +		 * (in the case of URB_DEQUEUE), so the channel needs to be +		 * shut down carefully to prevent crashes. +		 */ +		u32 hcintmsk = HCINTMSK_CHHLTD; + +		dev_vdbg(hsotg->dev, "dequeue/error\n"); +		writel(hcintmsk, hsotg->regs + HCINTMSK(chan->hc_num)); + +		/* +		 * Make sure no other interrupts besides halt are currently +		 * pending. Handling another interrupt could cause a crash due +		 * to the QTD and QH state. +		 */ +		writel(~hcintmsk, hsotg->regs + HCINT(chan->hc_num)); + +		/* +		 * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR +		 * even if the channel was already halted for some other +		 * reason +		 */ +		chan->halt_status = halt_status; + +		hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); +		if (!(hcchar & HCCHAR_CHENA)) { +			/* +			 * 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. +			 */ +			return; +		} +	} +	if (chan->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. +		 */ +		dev_vdbg(hsotg->dev, +			 "*** %s: Channel %d, chan->halt_pending already set ***\n", +			 __func__, chan->hc_num); +		return; +	} + +	hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); + +	/* No need to set the bit in DDMA for disabling the channel */ +	/* TODO check it everywhere channel is disabled */ +	if (hsotg->core_params->dma_desc_enable <= 0) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "desc DMA disabled\n"); +		hcchar |= HCCHAR_CHENA; +	} else { +		if (dbg_hc(chan)) +			dev_dbg(hsotg->dev, "desc DMA enabled\n"); +	} +	hcchar |= HCCHAR_CHDIS; + +	if (hsotg->core_params->dma_enable <= 0) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "DMA not enabled\n"); +		hcchar |= HCCHAR_CHENA; + +		/* Check for space in the request queue to issue the halt */ +		if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || +		    chan->ep_type == USB_ENDPOINT_XFER_BULK) { +			dev_vdbg(hsotg->dev, "control/bulk\n"); +			nptxsts = readl(hsotg->regs + GNPTXSTS); +			if ((nptxsts & TXSTS_QSPCAVAIL_MASK) == 0) { +				dev_vdbg(hsotg->dev, "Disabling channel\n"); +				hcchar &= ~HCCHAR_CHENA; +			} +		} else { +			if (dbg_perio()) +				dev_vdbg(hsotg->dev, "isoc/intr\n"); +			hptxsts = readl(hsotg->regs + HPTXSTS); +			if ((hptxsts & TXSTS_QSPCAVAIL_MASK) == 0 || +			    hsotg->queuing_high_bandwidth) { +				if (dbg_perio()) +					dev_vdbg(hsotg->dev, "Disabling channel\n"); +				hcchar &= ~HCCHAR_CHENA; +			} +		} +	} else { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "DMA enabled\n"); +	} + +	writel(hcchar, hsotg->regs + HCCHAR(chan->hc_num)); +	chan->halt_status = halt_status; + +	if (hcchar & HCCHAR_CHENA) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "Channel enabled\n"); +		chan->halt_pending = 1; +		chan->halt_on_queue = 0; +	} else { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "Channel disabled\n"); +		chan->halt_on_queue = 1; +	} + +	if (dbg_hc(chan)) { +		dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, +			 chan->hc_num); +		dev_vdbg(hsotg->dev, "	 hcchar: 0x%08x\n", +			 hcchar); +		dev_vdbg(hsotg->dev, "	 halt_pending: %d\n", +			 chan->halt_pending); +		dev_vdbg(hsotg->dev, "	 halt_on_queue: %d\n", +			 chan->halt_on_queue); +		dev_vdbg(hsotg->dev, "	 halt_status: %d\n", +			 chan->halt_status); +	} +} + +/** + * dwc2_hc_cleanup() - Clears the transfer state for a host channel + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Identifies the host channel to clean up + * + * This function is normally called after a transfer is done and the host + * channel is being released + */ +void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) +{ +	u32 hcintmsk; + +	chan->xfer_started = 0; + +	/* +	 * Clear channel interrupt enables and any unhandled channel interrupt +	 * conditions +	 */ +	writel(0, hsotg->regs + HCINTMSK(chan->hc_num)); +	hcintmsk = 0xffffffff; +	hcintmsk &= ~HCINTMSK_RESERVED14_31; +	writel(hcintmsk, hsotg->regs + HCINT(chan->hc_num)); +} + +/** + * dwc2_hc_set_even_odd_frame() - Sets the channel property that indicates in + * which frame a periodic transfer should occur + * + * @hsotg:  Programming view of DWC_otg controller + * @chan:   Identifies the host channel to set up and its properties + * @hcchar: Current value of the HCCHAR register for the specified host channel + * + * This function has no effect on non-periodic transfers + */ +static void dwc2_hc_set_even_odd_frame(struct dwc2_hsotg *hsotg, +				       struct dwc2_host_chan *chan, u32 *hcchar) +{ +	if (chan->ep_type == USB_ENDPOINT_XFER_INT || +	    chan->ep_type == USB_ENDPOINT_XFER_ISOC) { +		/* 1 if _next_ frame is odd, 0 if it's even */ +		if (!(dwc2_hcd_get_frame_number(hsotg) & 0x1)) +			*hcchar |= HCCHAR_ODDFRM; +	} +} + +static void dwc2_set_pid_isoc(struct dwc2_host_chan *chan) +{ +	/* Set up the initial PID for the transfer */ +	if (chan->speed == USB_SPEED_HIGH) { +		if (chan->ep_is_in) { +			if (chan->multi_count == 1) +				chan->data_pid_start = DWC2_HC_PID_DATA0; +			else if (chan->multi_count == 2) +				chan->data_pid_start = DWC2_HC_PID_DATA1; +			else +				chan->data_pid_start = DWC2_HC_PID_DATA2; +		} else { +			if (chan->multi_count == 1) +				chan->data_pid_start = DWC2_HC_PID_DATA0; +			else +				chan->data_pid_start = DWC2_HC_PID_MDATA; +		} +	} else { +		chan->data_pid_start = DWC2_HC_PID_DATA0; +	} +} + +/** + * dwc2_hc_write_packet() - Writes a packet into the Tx FIFO associated with + * the Host Channel + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Information needed to initialize the host channel + * + * This function should only be called in Slave mode. 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. + * + * Upon return the xfer_buf and xfer_count fields in chan are incremented by + * the number of bytes written to the Tx FIFO. + */ +static void dwc2_hc_write_packet(struct dwc2_hsotg *hsotg, +				 struct dwc2_host_chan *chan) +{ +	u32 i; +	u32 remaining_count; +	u32 byte_count; +	u32 dword_count; +	u32 __iomem *data_fifo; +	u32 *data_buf = (u32 *)chan->xfer_buf; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	data_fifo = (u32 __iomem *)(hsotg->regs + HCFIFO(chan->hc_num)); + +	remaining_count = chan->xfer_len - chan->xfer_count; +	if (remaining_count > chan->max_packet) +		byte_count = chan->max_packet; +	else +		byte_count = remaining_count; + +	dword_count = (byte_count + 3) / 4; + +	if (((unsigned long)data_buf & 0x3) == 0) { +		/* xfer_buf is DWORD aligned */ +		for (i = 0; i < dword_count; i++, data_buf++) +			writel(*data_buf, data_fifo); +	} else { +		/* xfer_buf is not DWORD aligned */ +		for (i = 0; i < dword_count; i++, data_buf++) { +			u32 data = data_buf[0] | data_buf[1] << 8 | +				   data_buf[2] << 16 | data_buf[3] << 24; +			writel(data, data_fifo); +		} +	} + +	chan->xfer_count += byte_count; +	chan->xfer_buf += byte_count; +} + +/** + * dwc2_hc_start_transfer() - Does the setup for a data transfer for a host + * channel and starts the transfer + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Information needed to initialize the host channel. The xfer_len value + *         may be reduced to accommodate the max widths of the XferSize and + *         PktCnt fields in the HCTSIZn register. The multi_count value may be + *         changed to reflect the final xfer_len value. + * + * This function 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 are 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. + */ +void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, +			    struct dwc2_host_chan *chan) +{ +	u32 max_hc_xfer_size = hsotg->core_params->max_transfer_size; +	u16 max_hc_pkt_count = hsotg->core_params->max_packet_count; +	u32 hcchar; +	u32 hctsiz = 0; +	u16 num_packets; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	if (chan->do_ping) { +		if (hsotg->core_params->dma_enable <= 0) { +			if (dbg_hc(chan)) +				dev_vdbg(hsotg->dev, "ping, no DMA\n"); +			dwc2_hc_do_ping(hsotg, chan); +			chan->xfer_started = 1; +			return; +		} else { +			if (dbg_hc(chan)) +				dev_vdbg(hsotg->dev, "ping, DMA\n"); +			hctsiz |= TSIZ_DOPNG; +		} +	} + +	if (chan->do_split) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "split\n"); +		num_packets = 1; + +		if (chan->complete_split && !chan->ep_is_in) +			/* +			 * For CSPLIT OUT Transfer, set the size to 0 so the +			 * core doesn't expect any data written to the FIFO +			 */ +			chan->xfer_len = 0; +		else if (chan->ep_is_in || chan->xfer_len > chan->max_packet) +			chan->xfer_len = chan->max_packet; +		else if (!chan->ep_is_in && chan->xfer_len > 188) +			chan->xfer_len = 188; + +		hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & +			  TSIZ_XFERSIZE_MASK; +	} else { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "no split\n"); +		/* +		 * Ensure that the transfer length and packet count will fit +		 * in the widths allocated for them in the HCTSIZn register +		 */ +		if (chan->ep_type == USB_ENDPOINT_XFER_INT || +		    chan->ep_type == USB_ENDPOINT_XFER_ISOC) { +			/* +			 * 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.) +			 */ +			u32 max_periodic_len = +				chan->multi_count * chan->max_packet; + +			if (chan->xfer_len > max_periodic_len) +				chan->xfer_len = max_periodic_len; +		} else if (chan->xfer_len > max_hc_xfer_size) { +			/* +			 * Make sure that xfer_len is a multiple of max packet +			 * size +			 */ +			chan->xfer_len = +				max_hc_xfer_size - chan->max_packet + 1; +		} + +		if (chan->xfer_len > 0) { +			num_packets = (chan->xfer_len + chan->max_packet - 1) / +					chan->max_packet; +			if (num_packets > max_hc_pkt_count) { +				num_packets = max_hc_pkt_count; +				chan->xfer_len = num_packets * chan->max_packet; +			} +		} else { +			/* Need 1 packet for transfer length of 0 */ +			num_packets = 1; +		} + +		if (chan->ep_is_in) +			/* +			 * Always program an integral # of max packets for IN +			 * transfers +			 */ +			chan->xfer_len = num_packets * chan->max_packet; + +		if (chan->ep_type == USB_ENDPOINT_XFER_INT || +		    chan->ep_type == USB_ENDPOINT_XFER_ISOC) +			/* +			 * Make sure that the multi_count field matches the +			 * actual transfer length +			 */ +			chan->multi_count = num_packets; + +		if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) +			dwc2_set_pid_isoc(chan); + +		hctsiz |= chan->xfer_len << TSIZ_XFERSIZE_SHIFT & +			  TSIZ_XFERSIZE_MASK; +	} + +	chan->start_pkt_count = num_packets; +	hctsiz |= num_packets << TSIZ_PKTCNT_SHIFT & TSIZ_PKTCNT_MASK; +	hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & +		  TSIZ_SC_MC_PID_MASK; +	writel(hctsiz, hsotg->regs + HCTSIZ(chan->hc_num)); +	if (dbg_hc(chan)) { +		dev_vdbg(hsotg->dev, "Wrote %08x to HCTSIZ(%d)\n", +			 hctsiz, chan->hc_num); + +		dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, +			 chan->hc_num); +		dev_vdbg(hsotg->dev, "	 Xfer Size: %d\n", +			 (hctsiz & TSIZ_XFERSIZE_MASK) >> +			 TSIZ_XFERSIZE_SHIFT); +		dev_vdbg(hsotg->dev, "	 Num Pkts: %d\n", +			 (hctsiz & TSIZ_PKTCNT_MASK) >> +			 TSIZ_PKTCNT_SHIFT); +		dev_vdbg(hsotg->dev, "	 Start PID: %d\n", +			 (hctsiz & TSIZ_SC_MC_PID_MASK) >> +			 TSIZ_SC_MC_PID_SHIFT); +	} + +	if (hsotg->core_params->dma_enable > 0) { +		dma_addr_t dma_addr; + +		if (chan->align_buf) { +			if (dbg_hc(chan)) +				dev_vdbg(hsotg->dev, "align_buf\n"); +			dma_addr = chan->align_buf; +		} else { +			dma_addr = chan->xfer_dma; +		} +		writel((u32)dma_addr, hsotg->regs + HCDMA(chan->hc_num)); +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "Wrote %08lx to HCDMA(%d)\n", +				 (unsigned long)dma_addr, chan->hc_num); +	} + +	/* Start the split */ +	if (chan->do_split) { +		u32 hcsplt = readl(hsotg->regs + HCSPLT(chan->hc_num)); + +		hcsplt |= HCSPLT_SPLTENA; +		writel(hcsplt, hsotg->regs + HCSPLT(chan->hc_num)); +	} + +	hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); +	hcchar &= ~HCCHAR_MULTICNT_MASK; +	hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & +		  HCCHAR_MULTICNT_MASK; +	dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); + +	if (hcchar & HCCHAR_CHDIS) +		dev_warn(hsotg->dev, +			 "%s: chdis set, channel %d, hcchar 0x%08x\n", +			 __func__, chan->hc_num, hcchar); + +	/* Set host channel enable after all other setup is complete */ +	hcchar |= HCCHAR_CHENA; +	hcchar &= ~HCCHAR_CHDIS; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "	 Multi Cnt: %d\n", +			 (hcchar & HCCHAR_MULTICNT_MASK) >> +			 HCCHAR_MULTICNT_SHIFT); + +	writel(hcchar, hsotg->regs + HCCHAR(chan->hc_num)); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, +			 chan->hc_num); + +	chan->xfer_started = 1; +	chan->requests++; + +	if (hsotg->core_params->dma_enable <= 0 && +	    !chan->ep_is_in && chan->xfer_len > 0) +		/* Load OUT packet into the appropriate Tx FIFO */ +		dwc2_hc_write_packet(hsotg, chan); +} + +/** + * dwc2_hc_start_transfer_ddma() - Does the setup for a data transfer for a + * host channel and starts the transfer in Descriptor DMA mode + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Information needed to initialize the host channel + * + * Initializes HCTSIZ register. For a PING transfer the Do Ping bit is set. + * Sets PID and NTD values. For periodic transfers initializes SCHED_INFO field + * with micro-frame bitmap. + * + * Initializes HCDMA register with descriptor list address and CTD value then + * starts the transfer via enabling the channel. + */ +void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, +				 struct dwc2_host_chan *chan) +{ +	u32 hcchar; +	u32 hc_dma; +	u32 hctsiz = 0; + +	if (chan->do_ping) +		hctsiz |= TSIZ_DOPNG; + +	if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) +		dwc2_set_pid_isoc(chan); + +	/* Packet Count and Xfer Size are not used in Descriptor DMA mode */ +	hctsiz |= chan->data_pid_start << TSIZ_SC_MC_PID_SHIFT & +		  TSIZ_SC_MC_PID_MASK; + +	/* 0 - 1 descriptor, 1 - 2 descriptors, etc */ +	hctsiz |= (chan->ntd - 1) << TSIZ_NTD_SHIFT & TSIZ_NTD_MASK; + +	/* Non-zero only for high-speed interrupt endpoints */ +	hctsiz |= chan->schinfo << TSIZ_SCHINFO_SHIFT & TSIZ_SCHINFO_MASK; + +	if (dbg_hc(chan)) { +		dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, +			 chan->hc_num); +		dev_vdbg(hsotg->dev, "	 Start PID: %d\n", +			 chan->data_pid_start); +		dev_vdbg(hsotg->dev, "	 NTD: %d\n", chan->ntd - 1); +	} + +	writel(hctsiz, hsotg->regs + HCTSIZ(chan->hc_num)); + +	hc_dma = (u32)chan->desc_list_addr & HCDMA_DMA_ADDR_MASK; + +	/* Always start from first descriptor */ +	hc_dma &= ~HCDMA_CTD_MASK; +	writel(hc_dma, hsotg->regs + HCDMA(chan->hc_num)); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "Wrote %08x to HCDMA(%d)\n", +			 hc_dma, chan->hc_num); + +	hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); +	hcchar &= ~HCCHAR_MULTICNT_MASK; +	hcchar |= chan->multi_count << HCCHAR_MULTICNT_SHIFT & +		  HCCHAR_MULTICNT_MASK; + +	if (hcchar & HCCHAR_CHDIS) +		dev_warn(hsotg->dev, +			 "%s: chdis set, channel %d, hcchar 0x%08x\n", +			 __func__, chan->hc_num, hcchar); + +	/* Set host channel enable after all other setup is complete */ +	hcchar |= HCCHAR_CHENA; +	hcchar &= ~HCCHAR_CHDIS; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "	 Multi Cnt: %d\n", +			 (hcchar & HCCHAR_MULTICNT_MASK) >> +			 HCCHAR_MULTICNT_SHIFT); + +	writel(hcchar, hsotg->regs + HCCHAR(chan->hc_num)); +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "Wrote %08x to HCCHAR(%d)\n", hcchar, +			 chan->hc_num); + +	chan->xfer_started = 1; +	chan->requests++; +} + +/** + * dwc2_hc_continue_transfer() - Continues a data transfer that was started by + * a previous call to dwc2_hc_start_transfer() + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Information needed to initialize the host channel + * + * 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. + * + * Return: 1 if a new request is queued, 0 if no more requests are required + * for this transfer + */ +int dwc2_hc_continue_transfer(struct dwc2_hsotg *hsotg, +			      struct dwc2_host_chan *chan) +{ +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, +			 chan->hc_num); + +	if (chan->do_split) +		/* SPLITs always queue just once per channel */ +		return 0; + +	if (chan->data_pid_start == DWC2_HC_PID_SETUP) +		/* SETUPs are queued only once since they can't be NAK'd */ +		return 0; + +	if (chan->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 NAK'd request. Instead, IN requests +		 * are issued each time this function is called. When the +		 * transfer completes, the extra requests for the channel will +		 * be flushed. +		 */ +		u32 hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); + +		dwc2_hc_set_even_odd_frame(hsotg, chan, &hcchar); +		hcchar |= HCCHAR_CHENA; +		hcchar &= ~HCCHAR_CHDIS; +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "	 IN xfer: hcchar = 0x%08x\n", +				 hcchar); +		writel(hcchar, hsotg->regs + HCCHAR(chan->hc_num)); +		chan->requests++; +		return 1; +	} + +	/* OUT transfers */ + +	if (chan->xfer_count < chan->xfer_len) { +		if (chan->ep_type == USB_ENDPOINT_XFER_INT || +		    chan->ep_type == USB_ENDPOINT_XFER_ISOC) { +			u32 hcchar = readl(hsotg->regs + +					   HCCHAR(chan->hc_num)); + +			dwc2_hc_set_even_odd_frame(hsotg, chan, +						   &hcchar); +		} + +		/* Load OUT packet into the appropriate Tx FIFO */ +		dwc2_hc_write_packet(hsotg, chan); +		chan->requests++; +		return 1; +	} + +	return 0; +} + +/** + * dwc2_hc_do_ping() - Starts a PING transfer + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Information needed to initialize the host channel + * + * 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 dwc2_hc_do_ping(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan) +{ +	u32 hcchar; +	u32 hctsiz; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s: Channel %d\n", __func__, +			 chan->hc_num); + + +	hctsiz = TSIZ_DOPNG; +	hctsiz |= 1 << TSIZ_PKTCNT_SHIFT; +	writel(hctsiz, hsotg->regs + HCTSIZ(chan->hc_num)); + +	hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); +	hcchar |= HCCHAR_CHENA; +	hcchar &= ~HCCHAR_CHDIS; +	writel(hcchar, hsotg->regs + HCCHAR(chan->hc_num)); +} + +/** + * dwc2_calc_frame_interval() - Calculates the correct frame Interval value for + * the HFIR register according to PHY type and speed + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: The caller can modify the value of the HFIR register only after the + * Port Enable bit of the Host Port Control and Status register (HPRT.EnaPort) + * has been set + */ +u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg) +{ +	u32 usbcfg; +	u32 hprt0; +	int clock = 60;	/* default value */ + +	usbcfg = readl(hsotg->regs + GUSBCFG); +	hprt0 = readl(hsotg->regs + HPRT0); + +	if (!(usbcfg & GUSBCFG_PHYSEL) && (usbcfg & GUSBCFG_ULPI_UTMI_SEL) && +	    !(usbcfg & GUSBCFG_PHYIF16)) +		clock = 60; +	if ((usbcfg & GUSBCFG_PHYSEL) && hsotg->hw_params.fs_phy_type == +	    GHWCFG2_FS_PHY_TYPE_SHARED_ULPI) +		clock = 48; +	if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && +	    !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) +		clock = 30; +	if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && +	    !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && !(usbcfg & GUSBCFG_PHYIF16)) +		clock = 60; +	if ((usbcfg & GUSBCFG_PHY_LP_CLK_SEL) && !(usbcfg & GUSBCFG_PHYSEL) && +	    !(usbcfg & GUSBCFG_ULPI_UTMI_SEL) && (usbcfg & GUSBCFG_PHYIF16)) +		clock = 48; +	if ((usbcfg & GUSBCFG_PHYSEL) && !(usbcfg & GUSBCFG_PHYIF16) && +	    hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_SHARED_UTMI) +		clock = 48; +	if ((usbcfg & GUSBCFG_PHYSEL) && +	    hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) +		clock = 48; + +	if ((hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT == HPRT0_SPD_HIGH_SPEED) +		/* High speed case */ +		return 125 * clock; +	else +		/* FS/LS case */ +		return 1000 * clock; +} + +/** + * dwc2_read_packet() - Reads a packet from the Rx FIFO into the destination + * buffer + * + * @core_if: Programming view of DWC_otg controller + * @dest:    Destination buffer for the packet + * @bytes:   Number of bytes to copy to the destination + */ +void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes) +{ +	u32 __iomem *fifo = hsotg->regs + HCFIFO(0); +	u32 *data_buf = (u32 *)dest; +	int word_count = (bytes + 3) / 4; +	int i; + +	/* +	 * Todo: Account for the case where dest is not dword aligned. This +	 * requires reading data from the FIFO into a u32 temp buffer, then +	 * moving it into the data buffer. +	 */ + +	dev_vdbg(hsotg->dev, "%s(%p,%p,%d)\n", __func__, hsotg, dest, bytes); + +	for (i = 0; i < word_count; i++, data_buf++) +		*data_buf = readl(fifo); +} + +/** + * dwc2_dump_host_registers() - Prints the host registers + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG +	u32 __iomem *addr; +	int i; + +	dev_dbg(hsotg->dev, "Host Global Registers\n"); +	addr = hsotg->regs + HCFG; +	dev_dbg(hsotg->dev, "HCFG	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + HFIR; +	dev_dbg(hsotg->dev, "HFIR	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + HFNUM; +	dev_dbg(hsotg->dev, "HFNUM	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + HPTXSTS; +	dev_dbg(hsotg->dev, "HPTXSTS	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + HAINT; +	dev_dbg(hsotg->dev, "HAINT	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + HAINTMSK; +	dev_dbg(hsotg->dev, "HAINTMSK	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	if (hsotg->core_params->dma_desc_enable > 0) { +		addr = hsotg->regs + HFLBADDR; +		dev_dbg(hsotg->dev, "HFLBADDR @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +	} + +	addr = hsotg->regs + HPRT0; +	dev_dbg(hsotg->dev, "HPRT0	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); + +	for (i = 0; i < hsotg->core_params->host_channels; i++) { +		dev_dbg(hsotg->dev, "Host Channel %d Specific Registers\n", i); +		addr = hsotg->regs + HCCHAR(i); +		dev_dbg(hsotg->dev, "HCCHAR	 @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +		addr = hsotg->regs + HCSPLT(i); +		dev_dbg(hsotg->dev, "HCSPLT	 @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +		addr = hsotg->regs + HCINT(i); +		dev_dbg(hsotg->dev, "HCINT	 @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +		addr = hsotg->regs + HCINTMSK(i); +		dev_dbg(hsotg->dev, "HCINTMSK	 @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +		addr = hsotg->regs + HCTSIZ(i); +		dev_dbg(hsotg->dev, "HCTSIZ	 @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +		addr = hsotg->regs + HCDMA(i); +		dev_dbg(hsotg->dev, "HCDMA	 @0x%08lX : 0x%08X\n", +			(unsigned long)addr, readl(addr)); +		if (hsotg->core_params->dma_desc_enable > 0) { +			addr = hsotg->regs + HCDMAB(i); +			dev_dbg(hsotg->dev, "HCDMAB	 @0x%08lX : 0x%08X\n", +				(unsigned long)addr, readl(addr)); +		} +	} +#endif +} + +/** + * dwc2_dump_global_registers() - Prints the core global registers + * + * @hsotg: Programming view of DWC_otg controller + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG +	u32 __iomem *addr; + +	dev_dbg(hsotg->dev, "Core Global Registers\n"); +	addr = hsotg->regs + GOTGCTL; +	dev_dbg(hsotg->dev, "GOTGCTL	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GOTGINT; +	dev_dbg(hsotg->dev, "GOTGINT	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GAHBCFG; +	dev_dbg(hsotg->dev, "GAHBCFG	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GUSBCFG; +	dev_dbg(hsotg->dev, "GUSBCFG	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GRSTCTL; +	dev_dbg(hsotg->dev, "GRSTCTL	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GINTSTS; +	dev_dbg(hsotg->dev, "GINTSTS	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GINTMSK; +	dev_dbg(hsotg->dev, "GINTMSK	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GRXSTSR; +	dev_dbg(hsotg->dev, "GRXSTSR	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GRXFSIZ; +	dev_dbg(hsotg->dev, "GRXFSIZ	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GNPTXFSIZ; +	dev_dbg(hsotg->dev, "GNPTXFSIZ	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GNPTXSTS; +	dev_dbg(hsotg->dev, "GNPTXSTS	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GI2CCTL; +	dev_dbg(hsotg->dev, "GI2CCTL	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GPVNDCTL; +	dev_dbg(hsotg->dev, "GPVNDCTL	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GGPIO; +	dev_dbg(hsotg->dev, "GGPIO	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GUID; +	dev_dbg(hsotg->dev, "GUID	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GSNPSID; +	dev_dbg(hsotg->dev, "GSNPSID	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GHWCFG1; +	dev_dbg(hsotg->dev, "GHWCFG1	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GHWCFG2; +	dev_dbg(hsotg->dev, "GHWCFG2	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GHWCFG3; +	dev_dbg(hsotg->dev, "GHWCFG3	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GHWCFG4; +	dev_dbg(hsotg->dev, "GHWCFG4	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GLPMCFG; +	dev_dbg(hsotg->dev, "GLPMCFG	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GPWRDN; +	dev_dbg(hsotg->dev, "GPWRDN	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + GDFIFOCFG; +	dev_dbg(hsotg->dev, "GDFIFOCFG	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +	addr = hsotg->regs + HPTXFSIZ; +	dev_dbg(hsotg->dev, "HPTXFSIZ	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); + +	addr = hsotg->regs + PCGCTL; +	dev_dbg(hsotg->dev, "PCGCTL	 @0x%08lX : 0x%08X\n", +		(unsigned long)addr, readl(addr)); +#endif +} + +/** + * dwc2_flush_tx_fifo() - Flushes a Tx FIFO + * + * @hsotg: Programming view of DWC_otg controller + * @num:   Tx FIFO to flush + */ +void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num) +{ +	u32 greset; +	int count = 0; + +	dev_vdbg(hsotg->dev, "Flush Tx FIFO %d\n", num); + +	greset = GRSTCTL_TXFFLSH; +	greset |= num << GRSTCTL_TXFNUM_SHIFT & GRSTCTL_TXFNUM_MASK; +	writel(greset, hsotg->regs + GRSTCTL); + +	do { +		greset = readl(hsotg->regs + GRSTCTL); +		if (++count > 10000) { +			dev_warn(hsotg->dev, +				 "%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n", +				 __func__, greset, +				 readl(hsotg->regs + GNPTXSTS)); +			break; +		} +		udelay(1); +	} while (greset & GRSTCTL_TXFFLSH); + +	/* Wait for at least 3 PHY Clocks */ +	udelay(1); +} + +/** + * dwc2_flush_rx_fifo() - Flushes the Rx FIFO + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg) +{ +	u32 greset; +	int count = 0; + +	dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	greset = GRSTCTL_RXFFLSH; +	writel(greset, hsotg->regs + GRSTCTL); + +	do { +		greset = readl(hsotg->regs + GRSTCTL); +		if (++count > 10000) { +			dev_warn(hsotg->dev, "%s() HANG! GRSTCTL=%0x\n", +				 __func__, greset); +			break; +		} +		udelay(1); +	} while (greset & GRSTCTL_RXFFLSH); + +	/* Wait for at least 3 PHY Clocks */ +	udelay(1); +} + +#define DWC2_OUT_OF_BOUNDS(a, b, c)	((a) < (b) || (a) > (c)) + +/* Parameter access functions */ +void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	switch (val) { +	case DWC2_CAP_PARAM_HNP_SRP_CAPABLE: +		if (hsotg->hw_params.op_mode != GHWCFG2_OP_MODE_HNP_SRP_CAPABLE) +			valid = 0; +		break; +	case DWC2_CAP_PARAM_SRP_ONLY_CAPABLE: +		switch (hsotg->hw_params.op_mode) { +		case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: +		case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: +		case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: +		case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: +			break; +		default: +			valid = 0; +			break; +		} +		break; +	case DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE: +		/* always valid */ +		break; +	default: +		valid = 0; +		break; +	} + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for otg_cap parameter. Check HW configuration.\n", +				val); +		switch (hsotg->hw_params.op_mode) { +		case GHWCFG2_OP_MODE_HNP_SRP_CAPABLE: +			val = DWC2_CAP_PARAM_HNP_SRP_CAPABLE; +			break; +		case GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE: +		case GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE: +		case GHWCFG2_OP_MODE_SRP_CAPABLE_HOST: +			val = DWC2_CAP_PARAM_SRP_ONLY_CAPABLE; +			break; +		default: +			val = DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE; +			break; +		} +		dev_dbg(hsotg->dev, "Setting otg_cap to %d\n", val); +	} + +	hsotg->core_params->otg_cap = val; +} + +void dwc2_set_param_dma_enable(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val > 0 && hsotg->hw_params.arch == GHWCFG2_SLAVE_ONLY_ARCH) +		valid = 0; +	if (val < 0) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for dma_enable parameter. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.arch != GHWCFG2_SLAVE_ONLY_ARCH; +		dev_dbg(hsotg->dev, "Setting dma_enable to %d\n", val); +	} + +	hsotg->core_params->dma_enable = val; +} + +void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val > 0 && (hsotg->core_params->dma_enable <= 0 || +			!hsotg->hw_params.dma_desc_enable)) +		valid = 0; +	if (val < 0) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for dma_desc_enable parameter. Check HW configuration.\n", +				val); +		val = (hsotg->core_params->dma_enable > 0 && +			hsotg->hw_params.dma_desc_enable); +		dev_dbg(hsotg->dev, "Setting dma_desc_enable to %d\n", val); +	} + +	hsotg->core_params->dma_desc_enable = val; +} + +void dwc2_set_param_host_support_fs_ls_low_power(struct dwc2_hsotg *hsotg, +						 int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"Wrong value for host_support_fs_low_power\n"); +			dev_err(hsotg->dev, +				"host_support_fs_low_power must be 0 or 1\n"); +		} +		val = 0; +		dev_dbg(hsotg->dev, +			"Setting host_support_fs_low_power to %d\n", val); +	} + +	hsotg->core_params->host_support_fs_ls_low_power = val; +} + +void dwc2_set_param_enable_dynamic_fifo(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val > 0 && !hsotg->hw_params.enable_dynamic_fifo) +		valid = 0; +	if (val < 0) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for enable_dynamic_fifo parameter. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.enable_dynamic_fifo; +		dev_dbg(hsotg->dev, "Setting enable_dynamic_fifo to %d\n", val); +	} + +	hsotg->core_params->enable_dynamic_fifo = val; +} + +void dwc2_set_param_host_rx_fifo_size(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val < 16 || val > hsotg->hw_params.host_rx_fifo_size) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for host_rx_fifo_size. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.host_rx_fifo_size; +		dev_dbg(hsotg->dev, "Setting host_rx_fifo_size to %d\n", val); +	} + +	hsotg->core_params->host_rx_fifo_size = val; +} + +void dwc2_set_param_host_nperio_tx_fifo_size(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val < 16 || val > hsotg->hw_params.host_nperio_tx_fifo_size) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for host_nperio_tx_fifo_size. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.host_nperio_tx_fifo_size; +		dev_dbg(hsotg->dev, "Setting host_nperio_tx_fifo_size to %d\n", +			val); +	} + +	hsotg->core_params->host_nperio_tx_fifo_size = val; +} + +void dwc2_set_param_host_perio_tx_fifo_size(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val < 16 || val > hsotg->hw_params.host_perio_tx_fifo_size) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for host_perio_tx_fifo_size. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.host_perio_tx_fifo_size; +		dev_dbg(hsotg->dev, "Setting host_perio_tx_fifo_size to %d\n", +			val); +	} + +	hsotg->core_params->host_perio_tx_fifo_size = val; +} + +void dwc2_set_param_max_transfer_size(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val < 2047 || val > hsotg->hw_params.max_transfer_size) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for max_transfer_size. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.max_transfer_size; +		dev_dbg(hsotg->dev, "Setting max_transfer_size to %d\n", val); +	} + +	hsotg->core_params->max_transfer_size = val; +} + +void dwc2_set_param_max_packet_count(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val < 15 || val > hsotg->hw_params.max_packet_count) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for max_packet_count. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.max_packet_count; +		dev_dbg(hsotg->dev, "Setting max_packet_count to %d\n", val); +	} + +	hsotg->core_params->max_packet_count = val; +} + +void dwc2_set_param_host_channels(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (val < 1 || val > hsotg->hw_params.host_channels) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for host_channels. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.host_channels; +		dev_dbg(hsotg->dev, "Setting host_channels to %d\n", val); +	} + +	hsotg->core_params->host_channels = val; +} + +void dwc2_set_param_phy_type(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 0; +	u32 hs_phy_type, fs_phy_type; + +	if (DWC2_OUT_OF_BOUNDS(val, DWC2_PHY_TYPE_PARAM_FS, +			       DWC2_PHY_TYPE_PARAM_ULPI)) { +		if (val >= 0) { +			dev_err(hsotg->dev, "Wrong value for phy_type\n"); +			dev_err(hsotg->dev, "phy_type must be 0, 1 or 2\n"); +		} + +		valid = 0; +	} + +	hs_phy_type = hsotg->hw_params.hs_phy_type; +	fs_phy_type = hsotg->hw_params.fs_phy_type; +	if (val == DWC2_PHY_TYPE_PARAM_UTMI && +	    (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI || +	     hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) +		valid = 1; +	else if (val == DWC2_PHY_TYPE_PARAM_ULPI && +		 (hs_phy_type == GHWCFG2_HS_PHY_TYPE_ULPI || +		  hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI)) +		valid = 1; +	else if (val == DWC2_PHY_TYPE_PARAM_FS && +		 fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) +		valid = 1; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for phy_type. Check HW configuration.\n", +				val); +		val = DWC2_PHY_TYPE_PARAM_FS; +		if (hs_phy_type != GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED) { +			if (hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI || +			    hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI_ULPI) +				val = DWC2_PHY_TYPE_PARAM_UTMI; +			else +				val = DWC2_PHY_TYPE_PARAM_ULPI; +		} +		dev_dbg(hsotg->dev, "Setting phy_type to %d\n", val); +	} + +	hsotg->core_params->phy_type = val; +} + +static int dwc2_get_param_phy_type(struct dwc2_hsotg *hsotg) +{ +	return hsotg->core_params->phy_type; +} + +void dwc2_set_param_speed(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, "Wrong value for speed parameter\n"); +			dev_err(hsotg->dev, "max_speed parameter must be 0 or 1\n"); +		} +		valid = 0; +	} + +	if (val == DWC2_SPEED_PARAM_HIGH && +	    dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for speed parameter. Check HW configuration.\n", +				val); +		val = dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS ? +				DWC2_SPEED_PARAM_FULL : DWC2_SPEED_PARAM_HIGH; +		dev_dbg(hsotg->dev, "Setting speed to %d\n", val); +	} + +	hsotg->core_params->speed = val; +} + +void dwc2_set_param_host_ls_low_power_phy_clk(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (DWC2_OUT_OF_BOUNDS(val, DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ, +			       DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"Wrong value for host_ls_low_power_phy_clk parameter\n"); +			dev_err(hsotg->dev, +				"host_ls_low_power_phy_clk must be 0 or 1\n"); +		} +		valid = 0; +	} + +	if (val == DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ && +	    dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for host_ls_low_power_phy_clk. Check HW configuration.\n", +				val); +		val = dwc2_get_param_phy_type(hsotg) == DWC2_PHY_TYPE_PARAM_FS +			? DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ +			: DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ; +		dev_dbg(hsotg->dev, "Setting host_ls_low_power_phy_clk to %d\n", +			val); +	} + +	hsotg->core_params->host_ls_low_power_phy_clk = val; +} + +void dwc2_set_param_phy_ulpi_ddr(struct dwc2_hsotg *hsotg, int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, "Wrong value for phy_ulpi_ddr\n"); +			dev_err(hsotg->dev, "phy_upli_ddr must be 0 or 1\n"); +		} +		val = 0; +		dev_dbg(hsotg->dev, "Setting phy_upli_ddr to %d\n", val); +	} + +	hsotg->core_params->phy_ulpi_ddr = val; +} + +void dwc2_set_param_phy_ulpi_ext_vbus(struct dwc2_hsotg *hsotg, int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"Wrong value for phy_ulpi_ext_vbus\n"); +			dev_err(hsotg->dev, +				"phy_ulpi_ext_vbus must be 0 or 1\n"); +		} +		val = 0; +		dev_dbg(hsotg->dev, "Setting phy_ulpi_ext_vbus to %d\n", val); +	} + +	hsotg->core_params->phy_ulpi_ext_vbus = val; +} + +void dwc2_set_param_phy_utmi_width(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 0; + +	switch (hsotg->hw_params.utmi_phy_data_width) { +	case GHWCFG4_UTMI_PHY_DATA_WIDTH_8: +		valid = (val == 8); +		break; +	case GHWCFG4_UTMI_PHY_DATA_WIDTH_16: +		valid = (val == 16); +		break; +	case GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16: +		valid = (val == 8 || val == 16); +		break; +	} + +	if (!valid) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"%d invalid for phy_utmi_width. Check HW configuration.\n", +				val); +		} +		val = (hsotg->hw_params.utmi_phy_data_width == +		       GHWCFG4_UTMI_PHY_DATA_WIDTH_8) ? 8 : 16; +		dev_dbg(hsotg->dev, "Setting phy_utmi_width to %d\n", val); +	} + +	hsotg->core_params->phy_utmi_width = val; +} + +void dwc2_set_param_ulpi_fs_ls(struct dwc2_hsotg *hsotg, int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, "Wrong value for ulpi_fs_ls\n"); +			dev_err(hsotg->dev, "ulpi_fs_ls must be 0 or 1\n"); +		} +		val = 0; +		dev_dbg(hsotg->dev, "Setting ulpi_fs_ls to %d\n", val); +	} + +	hsotg->core_params->ulpi_fs_ls = val; +} + +void dwc2_set_param_ts_dline(struct dwc2_hsotg *hsotg, int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, "Wrong value for ts_dline\n"); +			dev_err(hsotg->dev, "ts_dline must be 0 or 1\n"); +		} +		val = 0; +		dev_dbg(hsotg->dev, "Setting ts_dline to %d\n", val); +	} + +	hsotg->core_params->ts_dline = val; +} + +void dwc2_set_param_i2c_enable(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, "Wrong value for i2c_enable\n"); +			dev_err(hsotg->dev, "i2c_enable must be 0 or 1\n"); +		} + +		valid = 0; +	} + +	if (val == 1 && !(hsotg->hw_params.i2c_enable)) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for i2c_enable. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.i2c_enable; +		dev_dbg(hsotg->dev, "Setting i2c_enable to %d\n", val); +	} + +	hsotg->core_params->i2c_enable = val; +} + +void dwc2_set_param_en_multiple_tx_fifo(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"Wrong value for en_multiple_tx_fifo,\n"); +			dev_err(hsotg->dev, +				"en_multiple_tx_fifo must be 0 or 1\n"); +		} +		valid = 0; +	} + +	if (val == 1 && !hsotg->hw_params.en_multiple_tx_fifo) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for parameter en_multiple_tx_fifo. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.en_multiple_tx_fifo; +		dev_dbg(hsotg->dev, "Setting en_multiple_tx_fifo to %d\n", val); +	} + +	hsotg->core_params->en_multiple_tx_fifo = val; +} + +void dwc2_set_param_reload_ctl(struct dwc2_hsotg *hsotg, int val) +{ +	int valid = 1; + +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"'%d' invalid for parameter reload_ctl\n", val); +			dev_err(hsotg->dev, "reload_ctl must be 0 or 1\n"); +		} +		valid = 0; +	} + +	if (val == 1 && hsotg->hw_params.snpsid < DWC2_CORE_REV_2_92a) +		valid = 0; + +	if (!valid) { +		if (val >= 0) +			dev_err(hsotg->dev, +				"%d invalid for parameter reload_ctl. Check HW configuration.\n", +				val); +		val = hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_92a; +		dev_dbg(hsotg->dev, "Setting reload_ctl to %d\n", val); +	} + +	hsotg->core_params->reload_ctl = val; +} + +void dwc2_set_param_ahbcfg(struct dwc2_hsotg *hsotg, int val) +{ +	if (val != -1) +		hsotg->core_params->ahbcfg = val; +	else +		hsotg->core_params->ahbcfg = GAHBCFG_HBSTLEN_INCR4 << +						GAHBCFG_HBSTLEN_SHIFT; +} + +void dwc2_set_param_otg_ver(struct dwc2_hsotg *hsotg, int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"'%d' invalid for parameter otg_ver\n", val); +			dev_err(hsotg->dev, +				"otg_ver must be 0 (for OTG 1.3 support) or 1 (for OTG 2.0 support)\n"); +		} +		val = 0; +		dev_dbg(hsotg->dev, "Setting otg_ver to %d\n", val); +	} + +	hsotg->core_params->otg_ver = val; +} + +static void dwc2_set_param_uframe_sched(struct dwc2_hsotg *hsotg, int val) +{ +	if (DWC2_OUT_OF_BOUNDS(val, 0, 1)) { +		if (val >= 0) { +			dev_err(hsotg->dev, +				"'%d' invalid for parameter uframe_sched\n", +				val); +			dev_err(hsotg->dev, "uframe_sched must be 0 or 1\n"); +		} +		val = 1; +		dev_dbg(hsotg->dev, "Setting uframe_sched to %d\n", val); +	} + +	hsotg->core_params->uframe_sched = val; +} + +/* + * This function is called during module intialization to pass module parameters + * for the DWC_otg core. + */ +void dwc2_set_parameters(struct dwc2_hsotg *hsotg, +			 const struct dwc2_core_params *params) +{ +	dev_dbg(hsotg->dev, "%s()\n", __func__); + +	dwc2_set_param_otg_cap(hsotg, params->otg_cap); +	dwc2_set_param_dma_enable(hsotg, params->dma_enable); +	dwc2_set_param_dma_desc_enable(hsotg, params->dma_desc_enable); +	dwc2_set_param_host_support_fs_ls_low_power(hsotg, +			params->host_support_fs_ls_low_power); +	dwc2_set_param_enable_dynamic_fifo(hsotg, +			params->enable_dynamic_fifo); +	dwc2_set_param_host_rx_fifo_size(hsotg, +			params->host_rx_fifo_size); +	dwc2_set_param_host_nperio_tx_fifo_size(hsotg, +			params->host_nperio_tx_fifo_size); +	dwc2_set_param_host_perio_tx_fifo_size(hsotg, +			params->host_perio_tx_fifo_size); +	dwc2_set_param_max_transfer_size(hsotg, +			params->max_transfer_size); +	dwc2_set_param_max_packet_count(hsotg, +			params->max_packet_count); +	dwc2_set_param_host_channels(hsotg, params->host_channels); +	dwc2_set_param_phy_type(hsotg, params->phy_type); +	dwc2_set_param_speed(hsotg, params->speed); +	dwc2_set_param_host_ls_low_power_phy_clk(hsotg, +			params->host_ls_low_power_phy_clk); +	dwc2_set_param_phy_ulpi_ddr(hsotg, params->phy_ulpi_ddr); +	dwc2_set_param_phy_ulpi_ext_vbus(hsotg, +			params->phy_ulpi_ext_vbus); +	dwc2_set_param_phy_utmi_width(hsotg, params->phy_utmi_width); +	dwc2_set_param_ulpi_fs_ls(hsotg, params->ulpi_fs_ls); +	dwc2_set_param_ts_dline(hsotg, params->ts_dline); +	dwc2_set_param_i2c_enable(hsotg, params->i2c_enable); +	dwc2_set_param_en_multiple_tx_fifo(hsotg, +			params->en_multiple_tx_fifo); +	dwc2_set_param_reload_ctl(hsotg, params->reload_ctl); +	dwc2_set_param_ahbcfg(hsotg, params->ahbcfg); +	dwc2_set_param_otg_ver(hsotg, params->otg_ver); +	dwc2_set_param_uframe_sched(hsotg, params->uframe_sched); +} + +/** + * During device initialization, read various hardware configuration + * registers and interpret the contents. + */ +int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) +{ +	struct dwc2_hw_params *hw = &hsotg->hw_params; +	unsigned width; +	u32 hwcfg1, hwcfg2, hwcfg3, hwcfg4; +	u32 hptxfsiz, grxfsiz, gnptxfsiz; +	u32 gusbcfg; + +	/* +	 * Attempt to ensure this device is really a DWC_otg Controller. +	 * Read and verify the GSNPSID register contents. The value should be +	 * 0x45f42xxx or 0x45f43xxx, which corresponds to either "OT2" or "OT3", +	 * as in "OTG version 2.xx" or "OTG version 3.xx". +	 */ +	hw->snpsid = readl(hsotg->regs + GSNPSID); +	if ((hw->snpsid & 0xfffff000) != 0x4f542000 && +	    (hw->snpsid & 0xfffff000) != 0x4f543000) { +		dev_err(hsotg->dev, "Bad value for GSNPSID: 0x%08x\n", +			hw->snpsid); +		return -ENODEV; +	} + +	dev_dbg(hsotg->dev, "Core Release: %1x.%1x%1x%1x (snpsid=%x)\n", +		hw->snpsid >> 12 & 0xf, hw->snpsid >> 8 & 0xf, +		hw->snpsid >> 4 & 0xf, hw->snpsid & 0xf, hw->snpsid); + +	hwcfg1 = readl(hsotg->regs + GHWCFG1); +	hwcfg2 = readl(hsotg->regs + GHWCFG2); +	hwcfg3 = readl(hsotg->regs + GHWCFG3); +	hwcfg4 = readl(hsotg->regs + GHWCFG4); +	gnptxfsiz = readl(hsotg->regs + GNPTXFSIZ); +	grxfsiz = readl(hsotg->regs + GRXFSIZ); + +	dev_dbg(hsotg->dev, "hwcfg1=%08x\n", hwcfg1); +	dev_dbg(hsotg->dev, "hwcfg2=%08x\n", hwcfg2); +	dev_dbg(hsotg->dev, "hwcfg3=%08x\n", hwcfg3); +	dev_dbg(hsotg->dev, "hwcfg4=%08x\n", hwcfg4); +	dev_dbg(hsotg->dev, "gnptxfsiz=%08x\n", gnptxfsiz); +	dev_dbg(hsotg->dev, "grxfsiz=%08x\n", grxfsiz); + +	/* Force host mode to get HPTXFSIZ exact power on value */ +	gusbcfg = readl(hsotg->regs + GUSBCFG); +	gusbcfg |= GUSBCFG_FORCEHOSTMODE; +	writel(gusbcfg, hsotg->regs + GUSBCFG); +	usleep_range(100000, 150000); + +	hptxfsiz = readl(hsotg->regs + HPTXFSIZ); +	dev_dbg(hsotg->dev, "hptxfsiz=%08x\n", hptxfsiz); +	gusbcfg = readl(hsotg->regs + GUSBCFG); +	gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; +	writel(gusbcfg, hsotg->regs + GUSBCFG); +	usleep_range(100000, 150000); + +	/* hwcfg2 */ +	hw->op_mode = (hwcfg2 & GHWCFG2_OP_MODE_MASK) >> +		      GHWCFG2_OP_MODE_SHIFT; +	hw->arch = (hwcfg2 & GHWCFG2_ARCHITECTURE_MASK) >> +		   GHWCFG2_ARCHITECTURE_SHIFT; +	hw->enable_dynamic_fifo = !!(hwcfg2 & GHWCFG2_DYNAMIC_FIFO); +	hw->host_channels = 1 + ((hwcfg2 & GHWCFG2_NUM_HOST_CHAN_MASK) >> +				GHWCFG2_NUM_HOST_CHAN_SHIFT); +	hw->hs_phy_type = (hwcfg2 & GHWCFG2_HS_PHY_TYPE_MASK) >> +			  GHWCFG2_HS_PHY_TYPE_SHIFT; +	hw->fs_phy_type = (hwcfg2 & GHWCFG2_FS_PHY_TYPE_MASK) >> +			  GHWCFG2_FS_PHY_TYPE_SHIFT; +	hw->num_dev_ep = (hwcfg2 & GHWCFG2_NUM_DEV_EP_MASK) >> +			 GHWCFG2_NUM_DEV_EP_SHIFT; +	hw->nperio_tx_q_depth = +		(hwcfg2 & GHWCFG2_NONPERIO_TX_Q_DEPTH_MASK) >> +		GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT << 1; +	hw->host_perio_tx_q_depth = +		(hwcfg2 & GHWCFG2_HOST_PERIO_TX_Q_DEPTH_MASK) >> +		GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT << 1; +	hw->dev_token_q_depth = +		(hwcfg2 & GHWCFG2_DEV_TOKEN_Q_DEPTH_MASK) >> +		GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT; + +	/* hwcfg3 */ +	width = (hwcfg3 & GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK) >> +		GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT; +	hw->max_transfer_size = (1 << (width + 11)) - 1; +	width = (hwcfg3 & GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK) >> +		GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT; +	hw->max_packet_count = (1 << (width + 4)) - 1; +	hw->i2c_enable = !!(hwcfg3 & GHWCFG3_I2C); +	hw->total_fifo_size = (hwcfg3 & GHWCFG3_DFIFO_DEPTH_MASK) >> +			      GHWCFG3_DFIFO_DEPTH_SHIFT; + +	/* hwcfg4 */ +	hw->en_multiple_tx_fifo = !!(hwcfg4 & GHWCFG4_DED_FIFO_EN); +	hw->num_dev_perio_in_ep = (hwcfg4 & GHWCFG4_NUM_DEV_PERIO_IN_EP_MASK) >> +				  GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT; +	hw->dma_desc_enable = !!(hwcfg4 & GHWCFG4_DESC_DMA); +	hw->power_optimized = !!(hwcfg4 & GHWCFG4_POWER_OPTIMIZ); +	hw->utmi_phy_data_width = (hwcfg4 & GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK) >> +				  GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT; + +	/* fifo sizes */ +	hw->host_rx_fifo_size = (grxfsiz & GRXFSIZ_DEPTH_MASK) >> +				GRXFSIZ_DEPTH_SHIFT; +	hw->host_nperio_tx_fifo_size = (gnptxfsiz & FIFOSIZE_DEPTH_MASK) >> +				       FIFOSIZE_DEPTH_SHIFT; +	hw->host_perio_tx_fifo_size = (hptxfsiz & FIFOSIZE_DEPTH_MASK) >> +				      FIFOSIZE_DEPTH_SHIFT; + +	dev_dbg(hsotg->dev, "Detected values from hardware:\n"); +	dev_dbg(hsotg->dev, "  op_mode=%d\n", +		hw->op_mode); +	dev_dbg(hsotg->dev, "  arch=%d\n", +		hw->arch); +	dev_dbg(hsotg->dev, "  dma_desc_enable=%d\n", +		hw->dma_desc_enable); +	dev_dbg(hsotg->dev, "  power_optimized=%d\n", +		hw->power_optimized); +	dev_dbg(hsotg->dev, "  i2c_enable=%d\n", +		hw->i2c_enable); +	dev_dbg(hsotg->dev, "  hs_phy_type=%d\n", +		hw->hs_phy_type); +	dev_dbg(hsotg->dev, "  fs_phy_type=%d\n", +		hw->fs_phy_type); +	dev_dbg(hsotg->dev, "  utmi_phy_data_wdith=%d\n", +		hw->utmi_phy_data_width); +	dev_dbg(hsotg->dev, "  num_dev_ep=%d\n", +		hw->num_dev_ep); +	dev_dbg(hsotg->dev, "  num_dev_perio_in_ep=%d\n", +		hw->num_dev_perio_in_ep); +	dev_dbg(hsotg->dev, "  host_channels=%d\n", +		hw->host_channels); +	dev_dbg(hsotg->dev, "  max_transfer_size=%d\n", +		hw->max_transfer_size); +	dev_dbg(hsotg->dev, "  max_packet_count=%d\n", +		hw->max_packet_count); +	dev_dbg(hsotg->dev, "  nperio_tx_q_depth=0x%0x\n", +		hw->nperio_tx_q_depth); +	dev_dbg(hsotg->dev, "  host_perio_tx_q_depth=0x%0x\n", +		hw->host_perio_tx_q_depth); +	dev_dbg(hsotg->dev, "  dev_token_q_depth=0x%0x\n", +		hw->dev_token_q_depth); +	dev_dbg(hsotg->dev, "  enable_dynamic_fifo=%d\n", +		hw->enable_dynamic_fifo); +	dev_dbg(hsotg->dev, "  en_multiple_tx_fifo=%d\n", +		hw->en_multiple_tx_fifo); +	dev_dbg(hsotg->dev, "  total_fifo_size=%d\n", +		hw->total_fifo_size); +	dev_dbg(hsotg->dev, "  host_rx_fifo_size=%d\n", +		hw->host_rx_fifo_size); +	dev_dbg(hsotg->dev, "  host_nperio_tx_fifo_size=%d\n", +		hw->host_nperio_tx_fifo_size); +	dev_dbg(hsotg->dev, "  host_perio_tx_fifo_size=%d\n", +		hw->host_perio_tx_fifo_size); +	dev_dbg(hsotg->dev, "\n"); + +	return 0; +} + +u16 dwc2_get_otg_version(struct dwc2_hsotg *hsotg) +{ +	return hsotg->core_params->otg_ver == 1 ? 0x0200 : 0x0103; +} + +bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg) +{ +	if (readl(hsotg->regs + GSNPSID) == 0xffffffff) +		return false; +	else +		return true; +} + +/** + * dwc2_enable_global_interrupts() - Enables the controller's Global + * Interrupt in the AHB Config register + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_enable_global_interrupts(struct dwc2_hsotg *hsotg) +{ +	u32 ahbcfg = readl(hsotg->regs + GAHBCFG); + +	ahbcfg |= GAHBCFG_GLBL_INTR_EN; +	writel(ahbcfg, hsotg->regs + GAHBCFG); +} + +/** + * dwc2_disable_global_interrupts() - Disables the controller's Global + * Interrupt in the AHB Config register + * + * @hsotg: Programming view of DWC_otg controller + */ +void dwc2_disable_global_interrupts(struct dwc2_hsotg *hsotg) +{ +	u32 ahbcfg = readl(hsotg->regs + GAHBCFG); + +	ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; +	writel(ahbcfg, hsotg->regs + GAHBCFG); +} + +MODULE_DESCRIPTION("DESIGNWARE HS OTG Core"); +MODULE_AUTHOR("Synopsys, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h new file mode 100644 index 00000000000..1efd10cc962 --- /dev/null +++ b/drivers/usb/dwc2/core.h @@ -0,0 +1,950 @@ +/* + * core.h - DesignWare HS OTG Controller common declarations + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DWC2_CORE_H__ +#define __DWC2_CORE_H__ + +#include <linux/phy/phy.h> +#include <linux/regulator/consumer.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <linux/usb/phy.h> +#include "hw.h" + +#ifdef DWC2_LOG_WRITES +static inline void do_write(u32 value, void *addr) +{ +	writel(value, addr); +	pr_info("INFO:: wrote %08x to %p\n", value, addr); +} + +#undef writel +#define writel(v, a)	do_write(v, a) +#endif + +/* Maximum number of Endpoints/HostChannels */ +#define MAX_EPS_CHANNELS	16 + +/* s3c-hsotg declarations */ +static const char * const s3c_hsotg_supply_names[] = { +	"vusb_d",               /* digital USB supply, 1.2V */ +	"vusb_a",               /* analog USB supply, 1.1V */ +}; + +/* + * EP0_MPS_LIMIT + * + * Unfortunately there seems to be a limit of the amount of data that can + * be transferred by IN transactions on EP0. This is either 127 bytes or 3 + * packets (which practically means 1 packet and 63 bytes of data) when the + * MPS is set to 64. + * + * This means if we are wanting to move >127 bytes of data, we need to + * split the transactions up, but just doing one packet at a time does + * not work (this may be an implicit DATA0 PID on first packet of the + * transaction) and doing 2 packets is outside the controller's limits. + * + * If we try to lower the MPS size for EP0, then no transfers work properly + * for EP0, and the system will fail basic enumeration. As no cause for this + * has currently been found, we cannot support any large IN transfers for + * EP0. + */ +#define EP0_MPS_LIMIT   64 + +struct s3c_hsotg; +struct s3c_hsotg_req; + +/** + * struct s3c_hsotg_ep - driver endpoint definition. + * @ep: The gadget layer representation of the endpoint. + * @name: The driver generated name for the endpoint. + * @queue: Queue of requests for this endpoint. + * @parent: Reference back to the parent device structure. + * @req: The current request that the endpoint is processing. This is + *       used to indicate an request has been loaded onto the endpoint + *       and has yet to be completed (maybe due to data move, or simply + *       awaiting an ack from the core all the data has been completed). + * @debugfs: File entry for debugfs file for this endpoint. + * @lock: State lock to protect contents of endpoint. + * @dir_in: Set to true if this endpoint is of the IN direction, which + *          means that it is sending data to the Host. + * @index: The index for the endpoint registers. + * @mc: Multi Count - number of transactions per microframe + * @interval - Interval for periodic endpoints + * @name: The name array passed to the USB core. + * @halted: Set if the endpoint has been halted. + * @periodic: Set if this is a periodic ep, such as Interrupt + * @isochronous: Set if this is a isochronous ep + * @sent_zlp: Set if we've sent a zero-length packet. + * @total_data: The total number of data bytes done. + * @fifo_size: The size of the FIFO (for periodic IN endpoints) + * @fifo_load: The amount of data loaded into the FIFO (periodic IN) + * @last_load: The offset of data for the last start of request. + * @size_loaded: The last loaded size for DxEPTSIZE for periodic IN + * + * This is the driver's state for each registered enpoint, allowing it + * to keep track of transactions that need doing. Each endpoint has a + * lock to protect the state, to try and avoid using an overall lock + * for the host controller as much as possible. + * + * For periodic IN endpoints, we have fifo_size and fifo_load to try + * and keep track of the amount of data in the periodic FIFO for each + * of these as we don't have a status register that tells us how much + * is in each of them. (note, this may actually be useless information + * as in shared-fifo mode periodic in acts like a single-frame packet + * buffer than a fifo) + */ +struct s3c_hsotg_ep { +	struct usb_ep           ep; +	struct list_head        queue; +	struct s3c_hsotg        *parent; +	struct s3c_hsotg_req    *req; +	struct dentry           *debugfs; + +	unsigned long           total_data; +	unsigned int            size_loaded; +	unsigned int            last_load; +	unsigned int            fifo_load; +	unsigned short          fifo_size; + +	unsigned char           dir_in; +	unsigned char           index; +	unsigned char           mc; +	unsigned char           interval; + +	unsigned int            halted:1; +	unsigned int            periodic:1; +	unsigned int            isochronous:1; +	unsigned int            sent_zlp:1; + +	char                    name[10]; +}; + +/** + * struct s3c_hsotg - driver state. + * @dev: The parent device supplied to the probe function + * @driver: USB gadget driver + * @phy: The otg phy transceiver structure for phy control. + * @uphy: The otg phy transceiver structure for old USB phy control. + * @plat: The platform specific configuration data. This can be removed once + * all SoCs support usb transceiver. + * @regs: The memory area mapped for accessing registers. + * @irq: The IRQ number we are using + * @supplies: Definition of USB power supplies + * @phyif: PHY interface width + * @dedicated_fifos: Set if the hardware has dedicated IN-EP fifos. + * @num_of_eps: Number of available EPs (excluding EP0) + * @debug_root: root directrory for debugfs. + * @debug_file: main status file for debugfs. + * @debug_fifo: FIFO status file for debugfs. + * @ep0_reply: Request used for ep0 reply. + * @ep0_buff: Buffer for EP0 reply data, if needed. + * @ctrl_buff: Buffer for EP0 control requests. + * @ctrl_req: Request for EP0 control packets. + * @setup: NAK management for EP0 SETUP + * @last_rst: Time of last reset + * @eps: The endpoints being supplied to the gadget framework + */ +struct s3c_hsotg { +	struct device            *dev; +	struct usb_gadget_driver *driver; +	struct phy               *phy; +	struct usb_phy           *uphy; +	struct s3c_hsotg_plat    *plat; + +	spinlock_t              lock; + +	void __iomem            *regs; +	int                     irq; +	struct clk              *clk; + +	struct regulator_bulk_data supplies[ARRAY_SIZE(s3c_hsotg_supply_names)]; + +	u32                     phyif; +	unsigned int            dedicated_fifos:1; +	unsigned char           num_of_eps; + +	struct dentry           *debug_root; +	struct dentry           *debug_file; +	struct dentry           *debug_fifo; + +	struct usb_request      *ep0_reply; +	struct usb_request      *ctrl_req; +	u8                      ep0_buff[8]; +	u8                      ctrl_buff[8]; + +	struct usb_gadget       gadget; +	unsigned int            setup; +	unsigned long           last_rst; +	struct s3c_hsotg_ep     *eps; +}; + +/** + * struct s3c_hsotg_req - data transfer request + * @req: The USB gadget request + * @queue: The list of requests for the endpoint this is queued for. + * @in_progress: Has already had size/packets written to core + * @mapped: DMA buffer for this request has been mapped via dma_map_single(). + */ +struct s3c_hsotg_req { +	struct usb_request      req; +	struct list_head        queue; +	unsigned char           in_progress; +	unsigned char           mapped; +}; + +#define call_gadget(_hs, _entry) \ +do { \ +	if ((_hs)->gadget.speed != USB_SPEED_UNKNOWN && \ +		(_hs)->driver && (_hs)->driver->_entry) { \ +		spin_unlock(&_hs->lock); \ +		(_hs)->driver->_entry(&(_hs)->gadget); \ +		spin_lock(&_hs->lock); \ +	} \ +} while (0) + +struct dwc2_hsotg; +struct dwc2_host_chan; + +/* Device States */ +enum dwc2_lx_state { +	DWC2_L0,	/* On state */ +	DWC2_L1,	/* LPM sleep state */ +	DWC2_L2,	/* USB suspend state */ +	DWC2_L3,	/* Off state */ +}; + +/** + * struct dwc2_core_params - Parameters for configuring the core + * + * @otg_cap:            Specifies the OTG capabilities. + *                       0 - HNP and SRP capable + *                       1 - SRP Only capable + *                       2 - No HNP/SRP capable (always available) + *                      Defaults to best available option (0, 1, then 2) + * @otg_ver:            OTG version supported + *                       0 - 1.3 (default) + *                       1 - 2.0 + * @dma_enable:         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 (always available) + *                       1 - DMA (default, if available) + * @dma_desc_enable:    When DMA mode is enabled, specifies whether to use + *                      address DMA mode or descriptor DMA mode for accessing + *                      the data FIFOs. The driver will automatically detect the + *                      value for this if none is specified. + *                       0 - Address DMA + *                       1 - Descriptor DMA (default, if available) + * @speed:              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. + *                       0 - High Speed + *                           (default when phy_type is UTMI+ or ULPI) + *                       1 - Full Speed + *                           (default when phy_type is Full Speed) + * @enable_dynamic_fifo: 0 - Use coreConsultant-specified FIFO size parameters + *                       1 - Allow dynamic FIFO sizing (default, if available) + * @en_multiple_tx_fifo: Specifies whether dedicated per-endpoint transmit FIFOs + *                      are enabled + * @host_rx_fifo_size:  Number of 4-byte words in the Rx FIFO in host mode when + *                      dynamic FIFO sizing is enabled + *                       16 to 32768 + *                      Actual maximum value is autodetected and also + *                      the default. + * @host_nperio_tx_fifo_size: Number of 4-byte words in the non-periodic Tx FIFO + *                      in host mode when dynamic FIFO sizing is enabled + *                       16 to 32768 + *                      Actual maximum value is autodetected and also + *                      the default. + * @host_perio_tx_fifo_size: Number of 4-byte words in the periodic Tx FIFO in + *                      host mode when dynamic FIFO sizing is enabled + *                       16 to 32768 + *                      Actual maximum value is autodetected and also + *                      the default. + * @max_transfer_size:  The maximum transfer size supported, in bytes + *                       2047 to 65,535 + *                      Actual maximum value is autodetected and also + *                      the default. + * @max_packet_count:   The maximum number of packets in a transfer + *                       15 to 511 + *                      Actual maximum value is autodetected and also + *                      the default. + * @host_channels:      The number of host channel registers to use + *                       1 to 16 + *                      Actual maximum value is autodetected and also + *                      the default. + * @phy_type:           Specifies the type of PHY interface to use. By default, + *                      the driver will automatically detect the phy_type. + *                       0 - Full Speed Phy + *                       1 - UTMI+ Phy + *                       2 - ULPI Phy + *                      Defaults to best available option (2, 1, then 0) + * @phy_utmi_width:     Specifies the UTMI+ Data Width (in bits). 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 (default 16 if available) + * @phy_ulpi_ddr:       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 + * @phy_ulpi_ext_vbus:  For a ULPI phy, specifies whether to use the internal or + *                      external supply to drive the VBus + *                       0 - Internal supply (default) + *                       1 - External supply + * @i2c_enable:         Specifies whether to use the I2Cinterface for a full + *                      speed PHY. This parameter is only applicable if phy_type + *                      is FS. + *                       0 - No (default) + *                       1 - Yes + * @ulpi_fs_ls:         Make ULPI phy operate in FS/LS mode only + *                       0 - No (default) + *                       1 - Yes + * @host_support_fs_ls_low_power: 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 + * @host_ls_low_power_phy_clk: 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. + *                       0 - 48 MHz + *                           (default when phy_type is UTMI+ or ULPI) + *                       1 - 6 MHz + *                           (default when phy_type is Full Speed) + * @ts_dline:           Enable Term Select Dline pulsing + *                       0 - No (default) + *                       1 - Yes + * @reload_ctl:         Allow dynamic reloading of HFIR register during runtime + *                       0 - No (default for core < 2.92a) + *                       1 - Yes (default for core >= 2.92a) + * @ahbcfg:             This field allows the default value of the GAHBCFG + *                      register to be overridden + *                       -1         - GAHBCFG value will be set to 0x06 + *                                    (INCR4, default) + *                       all others - GAHBCFG value will be overridden with + *                                    this value + *                      Not all bits can be controlled like this, the + *                      bits defined by GAHBCFG_CTRL_MASK are controlled + *                      by the driver and are ignored in this + *                      configuration value. + * @uframe_sched:       True to enable the microframe scheduler + * + * The following parameters may be specified when starting the module. These + * parameters define how the DWC_otg controller should be configured. A + * value of -1 (or any other out of range value) for any parameter means + * to read the value from hardware (if possible) or use the builtin + * default described above. + */ +struct dwc2_core_params { +	/* +	 * Don't add any non-int members here, this will break +	 * dwc2_set_all_params! +	 */ +	int otg_cap; +	int otg_ver; +	int dma_enable; +	int dma_desc_enable; +	int speed; +	int enable_dynamic_fifo; +	int en_multiple_tx_fifo; +	int host_rx_fifo_size; +	int host_nperio_tx_fifo_size; +	int host_perio_tx_fifo_size; +	int max_transfer_size; +	int max_packet_count; +	int host_channels; +	int phy_type; +	int phy_utmi_width; +	int phy_ulpi_ddr; +	int phy_ulpi_ext_vbus; +	int i2c_enable; +	int ulpi_fs_ls; +	int host_support_fs_ls_low_power; +	int host_ls_low_power_phy_clk; +	int ts_dline; +	int reload_ctl; +	int ahbcfg; +	int uframe_sched; +}; + +/** + * struct dwc2_hw_params - Autodetected parameters. + * + * These parameters are the various parameters read from hardware + * registers during initialization. They typically contain the best + * supported or maximum value that can be configured in the + * corresponding dwc2_core_params value. + * + * The values that are not in dwc2_core_params are documented below. + * + * @op_mode             Mode of Operation + *                       0 - HNP- and SRP-Capable OTG (Host & Device) + *                       1 - SRP-Capable OTG (Host & Device) + *                       2 - Non-HNP and Non-SRP Capable OTG (Host & Device) + *                       3 - SRP-Capable Device + *                       4 - Non-OTG Device + *                       5 - SRP-Capable Host + *                       6 - Non-OTG Host + * @arch                Architecture + *                       0 - Slave only + *                       1 - External DMA + *                       2 - Internal DMA + * @power_optimized     Are power optimizations enabled? + * @num_dev_ep          Number of device endpoints available + * @num_dev_perio_in_ep Number of device periodic IN endpoints + *                      avaialable + * @dev_token_q_depth   Device Mode IN Token Sequence Learning Queue + *                      Depth + *                       0 to 30 + * @host_perio_tx_q_depth + *                      Host Mode Periodic Request Queue Depth + *                       2, 4 or 8 + * @nperio_tx_q_depth + *                      Non-Periodic Request Queue Depth + *                       2, 4 or 8 + * @hs_phy_type         High-speed PHY interface type + *                       0 - High-speed interface not supported + *                       1 - UTMI+ + *                       2 - ULPI + *                       3 - UTMI+ and ULPI + * @fs_phy_type         Full-speed PHY interface type + *                       0 - Full speed interface not supported + *                       1 - Dedicated full speed interface + *                       2 - FS pins shared with UTMI+ pins + *                       3 - FS pins shared with ULPI pins + * @total_fifo_size:    Total internal RAM for FIFOs (bytes) + * @utmi_phy_data_width UTMI+ PHY data width + *                       0 - 8 bits + *                       1 - 16 bits + *                       2 - 8 or 16 bits + * @snpsid:             Value from SNPSID register + */ +struct dwc2_hw_params { +	unsigned op_mode:3; +	unsigned arch:2; +	unsigned dma_desc_enable:1; +	unsigned enable_dynamic_fifo:1; +	unsigned en_multiple_tx_fifo:1; +	unsigned host_rx_fifo_size:16; +	unsigned host_nperio_tx_fifo_size:16; +	unsigned host_perio_tx_fifo_size:16; +	unsigned nperio_tx_q_depth:3; +	unsigned host_perio_tx_q_depth:3; +	unsigned dev_token_q_depth:5; +	unsigned max_transfer_size:26; +	unsigned max_packet_count:11; +	unsigned host_channels:5; +	unsigned hs_phy_type:2; +	unsigned fs_phy_type:2; +	unsigned i2c_enable:1; +	unsigned num_dev_ep:4; +	unsigned num_dev_perio_in_ep:4; +	unsigned total_fifo_size:16; +	unsigned power_optimized:1; +	unsigned utmi_phy_data_width:2; +	u32 snpsid; +}; + +/** + * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic + * and periodic schedules + * + * @dev:                The struct device pointer + * @regs:		Pointer to controller regs + * @core_params:        Parameters that define how the core should be configured + * @hw_params:          Parameters that were autodetected from the + *                      hardware registers + * @op_state:           The operational State, during transitions (a_host=> + *                      a_peripheral and b_device=>b_host) this may not match + *                      the core, but allows the software to determine + *                      transitions + * @queuing_high_bandwidth: True if multiple packets of a high-bandwidth + *                      transfer are in process of being queued + * @srp_success:        Stores status of SRP request in the case of a FS PHY + *                      with an I2C interface + * @wq_otg:             Workqueue object used for handling of some interrupts + * @wf_otg:             Work object for handling Connector ID Status Change + *                      interrupt + * @wkp_timer:          Timer object for handling Wakeup Detected interrupt + * @lx_state:           Lx state of connected device + * @flags:              Flags for handling root port state changes + * @non_periodic_sched_inactive: Inactive QHs in the non-periodic schedule. + *                      Transfers associated with these QHs are not currently + *                      assigned to a host channel. + * @non_periodic_sched_active: Active QHs in the non-periodic schedule. + *                      Transfers associated with these QHs are currently + *                      assigned to a host channel. + * @non_periodic_qh_ptr: Pointer to next QH to process in the active + *                      non-periodic schedule + * @periodic_sched_inactive: Inactive QHs 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. + * @periodic_sched_ready:  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. + * @periodic_sched_assigned: 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. + * @periodic_sched_queued: 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. + * @periodic_usecs:     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. + * @frame_usecs:        Internal variable used by the microframe scheduler + * @frame_number:       Frame number read from the core at SOF. The value ranges + *                      from 0 to HFNUM_MAX_FRNUM. + * @periodic_qh_count:  Count of periodic QHs, if using several eps. Used for + *                      SOF enable/disable. + * @free_hc_list:       Free host channels in the controller. This is a list of + *                      struct dwc2_host_chan items. + * @periodic_channels:  Number of host channels assigned to periodic transfers. + *                      Currently assuming that there is a dedicated host + *                      channel for each periodic transaction and at least one + *                      host channel is available for non-periodic transactions. + * @non_periodic_channels: Number of host channels assigned to non-periodic + *                      transfers + * @available_host_channels Number of host channels available for the microframe + *                      scheduler to use + * @hc_ptr_array:       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. + * @status_buf:         Buffer used for data received during the status phase of + *                      a control transfer. + * @status_buf_dma:     DMA address for status_buf + * @start_work:         Delayed work for handling host A-cable connection + * @reset_work:         Delayed work for handling a port reset + * @lock:               Spinlock that protects all the driver data structures + * @priv:               Stores a pointer to the struct usb_hcd + * @otg_port:           OTG port number + * @frame_list:         Frame list + * @frame_list_dma:     Frame list DMA address + */ +struct dwc2_hsotg { +	struct device *dev; +	void __iomem *regs; +	/** Params detected from hardware */ +	struct dwc2_hw_params hw_params; +	/** Params to actually use */ +	struct dwc2_core_params *core_params; +	enum usb_otg_state op_state; + +	unsigned int queuing_high_bandwidth:1; +	unsigned int srp_success:1; + +	struct workqueue_struct *wq_otg; +	struct work_struct wf_otg; +	struct timer_list wkp_timer; +	enum dwc2_lx_state lx_state; + +	union dwc2_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 port_l1_change:1; +			unsigned reserved:26; +		} b; +	} flags; + +	struct list_head non_periodic_sched_inactive; +	struct list_head non_periodic_sched_active; +	struct list_head *non_periodic_qh_ptr; +	struct list_head periodic_sched_inactive; +	struct list_head periodic_sched_ready; +	struct list_head periodic_sched_assigned; +	struct list_head periodic_sched_queued; +	u16 periodic_usecs; +	u16 frame_usecs[8]; +	u16 frame_number; +	u16 periodic_qh_count; + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS +#define FRAME_NUM_ARRAY_SIZE 1000 +	u16 last_frame_num; +	u16 *frame_num_array; +	u16 *last_frame_num_array; +	int frame_num_idx; +	int dumped_frame_num_array; +#endif + +	struct list_head free_hc_list; +	int periodic_channels; +	int non_periodic_channels; +	int available_host_channels; +	struct dwc2_host_chan *hc_ptr_array[MAX_EPS_CHANNELS]; +	u8 *status_buf; +	dma_addr_t status_buf_dma; +#define DWC2_HCD_STATUS_BUF_SIZE 64 + +	struct delayed_work start_work; +	struct delayed_work reset_work; +	spinlock_t lock; +	void *priv; +	u8 otg_port; +	u32 *frame_list; +	dma_addr_t frame_list_dma; + +	/* DWC OTG HW Release versions */ +#define DWC2_CORE_REV_2_71a	0x4f54271a +#define DWC2_CORE_REV_2_90a	0x4f54290a +#define DWC2_CORE_REV_2_92a	0x4f54292a +#define DWC2_CORE_REV_2_94a	0x4f54294a +#define DWC2_CORE_REV_3_00a	0x4f54300a + +#ifdef DEBUG +	u32 frrem_samples; +	u64 frrem_accum; + +	u32 hfnum_7_samples_a; +	u64 hfnum_7_frrem_accum_a; +	u32 hfnum_0_samples_a; +	u64 hfnum_0_frrem_accum_a; +	u32 hfnum_other_samples_a; +	u64 hfnum_other_frrem_accum_a; + +	u32 hfnum_7_samples_b; +	u64 hfnum_7_frrem_accum_b; +	u32 hfnum_0_samples_b; +	u64 hfnum_0_frrem_accum_b; +	u32 hfnum_other_samples_b; +	u64 hfnum_other_frrem_accum_b; +#endif +}; + +/* Reasons for halting a host channel */ +enum dwc2_halt_status { +	DWC2_HC_XFER_NO_HALT_STATUS, +	DWC2_HC_XFER_COMPLETE, +	DWC2_HC_XFER_URB_COMPLETE, +	DWC2_HC_XFER_ACK, +	DWC2_HC_XFER_NAK, +	DWC2_HC_XFER_NYET, +	DWC2_HC_XFER_STALL, +	DWC2_HC_XFER_XACT_ERR, +	DWC2_HC_XFER_FRAME_OVERRUN, +	DWC2_HC_XFER_BABBLE_ERR, +	DWC2_HC_XFER_DATA_TOGGLE_ERR, +	DWC2_HC_XFER_AHB_ERR, +	DWC2_HC_XFER_PERIODIC_INCOMPLETE, +	DWC2_HC_XFER_URB_DEQUEUE, +}; + +/* + * The following functions support initialization of the core driver component + * and the DWC_otg controller + */ +extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg); + +/* + * Host core Functions. + * The following functions support managing the DWC_otg controller in host + * mode. + */ +extern void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan); +extern void dwc2_hc_halt(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, +			 enum dwc2_halt_status halt_status); +extern void dwc2_hc_cleanup(struct dwc2_hsotg *hsotg, +			    struct dwc2_host_chan *chan); +extern void dwc2_hc_start_transfer(struct dwc2_hsotg *hsotg, +				   struct dwc2_host_chan *chan); +extern void dwc2_hc_start_transfer_ddma(struct dwc2_hsotg *hsotg, +					struct dwc2_host_chan *chan); +extern int dwc2_hc_continue_transfer(struct dwc2_hsotg *hsotg, +				     struct dwc2_host_chan *chan); +extern void dwc2_hc_do_ping(struct dwc2_hsotg *hsotg, +			    struct dwc2_host_chan *chan); +extern void dwc2_enable_host_interrupts(struct dwc2_hsotg *hsotg); +extern void dwc2_disable_host_interrupts(struct dwc2_hsotg *hsotg); + +extern u32 dwc2_calc_frame_interval(struct dwc2_hsotg *hsotg); +extern bool dwc2_is_controller_alive(struct dwc2_hsotg *hsotg); + +/* + * Common core Functions. + * The following functions support managing the DWC_otg controller in either + * device or host mode. + */ +extern void dwc2_read_packet(struct dwc2_hsotg *hsotg, u8 *dest, u16 bytes); +extern void dwc2_flush_tx_fifo(struct dwc2_hsotg *hsotg, const int num); +extern void dwc2_flush_rx_fifo(struct dwc2_hsotg *hsotg); + +extern int dwc2_core_init(struct dwc2_hsotg *hsotg, bool select_phy, int irq); +extern void dwc2_enable_global_interrupts(struct dwc2_hsotg *hcd); +extern void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd); + +/* This function should be called on every hardware interrupt. */ +extern irqreturn_t dwc2_handle_common_intr(int irq, void *dev); + +/* OTG Core Parameters */ + +/* + * 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 + */ +extern void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg, int val); +#define DWC2_CAP_PARAM_HNP_SRP_CAPABLE		0 +#define DWC2_CAP_PARAM_SRP_ONLY_CAPABLE		1 +#define DWC2_CAP_PARAM_NO_HNP_SRP_CAPABLE	2 + +/* + * 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) + */ +extern void dwc2_set_param_dma_enable(struct dwc2_hsotg *hsotg, int val); + +/* + * When DMA mode is enabled specifies whether to use + * address DMA or DMA Descritor mode for accessing the data + * FIFOs in device mode. The driver will automatically detect + * the value for this parameter if none is specified. + * 0 - address DMA + * 1 - DMA Descriptor(default, if available) + */ +extern void dwc2_set_param_dma_desc_enable(struct dwc2_hsotg *hsotg, int val); + +/* + * 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 + */ +extern void dwc2_set_param_speed(struct dwc2_hsotg *hsotg, int val); +#define DWC2_SPEED_PARAM_HIGH	0 +#define DWC2_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 + */ +extern void dwc2_set_param_host_support_fs_ls_low_power( +		struct dwc2_hsotg *hsotg, int val); + +/* + * 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 + */ +extern void dwc2_set_param_host_ls_low_power_phy_clk(struct dwc2_hsotg *hsotg, +						     int val); +#define DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ	0 +#define DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ	1 + +/* + * 0 - Use cC FIFO size parameters + * 1 - Allow dynamic FIFO sizing (default) + */ +extern void dwc2_set_param_enable_dynamic_fifo(struct dwc2_hsotg *hsotg, +					       int val); + +/* + * Number of 4-byte words in the Rx FIFO in host mode when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 1024) + */ +extern void dwc2_set_param_host_rx_fifo_size(struct dwc2_hsotg *hsotg, int val); + +/* + * 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 256) + */ +extern void dwc2_set_param_host_nperio_tx_fifo_size(struct dwc2_hsotg *hsotg, +						    int val); + +/* + * Number of 4-byte words in the host periodic Tx FIFO when dynamic + * FIFO sizing is enabled. + * 16 to 32768 (default 256) + */ +extern void dwc2_set_param_host_perio_tx_fifo_size(struct dwc2_hsotg *hsotg, +						   int val); + +/* + * The maximum transfer size supported in bytes. + * 2047 to 65,535  (default 65,535) + */ +extern void dwc2_set_param_max_transfer_size(struct dwc2_hsotg *hsotg, int val); + +/* + * The maximum number of packets in a transfer. + * 15 to 511  (default 511) + */ +extern void dwc2_set_param_max_packet_count(struct dwc2_hsotg *hsotg, int val); + +/* + * The number of host channel registers to use. + * 1 to 16 (default 11) + * Note: The FPGA configuration supports a maximum of 11 host channels. + */ +extern void dwc2_set_param_host_channels(struct dwc2_hsotg *hsotg, int val); + +/* + * 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 + */ +extern void dwc2_set_param_phy_type(struct dwc2_hsotg *hsotg, int val); +#define DWC2_PHY_TYPE_PARAM_FS		0 +#define DWC2_PHY_TYPE_PARAM_UTMI	1 +#define DWC2_PHY_TYPE_PARAM_ULPI	2 + +/* + * 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) + */ +extern void dwc2_set_param_phy_utmi_width(struct dwc2_hsotg *hsotg, int val); + +/* + * 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 + */ +extern void dwc2_set_param_phy_ulpi_ddr(struct dwc2_hsotg *hsotg, int val); + +/* + * Specifies whether to use the internal or external supply to + * drive the vbus with a ULPI phy. + */ +extern void dwc2_set_param_phy_ulpi_ext_vbus(struct dwc2_hsotg *hsotg, int val); +#define DWC2_PHY_ULPI_INTERNAL_VBUS	0 +#define DWC2_PHY_ULPI_EXTERNAL_VBUS	1 + +/* + * 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 + */ +extern void dwc2_set_param_i2c_enable(struct dwc2_hsotg *hsotg, int val); + +extern void dwc2_set_param_ulpi_fs_ls(struct dwc2_hsotg *hsotg, int val); + +extern void dwc2_set_param_ts_dline(struct dwc2_hsotg *hsotg, int val); + +/* + * Specifies whether dedicated transmit FIFOs are + * enabled for non periodic IN endpoints in device mode + * 0 - No + * 1 - Yes + */ +extern void dwc2_set_param_en_multiple_tx_fifo(struct dwc2_hsotg *hsotg, +					       int val); + +extern void dwc2_set_param_reload_ctl(struct dwc2_hsotg *hsotg, int val); + +extern void dwc2_set_param_ahbcfg(struct dwc2_hsotg *hsotg, int val); + +extern void dwc2_set_param_otg_ver(struct dwc2_hsotg *hsotg, int val); + +/* + * Dump core registers and SPRAM + */ +extern void dwc2_dump_dev_registers(struct dwc2_hsotg *hsotg); +extern void dwc2_dump_host_registers(struct dwc2_hsotg *hsotg); +extern void dwc2_dump_global_registers(struct dwc2_hsotg *hsotg); + +/* + * Return OTG version - either 1.3 or 2.0 + */ +extern u16 dwc2_get_otg_version(struct dwc2_hsotg *hsotg); + +#endif /* __DWC2_CORE_H__ */ diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c new file mode 100644 index 00000000000..c93918b70d0 --- /dev/null +++ b/drivers/usb/dwc2/core_intr.c @@ -0,0 +1,511 @@ +/* + * core_intr.c - DesignWare HS OTG Controller common interrupt handling + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the common interrupt handlers + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +static const char *dwc2_op_state_str(struct dwc2_hsotg *hsotg) +{ +	switch (hsotg->op_state) { +	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_B_PERIPHERAL: +		return "b_peripheral"; +	case OTG_STATE_B_HOST: +		return "b_host"; +	default: +		return "unknown"; +	} +} + +/** + * dwc2_handle_usb_port_intr - handles OTG PRTINT interrupts. + * When the PRTINT interrupt fires, there are certain status bits in the Host + * Port that needs to get cleared. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_usb_port_intr(struct dwc2_hsotg *hsotg) +{ +	u32 hprt0 = readl(hsotg->regs + HPRT0); + +	if (hprt0 & HPRT0_ENACHG) { +		hprt0 &= ~HPRT0_ENA; +		writel(hprt0, hsotg->regs + HPRT0); +	} + +	/* Clear interrupt */ +	writel(GINTSTS_PRTINT, hsotg->regs + GINTSTS); +} + +/** + * dwc2_handle_mode_mismatch_intr() - Logs a mode mismatch warning message + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_mode_mismatch_intr(struct dwc2_hsotg *hsotg) +{ +	dev_warn(hsotg->dev, "Mode Mismatch Interrupt: currently in %s mode\n", +		 dwc2_is_host_mode(hsotg) ? "Host" : "Device"); + +	/* Clear interrupt */ +	writel(GINTSTS_MODEMIS, hsotg->regs + GINTSTS); +} + +/** + * dwc2_handle_otg_intr() - Handles the OTG Interrupts. It reads the OTG + * Interrupt Register (GOTGINT) to determine what interrupt has occurred. + * + * @hsotg: Programming view of DWC_otg controller + */ +static void dwc2_handle_otg_intr(struct dwc2_hsotg *hsotg) +{ +	u32 gotgint; +	u32 gotgctl; +	u32 gintmsk; + +	gotgint = readl(hsotg->regs + GOTGINT); +	gotgctl = readl(hsotg->regs + GOTGCTL); +	dev_dbg(hsotg->dev, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint, +		dwc2_op_state_str(hsotg)); + +	if (gotgint & GOTGINT_SES_END_DET) { +		dev_dbg(hsotg->dev, +			" ++OTG Interrupt: Session End Detected++ (%s)\n", +			dwc2_op_state_str(hsotg)); +		gotgctl = readl(hsotg->regs + GOTGCTL); + +		if (hsotg->op_state == OTG_STATE_B_HOST) { +			hsotg->op_state = OTG_STATE_B_PERIPHERAL; +		} else { +			/* +			 * If not B_HOST and Device HNP still set, HNP did +			 * not succeed! +			 */ +			if (gotgctl & GOTGCTL_DEVHNPEN) { +				dev_dbg(hsotg->dev, "Session End Detected\n"); +				dev_err(hsotg->dev, +					"Device Not Connected/Responding!\n"); +			} + +			/* +			 * If Session End Detected the B-Cable has been +			 * disconnected +			 */ +			/* Reset to a clean state */ +			hsotg->lx_state = DWC2_L0; +		} + +		gotgctl = readl(hsotg->regs + GOTGCTL); +		gotgctl &= ~GOTGCTL_DEVHNPEN; +		writel(gotgctl, hsotg->regs + GOTGCTL); +	} + +	if (gotgint & GOTGINT_SES_REQ_SUC_STS_CHNG) { +		dev_dbg(hsotg->dev, +			" ++OTG Interrupt: Session Request Success Status Change++\n"); +		gotgctl = readl(hsotg->regs + GOTGCTL); +		if (gotgctl & GOTGCTL_SESREQSCS) { +			if (hsotg->core_params->phy_type == +					DWC2_PHY_TYPE_PARAM_FS +			    && hsotg->core_params->i2c_enable > 0) { +				hsotg->srp_success = 1; +			} else { +				/* Clear Session Request */ +				gotgctl = readl(hsotg->regs + GOTGCTL); +				gotgctl &= ~GOTGCTL_SESREQ; +				writel(gotgctl, hsotg->regs + GOTGCTL); +			} +		} +	} + +	if (gotgint & GOTGINT_HST_NEG_SUC_STS_CHNG) { +		/* +		 * Print statements during the HNP interrupt handling +		 * can cause it to fail +		 */ +		gotgctl = readl(hsotg->regs + GOTGCTL); +		/* +		 * WA for 3.00a- HW is not setting cur_mode, even sometimes +		 * this does not help +		 */ +		if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) +			udelay(100); +		if (gotgctl & GOTGCTL_HSTNEGSCS) { +			if (dwc2_is_host_mode(hsotg)) { +				hsotg->op_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 = readl(hsotg->regs + GINTMSK); +				gintmsk &= ~GINTSTS_SOF; +				writel(gintmsk, hsotg->regs + GINTMSK); + +				/* +				 * Call callback function with spin lock +				 * released +				 */ +				spin_unlock(&hsotg->lock); + +				/* Initialize the Core for Host mode */ +				dwc2_hcd_start(hsotg); +				spin_lock(&hsotg->lock); +				hsotg->op_state = OTG_STATE_B_HOST; +			} +		} else { +			gotgctl = readl(hsotg->regs + GOTGCTL); +			gotgctl &= ~(GOTGCTL_HNPREQ | GOTGCTL_DEVHNPEN); +			writel(gotgctl, hsotg->regs + GOTGCTL); +			dev_dbg(hsotg->dev, "HNP Failed\n"); +			dev_err(hsotg->dev, +				"Device Not Connected/Responding\n"); +		} +	} + +	if (gotgint & GOTGINT_HST_NEG_DET) { +		/* +		 * 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. +		 */ +		dev_dbg(hsotg->dev, +			" ++OTG Interrupt: Host Negotiation Detected++ (%s)\n", +			(dwc2_is_host_mode(hsotg) ? "Host" : "Device")); +		if (dwc2_is_device_mode(hsotg)) { +			dev_dbg(hsotg->dev, "a_suspend->a_peripheral (%d)\n", +				hsotg->op_state); +			spin_unlock(&hsotg->lock); +			dwc2_hcd_disconnect(hsotg); +			spin_lock(&hsotg->lock); +			hsotg->op_state = OTG_STATE_A_PERIPHERAL; +		} else { +			/* Need to disable SOF interrupt immediately */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk &= ~GINTSTS_SOF; +			writel(gintmsk, hsotg->regs + GINTMSK); +			spin_unlock(&hsotg->lock); +			dwc2_hcd_start(hsotg); +			spin_lock(&hsotg->lock); +			hsotg->op_state = OTG_STATE_A_HOST; +		} +	} + +	if (gotgint & GOTGINT_A_DEV_TOUT_CHG) +		dev_dbg(hsotg->dev, +			" ++OTG Interrupt: A-Device Timeout Change++\n"); +	if (gotgint & GOTGINT_DBNCE_DONE) +		dev_dbg(hsotg->dev, " ++OTG Interrupt: Debounce Done++\n"); + +	/* Clear GOTGINT */ +	writel(gotgint, hsotg->regs + GOTGINT); +} + +/** + * dwc2_handle_conn_id_status_change_intr() - Handles the Connector ID Status + * Change Interrupt + * + * @hsotg: Programming view of DWC_otg controller + * + * Reads the OTG Interrupt Register (GOTCTL) to determine whether this is a + * Device to Host Mode transition or a Host to Device Mode transition. This only + * occurs when the cable is connected/removed from the PHY connector. + */ +static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) +{ +	u32 gintmsk = readl(hsotg->regs + GINTMSK); + +	/* Need to disable SOF interrupt immediately */ +	gintmsk &= ~GINTSTS_SOF; +	writel(gintmsk, hsotg->regs + GINTMSK); + +	dev_dbg(hsotg->dev, " ++Connector ID Status Change Interrupt++  (%s)\n", +		dwc2_is_host_mode(hsotg) ? "Host" : "Device"); + +	/* +	 * Need to schedule a work, as there are possible DELAY function calls. +	 * Release lock before scheduling workq as it holds spinlock during +	 * scheduling. +	 */ +	spin_unlock(&hsotg->lock); +	queue_work(hsotg->wq_otg, &hsotg->wf_otg); +	spin_lock(&hsotg->lock); + +	/* Clear interrupt */ +	writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); +} + +/** + * dwc2_handle_session_req_intr() - 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 + * + * @hsotg: Programming view of DWC_otg controller + * + * This handler responds by turning on bus power. If the DWC_otg controller is + * in low power mode, this handler brings the controller out of low power mode + * before turning on bus power. + */ +static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) +{ +	dev_dbg(hsotg->dev, "++Session Request Interrupt++\n"); + +	/* Clear interrupt */ +	writel(GINTSTS_SESSREQINT, hsotg->regs + GINTSTS); +} + +/* + * 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 void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) +{ +	dev_dbg(hsotg->dev, "++Resume or Remote Wakeup Detected Interrupt++\n"); +	dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); + +	if (dwc2_is_device_mode(hsotg)) { +		dev_dbg(hsotg->dev, "DSTS=0x%0x\n", readl(hsotg->regs + DSTS)); +		if (hsotg->lx_state == DWC2_L2) { +			u32 dctl = readl(hsotg->regs + DCTL); + +			/* Clear Remote Wakeup Signaling */ +			dctl &= ~DCTL_RMTWKUPSIG; +			writel(dctl, hsotg->regs + DCTL); +		} +		/* Change to L0 state */ +		hsotg->lx_state = DWC2_L0; +	} else { +		if (hsotg->lx_state != DWC2_L1) { +			u32 pcgcctl = readl(hsotg->regs + PCGCTL); + +			/* Restart the Phy Clock */ +			pcgcctl &= ~PCGCTL_STOPPCLK; +			writel(pcgcctl, hsotg->regs + PCGCTL); +			mod_timer(&hsotg->wkp_timer, +				  jiffies + msecs_to_jiffies(71)); +		} else { +			/* Change to L0 state */ +			hsotg->lx_state = DWC2_L0; +		} +	} + +	/* Clear interrupt */ +	writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); +} + +/* + * This interrupt indicates that a device has been disconnected from the + * root port + */ +static void dwc2_handle_disconnect_intr(struct dwc2_hsotg *hsotg) +{ +	dev_dbg(hsotg->dev, "++Disconnect Detected Interrupt++ (%s) %s\n", +		dwc2_is_host_mode(hsotg) ? "Host" : "Device", +		dwc2_op_state_str(hsotg)); + +	/* Change to L3 (OFF) state */ +	hsotg->lx_state = DWC2_L3; + +	writel(GINTSTS_DISCONNINT, hsotg->regs + GINTSTS); +} + +/* + * 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 void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) +{ +	u32 dsts; + +	dev_dbg(hsotg->dev, "USB SUSPEND\n"); + +	if (dwc2_is_device_mode(hsotg)) { +		/* +		 * Check the Device status register to determine if the Suspend +		 * state is active +		 */ +		dsts = readl(hsotg->regs + DSTS); +		dev_dbg(hsotg->dev, "DSTS=0x%0x\n", dsts); +		dev_dbg(hsotg->dev, +			"DSTS.Suspend Status=%d HWCFG4.Power Optimize=%d\n", +			!!(dsts & DSTS_SUSPSTS), +			hsotg->hw_params.power_optimized); +	} else { +		if (hsotg->op_state == OTG_STATE_A_PERIPHERAL) { +			dev_dbg(hsotg->dev, "a_peripheral->a_host\n"); + +			/* Clear the a_peripheral flag, back to a_host */ +			spin_unlock(&hsotg->lock); +			dwc2_hcd_start(hsotg); +			spin_lock(&hsotg->lock); +			hsotg->op_state = OTG_STATE_A_HOST; +		} +	} + +	/* Change to L2 (suspend) state */ +	hsotg->lx_state = DWC2_L2; + +	/* Clear interrupt */ +	writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); +} + +#define GINTMSK_COMMON	(GINTSTS_WKUPINT | GINTSTS_SESSREQINT |		\ +			 GINTSTS_CONIDSTSCHNG | GINTSTS_OTGINT |	\ +			 GINTSTS_MODEMIS | GINTSTS_DISCONNINT |		\ +			 GINTSTS_USBSUSP | GINTSTS_PRTINT) + +/* + * This function returns the Core Interrupt register + */ +static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) +{ +	u32 gintsts; +	u32 gintmsk; +	u32 gahbcfg; +	u32 gintmsk_common = GINTMSK_COMMON; + +	gintsts = readl(hsotg->regs + GINTSTS); +	gintmsk = readl(hsotg->regs + GINTMSK); +	gahbcfg = readl(hsotg->regs + GAHBCFG); + +	/* If any common interrupts set */ +	if (gintsts & gintmsk_common) +		dev_dbg(hsotg->dev, "gintsts=%08x  gintmsk=%08x\n", +			gintsts, gintmsk); + +	if (gahbcfg & GAHBCFG_GLBL_INTR_EN) +		return gintsts & gintmsk & gintmsk_common; +	else +		return 0; +} + +/* + * 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 + * - OTG Interrupt + * - Connector ID Status Change Interrupt + * - Disconnect Interrupt + * - Session Request Interrupt + * - Resume / Remote Wakeup Detected Interrupt + * - Suspend Interrupt + */ +irqreturn_t dwc2_handle_common_intr(int irq, void *dev) +{ +	struct dwc2_hsotg *hsotg = dev; +	u32 gintsts; +	irqreturn_t retval = IRQ_NONE; + +	if (!dwc2_is_controller_alive(hsotg)) { +		dev_warn(hsotg->dev, "Controller is dead\n"); +		goto out; +	} + +	spin_lock(&hsotg->lock); + +	gintsts = dwc2_read_common_intr(hsotg); +	if (gintsts & ~GINTSTS_PRTINT) +		retval = IRQ_HANDLED; + +	if (gintsts & GINTSTS_MODEMIS) +		dwc2_handle_mode_mismatch_intr(hsotg); +	if (gintsts & GINTSTS_OTGINT) +		dwc2_handle_otg_intr(hsotg); +	if (gintsts & GINTSTS_CONIDSTSCHNG) +		dwc2_handle_conn_id_status_change_intr(hsotg); +	if (gintsts & GINTSTS_DISCONNINT) +		dwc2_handle_disconnect_intr(hsotg); +	if (gintsts & GINTSTS_SESSREQINT) +		dwc2_handle_session_req_intr(hsotg); +	if (gintsts & GINTSTS_WKUPINT) +		dwc2_handle_wakeup_detected_intr(hsotg); +	if (gintsts & GINTSTS_USBSUSP) +		dwc2_handle_usb_suspend_intr(hsotg); + +	if (gintsts & GINTSTS_PRTINT) { +		/* +		 * The port interrupt occurs while in device mode with HPRT0 +		 * Port Enable/Disable +		 */ +		if (dwc2_is_device_mode(hsotg)) { +			dev_dbg(hsotg->dev, +				" --Port interrupt received in Device mode--\n"); +			dwc2_handle_usb_port_intr(hsotg); +			retval = IRQ_HANDLED; +		} +	} + +	spin_unlock(&hsotg->lock); +out: +	return retval; +} +EXPORT_SYMBOL_GPL(dwc2_handle_common_intr); diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c new file mode 100644 index 00000000000..f3c56a2fed5 --- /dev/null +++ b/drivers/usb/dwc2/gadget.c @@ -0,0 +1,3673 @@ +/** + * linux/drivers/usb/gadget/s3c-hsotg.c + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + *		http://www.samsung.com + * + * Copyright 2008 Openmoko, Inc. + * Copyright 2008 Simtec Electronics + *      Ben Dooks <ben@simtec.co.uk> + *      http://armlinux.simtec.co.uk/ + * + * S3C USB2.0 High-speed / OtG driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/of_platform.h> +#include <linux/phy/phy.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/phy.h> +#include <linux/platform_data/s3c-hsotg.h> + +#include "core.h" + +/* conversion functions */ +static inline struct s3c_hsotg_req *our_req(struct usb_request *req) +{ +	return container_of(req, struct s3c_hsotg_req, req); +} + +static inline struct s3c_hsotg_ep *our_ep(struct usb_ep *ep) +{ +	return container_of(ep, struct s3c_hsotg_ep, ep); +} + +static inline struct s3c_hsotg *to_hsotg(struct usb_gadget *gadget) +{ +	return container_of(gadget, struct s3c_hsotg, gadget); +} + +static inline void __orr32(void __iomem *ptr, u32 val) +{ +	writel(readl(ptr) | val, ptr); +} + +static inline void __bic32(void __iomem *ptr, u32 val) +{ +	writel(readl(ptr) & ~val, ptr); +} + +/* forward decleration of functions */ +static void s3c_hsotg_dump(struct s3c_hsotg *hsotg); + +/** + * using_dma - return the DMA status of the driver. + * @hsotg: The driver state. + * + * Return true if we're using DMA. + * + * Currently, we have the DMA support code worked into everywhere + * that needs it, but the AMBA DMA implementation in the hardware can + * only DMA from 32bit aligned addresses. This means that gadgets such + * as the CDC Ethernet cannot work as they often pass packets which are + * not 32bit aligned. + * + * Unfortunately the choice to use DMA or not is global to the controller + * and seems to be only settable when the controller is being put through + * a core reset. This means we either need to fix the gadgets to take + * account of DMA alignment, or add bounce buffers (yuerk). + * + * Until this issue is sorted out, we always return 'false'. + */ +static inline bool using_dma(struct s3c_hsotg *hsotg) +{ +	return false;	/* support is not complete */ +} + +/** + * s3c_hsotg_en_gsint - enable one or more of the general interrupt + * @hsotg: The device state + * @ints: A bitmask of the interrupts to enable + */ +static void s3c_hsotg_en_gsint(struct s3c_hsotg *hsotg, u32 ints) +{ +	u32 gsintmsk = readl(hsotg->regs + GINTMSK); +	u32 new_gsintmsk; + +	new_gsintmsk = gsintmsk | ints; + +	if (new_gsintmsk != gsintmsk) { +		dev_dbg(hsotg->dev, "gsintmsk now 0x%08x\n", new_gsintmsk); +		writel(new_gsintmsk, hsotg->regs + GINTMSK); +	} +} + +/** + * s3c_hsotg_disable_gsint - disable one or more of the general interrupt + * @hsotg: The device state + * @ints: A bitmask of the interrupts to enable + */ +static void s3c_hsotg_disable_gsint(struct s3c_hsotg *hsotg, u32 ints) +{ +	u32 gsintmsk = readl(hsotg->regs + GINTMSK); +	u32 new_gsintmsk; + +	new_gsintmsk = gsintmsk & ~ints; + +	if (new_gsintmsk != gsintmsk) +		writel(new_gsintmsk, hsotg->regs + GINTMSK); +} + +/** + * s3c_hsotg_ctrl_epint - enable/disable an endpoint irq + * @hsotg: The device state + * @ep: The endpoint index + * @dir_in: True if direction is in. + * @en: The enable value, true to enable + * + * Set or clear the mask for an individual endpoint's interrupt + * request. + */ +static void s3c_hsotg_ctrl_epint(struct s3c_hsotg *hsotg, +				 unsigned int ep, unsigned int dir_in, +				 unsigned int en) +{ +	unsigned long flags; +	u32 bit = 1 << ep; +	u32 daint; + +	if (!dir_in) +		bit <<= 16; + +	local_irq_save(flags); +	daint = readl(hsotg->regs + DAINTMSK); +	if (en) +		daint |= bit; +	else +		daint &= ~bit; +	writel(daint, hsotg->regs + DAINTMSK); +	local_irq_restore(flags); +} + +/** + * s3c_hsotg_init_fifo - initialise non-periodic FIFOs + * @hsotg: The device instance. + */ +static void s3c_hsotg_init_fifo(struct s3c_hsotg *hsotg) +{ +	unsigned int ep; +	unsigned int addr; +	unsigned int size; +	int timeout; +	u32 val; + +	/* set FIFO sizes to 2048/1024 */ + +	writel(2048, hsotg->regs + GRXFSIZ); +	writel((2048 << FIFOSIZE_STARTADDR_SHIFT) | +		(1024 << FIFOSIZE_DEPTH_SHIFT), hsotg->regs + GNPTXFSIZ); + +	/* +	 * arange all the rest of the TX FIFOs, as some versions of this +	 * block have overlapping default addresses. This also ensures +	 * that if the settings have been changed, then they are set to +	 * known values. +	 */ + +	/* start at the end of the GNPTXFSIZ, rounded up */ +	addr = 2048 + 1024; +	size = 768; + +	/* +	 * currently we allocate TX FIFOs for all possible endpoints, +	 * and assume that they are all the same size. +	 */ + +	for (ep = 1; ep <= 15; ep++) { +		val = addr; +		val |= size << FIFOSIZE_DEPTH_SHIFT; +		addr += size; + +		writel(val, hsotg->regs + DPTXFSIZN(ep)); +	} + +	/* +	 * according to p428 of the design guide, we need to ensure that +	 * all fifos are flushed before continuing +	 */ + +	writel(GRSTCTL_TXFNUM(0x10) | GRSTCTL_TXFFLSH | +	       GRSTCTL_RXFFLSH, hsotg->regs + GRSTCTL); + +	/* wait until the fifos are both flushed */ +	timeout = 100; +	while (1) { +		val = readl(hsotg->regs + GRSTCTL); + +		if ((val & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH)) == 0) +			break; + +		if (--timeout == 0) { +			dev_err(hsotg->dev, +				"%s: timeout flushing fifos (GRSTCTL=%08x)\n", +				__func__, val); +		} + +		udelay(1); +	} + +	dev_dbg(hsotg->dev, "FIFOs reset, timeout at %d\n", timeout); +} + +/** + * @ep: USB endpoint to allocate request for. + * @flags: Allocation flags + * + * Allocate a new USB request structure appropriate for the specified endpoint + */ +static struct usb_request *s3c_hsotg_ep_alloc_request(struct usb_ep *ep, +						      gfp_t flags) +{ +	struct s3c_hsotg_req *req; + +	req = kzalloc(sizeof(struct s3c_hsotg_req), flags); +	if (!req) +		return NULL; + +	INIT_LIST_HEAD(&req->queue); + +	return &req->req; +} + +/** + * is_ep_periodic - return true if the endpoint is in periodic mode. + * @hs_ep: The endpoint to query. + * + * Returns true if the endpoint is in periodic mode, meaning it is being + * used for an Interrupt or ISO transfer. + */ +static inline int is_ep_periodic(struct s3c_hsotg_ep *hs_ep) +{ +	return hs_ep->periodic; +} + +/** + * s3c_hsotg_unmap_dma - unmap the DMA memory being used for the request + * @hsotg: The device state. + * @hs_ep: The endpoint for the request + * @hs_req: The request being processed. + * + * This is the reverse of s3c_hsotg_map_dma(), called for the completion + * of a request to ensure the buffer is ready for access by the caller. + */ +static void s3c_hsotg_unmap_dma(struct s3c_hsotg *hsotg, +				struct s3c_hsotg_ep *hs_ep, +				struct s3c_hsotg_req *hs_req) +{ +	struct usb_request *req = &hs_req->req; + +	/* ignore this if we're not moving any data */ +	if (hs_req->req.length == 0) +		return; + +	usb_gadget_unmap_request(&hsotg->gadget, req, hs_ep->dir_in); +} + +/** + * s3c_hsotg_write_fifo - write packet Data to the TxFIFO + * @hsotg: The controller state. + * @hs_ep: The endpoint we're going to write for. + * @hs_req: The request to write data for. + * + * This is called when the TxFIFO has some space in it to hold a new + * transmission and we have something to give it. The actual setup of + * the data size is done elsewhere, so all we have to do is to actually + * write the data. + * + * The return value is zero if there is more space (or nothing was done) + * otherwise -ENOSPC is returned if the FIFO space was used up. + * + * This routine is only needed for PIO + */ +static int s3c_hsotg_write_fifo(struct s3c_hsotg *hsotg, +				struct s3c_hsotg_ep *hs_ep, +				struct s3c_hsotg_req *hs_req) +{ +	bool periodic = is_ep_periodic(hs_ep); +	u32 gnptxsts = readl(hsotg->regs + GNPTXSTS); +	int buf_pos = hs_req->req.actual; +	int to_write = hs_ep->size_loaded; +	void *data; +	int can_write; +	int pkt_round; +	int max_transfer; + +	to_write -= (buf_pos - hs_ep->last_load); + +	/* if there's nothing to write, get out early */ +	if (to_write == 0) +		return 0; + +	if (periodic && !hsotg->dedicated_fifos) { +		u32 epsize = readl(hsotg->regs + DIEPTSIZ(hs_ep->index)); +		int size_left; +		int size_done; + +		/* +		 * work out how much data was loaded so we can calculate +		 * how much data is left in the fifo. +		 */ + +		size_left = DXEPTSIZ_XFERSIZE_GET(epsize); + +		/* +		 * if shared fifo, we cannot write anything until the +		 * previous data has been completely sent. +		 */ +		if (hs_ep->fifo_load != 0) { +			s3c_hsotg_en_gsint(hsotg, GINTSTS_PTXFEMP); +			return -ENOSPC; +		} + +		dev_dbg(hsotg->dev, "%s: left=%d, load=%d, fifo=%d, size %d\n", +			__func__, size_left, +			hs_ep->size_loaded, hs_ep->fifo_load, hs_ep->fifo_size); + +		/* how much of the data has moved */ +		size_done = hs_ep->size_loaded - size_left; + +		/* how much data is left in the fifo */ +		can_write = hs_ep->fifo_load - size_done; +		dev_dbg(hsotg->dev, "%s: => can_write1=%d\n", +			__func__, can_write); + +		can_write = hs_ep->fifo_size - can_write; +		dev_dbg(hsotg->dev, "%s: => can_write2=%d\n", +			__func__, can_write); + +		if (can_write <= 0) { +			s3c_hsotg_en_gsint(hsotg, GINTSTS_PTXFEMP); +			return -ENOSPC; +		} +	} else if (hsotg->dedicated_fifos && hs_ep->index != 0) { +		can_write = readl(hsotg->regs + DTXFSTS(hs_ep->index)); + +		can_write &= 0xffff; +		can_write *= 4; +	} else { +		if (GNPTXSTS_NP_TXQ_SPC_AVAIL_GET(gnptxsts) == 0) { +			dev_dbg(hsotg->dev, +				"%s: no queue slots available (0x%08x)\n", +				__func__, gnptxsts); + +			s3c_hsotg_en_gsint(hsotg, GINTSTS_NPTXFEMP); +			return -ENOSPC; +		} + +		can_write = GNPTXSTS_NP_TXF_SPC_AVAIL_GET(gnptxsts); +		can_write *= 4;	/* fifo size is in 32bit quantities. */ +	} + +	max_transfer = hs_ep->ep.maxpacket * hs_ep->mc; + +	dev_dbg(hsotg->dev, "%s: GNPTXSTS=%08x, can=%d, to=%d, max_transfer %d\n", +		 __func__, gnptxsts, can_write, to_write, max_transfer); + +	/* +	 * limit to 512 bytes of data, it seems at least on the non-periodic +	 * FIFO, requests of >512 cause the endpoint to get stuck with a +	 * fragment of the end of the transfer in it. +	 */ +	if (can_write > 512 && !periodic) +		can_write = 512; + +	/* +	 * limit the write to one max-packet size worth of data, but allow +	 * the transfer to return that it did not run out of fifo space +	 * doing it. +	 */ +	if (to_write > max_transfer) { +		to_write = max_transfer; + +		/* it's needed only when we do not use dedicated fifos */ +		if (!hsotg->dedicated_fifos) +			s3c_hsotg_en_gsint(hsotg, +					   periodic ? GINTSTS_PTXFEMP : +					   GINTSTS_NPTXFEMP); +	} + +	/* see if we can write data */ + +	if (to_write > can_write) { +		to_write = can_write; +		pkt_round = to_write % max_transfer; + +		/* +		 * Round the write down to an +		 * exact number of packets. +		 * +		 * Note, we do not currently check to see if we can ever +		 * write a full packet or not to the FIFO. +		 */ + +		if (pkt_round) +			to_write -= pkt_round; + +		/* +		 * enable correct FIFO interrupt to alert us when there +		 * is more room left. +		 */ + +		/* it's needed only when we do not use dedicated fifos */ +		if (!hsotg->dedicated_fifos) +			s3c_hsotg_en_gsint(hsotg, +					   periodic ? GINTSTS_PTXFEMP : +					   GINTSTS_NPTXFEMP); +	} + +	dev_dbg(hsotg->dev, "write %d/%d, can_write %d, done %d\n", +		 to_write, hs_req->req.length, can_write, buf_pos); + +	if (to_write <= 0) +		return -ENOSPC; + +	hs_req->req.actual = buf_pos + to_write; +	hs_ep->total_data += to_write; + +	if (periodic) +		hs_ep->fifo_load += to_write; + +	to_write = DIV_ROUND_UP(to_write, 4); +	data = hs_req->req.buf + buf_pos; + +	iowrite32_rep(hsotg->regs + EPFIFO(hs_ep->index), data, to_write); + +	return (to_write >= can_write) ? -ENOSPC : 0; +} + +/** + * get_ep_limit - get the maximum data legnth for this endpoint + * @hs_ep: The endpoint + * + * Return the maximum data that can be queued in one go on a given endpoint + * so that transfers that are too long can be split. + */ +static unsigned get_ep_limit(struct s3c_hsotg_ep *hs_ep) +{ +	int index = hs_ep->index; +	unsigned maxsize; +	unsigned maxpkt; + +	if (index != 0) { +		maxsize = DXEPTSIZ_XFERSIZE_LIMIT + 1; +		maxpkt = DXEPTSIZ_PKTCNT_LIMIT + 1; +	} else { +		maxsize = 64+64; +		if (hs_ep->dir_in) +			maxpkt = DIEPTSIZ0_PKTCNT_LIMIT + 1; +		else +			maxpkt = 2; +	} + +	/* we made the constant loading easier above by using +1 */ +	maxpkt--; +	maxsize--; + +	/* +	 * constrain by packet count if maxpkts*pktsize is greater +	 * than the length register size. +	 */ + +	if ((maxpkt * hs_ep->ep.maxpacket) < maxsize) +		maxsize = maxpkt * hs_ep->ep.maxpacket; + +	return maxsize; +} + +/** + * s3c_hsotg_start_req - start a USB request from an endpoint's queue + * @hsotg: The controller state. + * @hs_ep: The endpoint to process a request for + * @hs_req: The request to start. + * @continuing: True if we are doing more for the current request. + * + * Start the given request running by setting the endpoint registers + * appropriately, and writing any data to the FIFOs. + */ +static void s3c_hsotg_start_req(struct s3c_hsotg *hsotg, +				struct s3c_hsotg_ep *hs_ep, +				struct s3c_hsotg_req *hs_req, +				bool continuing) +{ +	struct usb_request *ureq = &hs_req->req; +	int index = hs_ep->index; +	int dir_in = hs_ep->dir_in; +	u32 epctrl_reg; +	u32 epsize_reg; +	u32 epsize; +	u32 ctrl; +	unsigned length; +	unsigned packets; +	unsigned maxreq; + +	if (index != 0) { +		if (hs_ep->req && !continuing) { +			dev_err(hsotg->dev, "%s: active request\n", __func__); +			WARN_ON(1); +			return; +		} else if (hs_ep->req != hs_req && continuing) { +			dev_err(hsotg->dev, +				"%s: continue different req\n", __func__); +			WARN_ON(1); +			return; +		} +	} + +	epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); +	epsize_reg = dir_in ? DIEPTSIZ(index) : DOEPTSIZ(index); + +	dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x, ep %d, dir %s\n", +		__func__, readl(hsotg->regs + epctrl_reg), index, +		hs_ep->dir_in ? "in" : "out"); + +	/* If endpoint is stalled, we will restart request later */ +	ctrl = readl(hsotg->regs + epctrl_reg); + +	if (ctrl & DXEPCTL_STALL) { +		dev_warn(hsotg->dev, "%s: ep%d is stalled\n", __func__, index); +		return; +	} + +	length = ureq->length - ureq->actual; +	dev_dbg(hsotg->dev, "ureq->length:%d ureq->actual:%d\n", +		ureq->length, ureq->actual); +	if (0) +		dev_dbg(hsotg->dev, +			"REQ buf %p len %d dma %pad noi=%d zp=%d snok=%d\n", +			ureq->buf, length, &ureq->dma, +			ureq->no_interrupt, ureq->zero, ureq->short_not_ok); + +	maxreq = get_ep_limit(hs_ep); +	if (length > maxreq) { +		int round = maxreq % hs_ep->ep.maxpacket; + +		dev_dbg(hsotg->dev, "%s: length %d, max-req %d, r %d\n", +			__func__, length, maxreq, round); + +		/* round down to multiple of packets */ +		if (round) +			maxreq -= round; + +		length = maxreq; +	} + +	if (length) +		packets = DIV_ROUND_UP(length, hs_ep->ep.maxpacket); +	else +		packets = 1;	/* send one packet if length is zero. */ + +	if (hs_ep->isochronous && length > (hs_ep->mc * hs_ep->ep.maxpacket)) { +		dev_err(hsotg->dev, "req length > maxpacket*mc\n"); +		return; +	} + +	if (dir_in && index != 0) +		if (hs_ep->isochronous) +			epsize = DXEPTSIZ_MC(packets); +		else +			epsize = DXEPTSIZ_MC(1); +	else +		epsize = 0; + +	if (index != 0 && ureq->zero) { +		/* +		 * test for the packets being exactly right for the +		 * transfer +		 */ + +		if (length == (packets * hs_ep->ep.maxpacket)) +			packets++; +	} + +	epsize |= DXEPTSIZ_PKTCNT(packets); +	epsize |= DXEPTSIZ_XFERSIZE(length); + +	dev_dbg(hsotg->dev, "%s: %d@%d/%d, 0x%08x => 0x%08x\n", +		__func__, packets, length, ureq->length, epsize, epsize_reg); + +	/* store the request as the current one we're doing */ +	hs_ep->req = hs_req; + +	/* write size / packets */ +	writel(epsize, hsotg->regs + epsize_reg); + +	if (using_dma(hsotg) && !continuing) { +		unsigned int dma_reg; + +		/* +		 * write DMA address to control register, buffer already +		 * synced by s3c_hsotg_ep_queue(). +		 */ + +		dma_reg = dir_in ? DIEPDMA(index) : DOEPDMA(index); +		writel(ureq->dma, hsotg->regs + dma_reg); + +		dev_dbg(hsotg->dev, "%s: %pad => 0x%08x\n", +			__func__, &ureq->dma, dma_reg); +	} + +	ctrl |= DXEPCTL_EPENA;	/* ensure ep enabled */ +	ctrl |= DXEPCTL_USBACTEP; + +	dev_dbg(hsotg->dev, "setup req:%d\n", hsotg->setup); + +	/* For Setup request do not clear NAK */ +	if (hsotg->setup && index == 0) +		hsotg->setup = 0; +	else +		ctrl |= DXEPCTL_CNAK;	/* clear NAK set by core */ + + +	dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); +	writel(ctrl, hsotg->regs + epctrl_reg); + +	/* +	 * set these, it seems that DMA support increments past the end +	 * of the packet buffer so we need to calculate the length from +	 * this information. +	 */ +	hs_ep->size_loaded = length; +	hs_ep->last_load = ureq->actual; + +	if (dir_in && !using_dma(hsotg)) { +		/* set these anyway, we may need them for non-periodic in */ +		hs_ep->fifo_load = 0; + +		s3c_hsotg_write_fifo(hsotg, hs_ep, hs_req); +	} + +	/* +	 * clear the INTknTXFEmpMsk when we start request, more as a aide +	 * to debugging to see what is going on. +	 */ +	if (dir_in) +		writel(DIEPMSK_INTKNTXFEMPMSK, +		       hsotg->regs + DIEPINT(index)); + +	/* +	 * Note, trying to clear the NAK here causes problems with transmit +	 * on the S3C6400 ending up with the TXFIFO becoming full. +	 */ + +	/* check ep is enabled */ +	if (!(readl(hsotg->regs + epctrl_reg) & DXEPCTL_EPENA)) +		dev_warn(hsotg->dev, +			 "ep%d: failed to become enabled (DXEPCTL=0x%08x)?\n", +			 index, readl(hsotg->regs + epctrl_reg)); + +	dev_dbg(hsotg->dev, "%s: DXEPCTL=0x%08x\n", +		__func__, readl(hsotg->regs + epctrl_reg)); + +	/* enable ep interrupts */ +	s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 1); +} + +/** + * s3c_hsotg_map_dma - map the DMA memory being used for the request + * @hsotg: The device state. + * @hs_ep: The endpoint the request is on. + * @req: The request being processed. + * + * We've been asked to queue a request, so ensure that the memory buffer + * is correctly setup for DMA. If we've been passed an extant DMA address + * then ensure the buffer has been synced to memory. If our buffer has no + * DMA memory, then we map the memory and mark our request to allow us to + * cleanup on completion. + */ +static int s3c_hsotg_map_dma(struct s3c_hsotg *hsotg, +			     struct s3c_hsotg_ep *hs_ep, +			     struct usb_request *req) +{ +	struct s3c_hsotg_req *hs_req = our_req(req); +	int ret; + +	/* if the length is zero, ignore the DMA data */ +	if (hs_req->req.length == 0) +		return 0; + +	ret = usb_gadget_map_request(&hsotg->gadget, req, hs_ep->dir_in); +	if (ret) +		goto dma_error; + +	return 0; + +dma_error: +	dev_err(hsotg->dev, "%s: failed to map buffer %p, %d bytes\n", +		__func__, req->buf, req->length); + +	return -EIO; +} + +static int s3c_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, +			      gfp_t gfp_flags) +{ +	struct s3c_hsotg_req *hs_req = our_req(req); +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hs = hs_ep->parent; +	bool first; + +	dev_dbg(hs->dev, "%s: req %p: %d@%p, noi=%d, zero=%d, snok=%d\n", +		ep->name, req, req->length, req->buf, req->no_interrupt, +		req->zero, req->short_not_ok); + +	/* initialise status of the request */ +	INIT_LIST_HEAD(&hs_req->queue); +	req->actual = 0; +	req->status = -EINPROGRESS; + +	/* if we're using DMA, sync the buffers as necessary */ +	if (using_dma(hs)) { +		int ret = s3c_hsotg_map_dma(hs, hs_ep, req); +		if (ret) +			return ret; +	} + +	first = list_empty(&hs_ep->queue); +	list_add_tail(&hs_req->queue, &hs_ep->queue); + +	if (first) +		s3c_hsotg_start_req(hs, hs_ep, hs_req, false); + +	return 0; +} + +static int s3c_hsotg_ep_queue_lock(struct usb_ep *ep, struct usb_request *req, +			      gfp_t gfp_flags) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hs = hs_ep->parent; +	unsigned long flags = 0; +	int ret = 0; + +	spin_lock_irqsave(&hs->lock, flags); +	ret = s3c_hsotg_ep_queue(ep, req, gfp_flags); +	spin_unlock_irqrestore(&hs->lock, flags); + +	return ret; +} + +static void s3c_hsotg_ep_free_request(struct usb_ep *ep, +				      struct usb_request *req) +{ +	struct s3c_hsotg_req *hs_req = our_req(req); + +	kfree(hs_req); +} + +/** + * s3c_hsotg_complete_oursetup - setup completion callback + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself + * submitted that need cleaning up. + */ +static void s3c_hsotg_complete_oursetup(struct usb_ep *ep, +					struct usb_request *req) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hsotg = hs_ep->parent; + +	dev_dbg(hsotg->dev, "%s: ep %p, req %p\n", __func__, ep, req); + +	s3c_hsotg_ep_free_request(ep, req); +} + +/** + * ep_from_windex - convert control wIndex value to endpoint + * @hsotg: The driver state. + * @windex: The control request wIndex field (in host order). + * + * Convert the given wIndex into a pointer to an driver endpoint + * structure, or return NULL if it is not a valid endpoint. + */ +static struct s3c_hsotg_ep *ep_from_windex(struct s3c_hsotg *hsotg, +					   u32 windex) +{ +	struct s3c_hsotg_ep *ep = &hsotg->eps[windex & 0x7F]; +	int dir = (windex & USB_DIR_IN) ? 1 : 0; +	int idx = windex & 0x7F; + +	if (windex >= 0x100) +		return NULL; + +	if (idx > hsotg->num_of_eps) +		return NULL; + +	if (idx && ep->dir_in != dir) +		return NULL; + +	return ep; +} + +/** + * s3c_hsotg_send_reply - send reply to control request + * @hsotg: The device state + * @ep: Endpoint 0 + * @buff: Buffer for request + * @length: Length of reply. + * + * Create a request and queue it on the given endpoint. This is useful as + * an internal method of sending replies to certain control requests, etc. + */ +static int s3c_hsotg_send_reply(struct s3c_hsotg *hsotg, +				struct s3c_hsotg_ep *ep, +				void *buff, +				int length) +{ +	struct usb_request *req; +	int ret; + +	dev_dbg(hsotg->dev, "%s: buff %p, len %d\n", __func__, buff, length); + +	req = s3c_hsotg_ep_alloc_request(&ep->ep, GFP_ATOMIC); +	hsotg->ep0_reply = req; +	if (!req) { +		dev_warn(hsotg->dev, "%s: cannot alloc req\n", __func__); +		return -ENOMEM; +	} + +	req->buf = hsotg->ep0_buff; +	req->length = length; +	req->zero = 1; /* always do zero-length final transfer */ +	req->complete = s3c_hsotg_complete_oursetup; + +	if (length) +		memcpy(req->buf, buff, length); +	else +		ep->sent_zlp = 1; + +	ret = s3c_hsotg_ep_queue(&ep->ep, req, GFP_ATOMIC); +	if (ret) { +		dev_warn(hsotg->dev, "%s: cannot queue req\n", __func__); +		return ret; +	} + +	return 0; +} + +/** + * s3c_hsotg_process_req_status - process request GET_STATUS + * @hsotg: The device state + * @ctrl: USB control request + */ +static int s3c_hsotg_process_req_status(struct s3c_hsotg *hsotg, +					struct usb_ctrlrequest *ctrl) +{ +	struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; +	struct s3c_hsotg_ep *ep; +	__le16 reply; +	int ret; + +	dev_dbg(hsotg->dev, "%s: USB_REQ_GET_STATUS\n", __func__); + +	if (!ep0->dir_in) { +		dev_warn(hsotg->dev, "%s: direction out?\n", __func__); +		return -EINVAL; +	} + +	switch (ctrl->bRequestType & USB_RECIP_MASK) { +	case USB_RECIP_DEVICE: +		reply = cpu_to_le16(0); /* bit 0 => self powered, +					 * bit 1 => remote wakeup */ +		break; + +	case USB_RECIP_INTERFACE: +		/* currently, the data result should be zero */ +		reply = cpu_to_le16(0); +		break; + +	case USB_RECIP_ENDPOINT: +		ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); +		if (!ep) +			return -ENOENT; + +		reply = cpu_to_le16(ep->halted ? 1 : 0); +		break; + +	default: +		return 0; +	} + +	if (le16_to_cpu(ctrl->wLength) != 2) +		return -EINVAL; + +	ret = s3c_hsotg_send_reply(hsotg, ep0, &reply, 2); +	if (ret) { +		dev_err(hsotg->dev, "%s: failed to send reply\n", __func__); +		return ret; +	} + +	return 1; +} + +static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value); + +/** + * get_ep_head - return the first request on the endpoint + * @hs_ep: The controller endpoint to get + * + * Get the first request on the endpoint. + */ +static struct s3c_hsotg_req *get_ep_head(struct s3c_hsotg_ep *hs_ep) +{ +	if (list_empty(&hs_ep->queue)) +		return NULL; + +	return list_first_entry(&hs_ep->queue, struct s3c_hsotg_req, queue); +} + +/** + * s3c_hsotg_process_req_featire - process request {SET,CLEAR}_FEATURE + * @hsotg: The device state + * @ctrl: USB control request + */ +static int s3c_hsotg_process_req_feature(struct s3c_hsotg *hsotg, +					 struct usb_ctrlrequest *ctrl) +{ +	struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; +	struct s3c_hsotg_req *hs_req; +	bool restart; +	bool set = (ctrl->bRequest == USB_REQ_SET_FEATURE); +	struct s3c_hsotg_ep *ep; +	int ret; +	bool halted; + +	dev_dbg(hsotg->dev, "%s: %s_FEATURE\n", +		__func__, set ? "SET" : "CLEAR"); + +	if (ctrl->bRequestType == USB_RECIP_ENDPOINT) { +		ep = ep_from_windex(hsotg, le16_to_cpu(ctrl->wIndex)); +		if (!ep) { +			dev_dbg(hsotg->dev, "%s: no endpoint for 0x%04x\n", +				__func__, le16_to_cpu(ctrl->wIndex)); +			return -ENOENT; +		} + +		switch (le16_to_cpu(ctrl->wValue)) { +		case USB_ENDPOINT_HALT: +			halted = ep->halted; + +			s3c_hsotg_ep_sethalt(&ep->ep, set); + +			ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); +			if (ret) { +				dev_err(hsotg->dev, +					"%s: failed to send reply\n", __func__); +				return ret; +			} + +			/* +			 * we have to complete all requests for ep if it was +			 * halted, and the halt was cleared by CLEAR_FEATURE +			 */ + +			if (!set && halted) { +				/* +				 * If we have request in progress, +				 * then complete it +				 */ +				if (ep->req) { +					hs_req = ep->req; +					ep->req = NULL; +					list_del_init(&hs_req->queue); +					hs_req->req.complete(&ep->ep, +							     &hs_req->req); +				} + +				/* If we have pending request, then start it */ +				restart = !list_empty(&ep->queue); +				if (restart) { +					hs_req = get_ep_head(ep); +					s3c_hsotg_start_req(hsotg, ep, +							    hs_req, false); +				} +			} + +			break; + +		default: +			return -ENOENT; +		} +	} else +		return -ENOENT;  /* currently only deal with endpoint */ + +	return 1; +} + +static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg); +static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg); + +/** + * s3c_hsotg_stall_ep0 - stall ep0 + * @hsotg: The device state + * + * Set stall for ep0 as response for setup request. + */ +static void s3c_hsotg_stall_ep0(struct s3c_hsotg *hsotg) { +	struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; +	u32 reg; +	u32 ctrl; + +	dev_dbg(hsotg->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); +	reg = (ep0->dir_in) ? DIEPCTL0 : DOEPCTL0; + +	/* +	 * DxEPCTL_Stall will be cleared by EP once it has +	 * taken effect, so no need to clear later. +	 */ + +	ctrl = readl(hsotg->regs + reg); +	ctrl |= DXEPCTL_STALL; +	ctrl |= DXEPCTL_CNAK; +	writel(ctrl, hsotg->regs + reg); + +	dev_dbg(hsotg->dev, +		"written DXEPCTL=0x%08x to %08x (DXEPCTL=0x%08x)\n", +		ctrl, reg, readl(hsotg->regs + reg)); + +	 /* +	  * complete won't be called, so we enqueue +	  * setup request here +	  */ +	 s3c_hsotg_enqueue_setup(hsotg); +} + +/** + * s3c_hsotg_process_control - process a control request + * @hsotg: The device state + * @ctrl: The control request received + * + * The controller has received the SETUP phase of a control request, and + * needs to work out what to do next (and whether to pass it on to the + * gadget driver). + */ +static void s3c_hsotg_process_control(struct s3c_hsotg *hsotg, +				      struct usb_ctrlrequest *ctrl) +{ +	struct s3c_hsotg_ep *ep0 = &hsotg->eps[0]; +	int ret = 0; +	u32 dcfg; + +	ep0->sent_zlp = 0; + +	dev_dbg(hsotg->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", +		 ctrl->bRequest, ctrl->bRequestType, +		 ctrl->wValue, ctrl->wLength); + +	/* +	 * record the direction of the request, for later use when enquing +	 * packets onto EP0. +	 */ + +	ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; +	dev_dbg(hsotg->dev, "ctrl: dir_in=%d\n", ep0->dir_in); + +	/* +	 * if we've no data with this request, then the last part of the +	 * transaction is going to implicitly be IN. +	 */ +	if (ctrl->wLength == 0) +		ep0->dir_in = 1; + +	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { +		switch (ctrl->bRequest) { +		case USB_REQ_SET_ADDRESS: +			s3c_hsotg_disconnect(hsotg); +			dcfg = readl(hsotg->regs + DCFG); +			dcfg &= ~DCFG_DEVADDR_MASK; +			dcfg |= (le16_to_cpu(ctrl->wValue) << +				 DCFG_DEVADDR_SHIFT) & DCFG_DEVADDR_MASK; +			writel(dcfg, hsotg->regs + DCFG); + +			dev_info(hsotg->dev, "new address %d\n", ctrl->wValue); + +			ret = s3c_hsotg_send_reply(hsotg, ep0, NULL, 0); +			return; + +		case USB_REQ_GET_STATUS: +			ret = s3c_hsotg_process_req_status(hsotg, ctrl); +			break; + +		case USB_REQ_CLEAR_FEATURE: +		case USB_REQ_SET_FEATURE: +			ret = s3c_hsotg_process_req_feature(hsotg, ctrl); +			break; +		} +	} + +	/* as a fallback, try delivering it to the driver to deal with */ + +	if (ret == 0 && hsotg->driver) { +		spin_unlock(&hsotg->lock); +		ret = hsotg->driver->setup(&hsotg->gadget, ctrl); +		spin_lock(&hsotg->lock); +		if (ret < 0) +			dev_dbg(hsotg->dev, "driver->setup() ret %d\n", ret); +	} + +	/* +	 * the request is either unhandlable, or is not formatted correctly +	 * so respond with a STALL for the status stage to indicate failure. +	 */ + +	if (ret < 0) +		s3c_hsotg_stall_ep0(hsotg); +} + +/** + * s3c_hsotg_complete_setup - completion of a setup transfer + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself submitted for + * EP0 setup packets + */ +static void s3c_hsotg_complete_setup(struct usb_ep *ep, +				     struct usb_request *req) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hsotg = hs_ep->parent; + +	if (req->status < 0) { +		dev_dbg(hsotg->dev, "%s: failed %d\n", __func__, req->status); +		return; +	} + +	spin_lock(&hsotg->lock); +	if (req->actual == 0) +		s3c_hsotg_enqueue_setup(hsotg); +	else +		s3c_hsotg_process_control(hsotg, req->buf); +	spin_unlock(&hsotg->lock); +} + +/** + * s3c_hsotg_enqueue_setup - start a request for EP0 packets + * @hsotg: The device state. + * + * Enqueue a request on EP0 if necessary to received any SETUP packets + * received from the host. + */ +static void s3c_hsotg_enqueue_setup(struct s3c_hsotg *hsotg) +{ +	struct usb_request *req = hsotg->ctrl_req; +	struct s3c_hsotg_req *hs_req = our_req(req); +	int ret; + +	dev_dbg(hsotg->dev, "%s: queueing setup request\n", __func__); + +	req->zero = 0; +	req->length = 8; +	req->buf = hsotg->ctrl_buff; +	req->complete = s3c_hsotg_complete_setup; + +	if (!list_empty(&hs_req->queue)) { +		dev_dbg(hsotg->dev, "%s already queued???\n", __func__); +		return; +	} + +	hsotg->eps[0].dir_in = 0; + +	ret = s3c_hsotg_ep_queue(&hsotg->eps[0].ep, req, GFP_ATOMIC); +	if (ret < 0) { +		dev_err(hsotg->dev, "%s: failed queue (%d)\n", __func__, ret); +		/* +		 * Don't think there's much we can do other than watch the +		 * driver fail. +		 */ +	} +} + +/** + * s3c_hsotg_complete_request - complete a request given to us + * @hsotg: The device state. + * @hs_ep: The endpoint the request was on. + * @hs_req: The request to complete. + * @result: The result code (0 => Ok, otherwise errno) + * + * The given request has finished, so call the necessary completion + * if it has one and then look to see if we can start a new request + * on the endpoint. + * + * Note, expects the ep to already be locked as appropriate. + */ +static void s3c_hsotg_complete_request(struct s3c_hsotg *hsotg, +				       struct s3c_hsotg_ep *hs_ep, +				       struct s3c_hsotg_req *hs_req, +				       int result) +{ +	bool restart; + +	if (!hs_req) { +		dev_dbg(hsotg->dev, "%s: nothing to complete?\n", __func__); +		return; +	} + +	dev_dbg(hsotg->dev, "complete: ep %p %s, req %p, %d => %p\n", +		hs_ep, hs_ep->ep.name, hs_req, result, hs_req->req.complete); + +	/* +	 * only replace the status if we've not already set an error +	 * from a previous transaction +	 */ + +	if (hs_req->req.status == -EINPROGRESS) +		hs_req->req.status = result; + +	hs_ep->req = NULL; +	list_del_init(&hs_req->queue); + +	if (using_dma(hsotg)) +		s3c_hsotg_unmap_dma(hsotg, hs_ep, hs_req); + +	/* +	 * call the complete request with the locks off, just in case the +	 * request tries to queue more work for this endpoint. +	 */ + +	if (hs_req->req.complete) { +		spin_unlock(&hsotg->lock); +		hs_req->req.complete(&hs_ep->ep, &hs_req->req); +		spin_lock(&hsotg->lock); +	} + +	/* +	 * Look to see if there is anything else to do. Note, the completion +	 * of the previous request may have caused a new request to be started +	 * so be careful when doing this. +	 */ + +	if (!hs_ep->req && result >= 0) { +		restart = !list_empty(&hs_ep->queue); +		if (restart) { +			hs_req = get_ep_head(hs_ep); +			s3c_hsotg_start_req(hsotg, hs_ep, hs_req, false); +		} +	} +} + +/** + * s3c_hsotg_rx_data - receive data from the FIFO for an endpoint + * @hsotg: The device state. + * @ep_idx: The endpoint index for the data + * @size: The size of data in the fifo, in bytes + * + * The FIFO status shows there is data to read from the FIFO for a given + * endpoint, so sort out whether we need to read the data into a request + * that has been made for that endpoint. + */ +static void s3c_hsotg_rx_data(struct s3c_hsotg *hsotg, int ep_idx, int size) +{ +	struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep_idx]; +	struct s3c_hsotg_req *hs_req = hs_ep->req; +	void __iomem *fifo = hsotg->regs + EPFIFO(ep_idx); +	int to_read; +	int max_req; +	int read_ptr; + + +	if (!hs_req) { +		u32 epctl = readl(hsotg->regs + DOEPCTL(ep_idx)); +		int ptr; + +		dev_warn(hsotg->dev, +			 "%s: FIFO %d bytes on ep%d but no req (DXEPCTl=0x%08x)\n", +			 __func__, size, ep_idx, epctl); + +		/* dump the data from the FIFO, we've nothing we can do */ +		for (ptr = 0; ptr < size; ptr += 4) +			(void)readl(fifo); + +		return; +	} + +	to_read = size; +	read_ptr = hs_req->req.actual; +	max_req = hs_req->req.length - read_ptr; + +	dev_dbg(hsotg->dev, "%s: read %d/%d, done %d/%d\n", +		__func__, to_read, max_req, read_ptr, hs_req->req.length); + +	if (to_read > max_req) { +		/* +		 * more data appeared than we where willing +		 * to deal with in this request. +		 */ + +		/* currently we don't deal this */ +		WARN_ON_ONCE(1); +	} + +	hs_ep->total_data += to_read; +	hs_req->req.actual += to_read; +	to_read = DIV_ROUND_UP(to_read, 4); + +	/* +	 * note, we might over-write the buffer end by 3 bytes depending on +	 * alignment of the data. +	 */ +	ioread32_rep(fifo, hs_req->req.buf + read_ptr, to_read); +} + +/** + * s3c_hsotg_send_zlp - send zero-length packet on control endpoint + * @hsotg: The device instance + * @req: The request currently on this endpoint + * + * Generate a zero-length IN packet request for terminating a SETUP + * transaction. + * + * Note, since we don't write any data to the TxFIFO, then it is + * currently believed that we do not need to wait for any space in + * the TxFIFO. + */ +static void s3c_hsotg_send_zlp(struct s3c_hsotg *hsotg, +			       struct s3c_hsotg_req *req) +{ +	u32 ctrl; + +	if (!req) { +		dev_warn(hsotg->dev, "%s: no request?\n", __func__); +		return; +	} + +	if (req->req.length == 0) { +		hsotg->eps[0].sent_zlp = 1; +		s3c_hsotg_enqueue_setup(hsotg); +		return; +	} + +	hsotg->eps[0].dir_in = 1; +	hsotg->eps[0].sent_zlp = 1; + +	dev_dbg(hsotg->dev, "sending zero-length packet\n"); + +	/* issue a zero-sized packet to terminate this */ +	writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | +	       DXEPTSIZ_XFERSIZE(0), hsotg->regs + DIEPTSIZ(0)); + +	ctrl = readl(hsotg->regs + DIEPCTL0); +	ctrl |= DXEPCTL_CNAK;  /* clear NAK set by core */ +	ctrl |= DXEPCTL_EPENA; /* ensure ep enabled */ +	ctrl |= DXEPCTL_USBACTEP; +	writel(ctrl, hsotg->regs + DIEPCTL0); +} + +/** + * s3c_hsotg_handle_outdone - handle receiving OutDone/SetupDone from RXFIFO + * @hsotg: The device instance + * @epnum: The endpoint received from + * @was_setup: Set if processing a SetupDone event. + * + * The RXFIFO has delivered an OutDone event, which means that the data + * transfer for an OUT endpoint has been completed, either by a short + * packet or by the finish of a transfer. + */ +static void s3c_hsotg_handle_outdone(struct s3c_hsotg *hsotg, +				     int epnum, bool was_setup) +{ +	u32 epsize = readl(hsotg->regs + DOEPTSIZ(epnum)); +	struct s3c_hsotg_ep *hs_ep = &hsotg->eps[epnum]; +	struct s3c_hsotg_req *hs_req = hs_ep->req; +	struct usb_request *req = &hs_req->req; +	unsigned size_left = DXEPTSIZ_XFERSIZE_GET(epsize); +	int result = 0; + +	if (!hs_req) { +		dev_dbg(hsotg->dev, "%s: no request active\n", __func__); +		return; +	} + +	if (using_dma(hsotg)) { +		unsigned size_done; + +		/* +		 * Calculate the size of the transfer by checking how much +		 * is left in the endpoint size register and then working it +		 * out from the amount we loaded for the transfer. +		 * +		 * We need to do this as DMA pointers are always 32bit aligned +		 * so may overshoot/undershoot the transfer. +		 */ + +		size_done = hs_ep->size_loaded - size_left; +		size_done += hs_ep->last_load; + +		req->actual = size_done; +	} + +	/* if there is more request to do, schedule new transfer */ +	if (req->actual < req->length && size_left == 0) { +		s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); +		return; +	} else if (epnum == 0) { +		/* +		 * After was_setup = 1 => +		 * set CNAK for non Setup requests +		 */ +		hsotg->setup = was_setup ? 0 : 1; +	} + +	if (req->actual < req->length && req->short_not_ok) { +		dev_dbg(hsotg->dev, "%s: got %d/%d (short not ok) => error\n", +			__func__, req->actual, req->length); + +		/* +		 * todo - what should we return here? there's no one else +		 * even bothering to check the status. +		 */ +	} + +	if (epnum == 0) { +		/* +		 * Condition req->complete != s3c_hsotg_complete_setup says: +		 * send ZLP when we have an asynchronous request from gadget +		 */ +		if (!was_setup && req->complete != s3c_hsotg_complete_setup) +			s3c_hsotg_send_zlp(hsotg, hs_req); +	} + +	s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, result); +} + +/** + * s3c_hsotg_read_frameno - read current frame number + * @hsotg: The device instance + * + * Return the current frame number + */ +static u32 s3c_hsotg_read_frameno(struct s3c_hsotg *hsotg) +{ +	u32 dsts; + +	dsts = readl(hsotg->regs + DSTS); +	dsts &= DSTS_SOFFN_MASK; +	dsts >>= DSTS_SOFFN_SHIFT; + +	return dsts; +} + +/** + * s3c_hsotg_handle_rx - RX FIFO has data + * @hsotg: The device instance + * + * The IRQ handler has detected that the RX FIFO has some data in it + * that requires processing, so find out what is in there and do the + * appropriate read. + * + * The RXFIFO is a true FIFO, the packets coming out are still in packet + * chunks, so if you have x packets received on an endpoint you'll get x + * FIFO events delivered, each with a packet's worth of data in it. + * + * When using DMA, we should not be processing events from the RXFIFO + * as the actual data should be sent to the memory directly and we turn + * on the completion interrupts to get notifications of transfer completion. + */ +static void s3c_hsotg_handle_rx(struct s3c_hsotg *hsotg) +{ +	u32 grxstsr = readl(hsotg->regs + GRXSTSP); +	u32 epnum, status, size; + +	WARN_ON(using_dma(hsotg)); + +	epnum = grxstsr & GRXSTS_EPNUM_MASK; +	status = grxstsr & GRXSTS_PKTSTS_MASK; + +	size = grxstsr & GRXSTS_BYTECNT_MASK; +	size >>= GRXSTS_BYTECNT_SHIFT; + +	if (1) +		dev_dbg(hsotg->dev, "%s: GRXSTSP=0x%08x (%d@%d)\n", +			__func__, grxstsr, size, epnum); + +	switch ((status & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT) { +	case GRXSTS_PKTSTS_GLOBALOUTNAK: +		dev_dbg(hsotg->dev, "GLOBALOUTNAK\n"); +		break; + +	case GRXSTS_PKTSTS_OUTDONE: +		dev_dbg(hsotg->dev, "OutDone (Frame=0x%08x)\n", +			s3c_hsotg_read_frameno(hsotg)); + +		if (!using_dma(hsotg)) +			s3c_hsotg_handle_outdone(hsotg, epnum, false); +		break; + +	case GRXSTS_PKTSTS_SETUPDONE: +		dev_dbg(hsotg->dev, +			"SetupDone (Frame=0x%08x, DOPEPCTL=0x%08x)\n", +			s3c_hsotg_read_frameno(hsotg), +			readl(hsotg->regs + DOEPCTL(0))); + +		s3c_hsotg_handle_outdone(hsotg, epnum, true); +		break; + +	case GRXSTS_PKTSTS_OUTRX: +		s3c_hsotg_rx_data(hsotg, epnum, size); +		break; + +	case GRXSTS_PKTSTS_SETUPRX: +		dev_dbg(hsotg->dev, +			"SetupRX (Frame=0x%08x, DOPEPCTL=0x%08x)\n", +			s3c_hsotg_read_frameno(hsotg), +			readl(hsotg->regs + DOEPCTL(0))); + +		s3c_hsotg_rx_data(hsotg, epnum, size); +		break; + +	default: +		dev_warn(hsotg->dev, "%s: unknown status %08x\n", +			 __func__, grxstsr); + +		s3c_hsotg_dump(hsotg); +		break; +	} +} + +/** + * s3c_hsotg_ep0_mps - turn max packet size into register setting + * @mps: The maximum packet size in bytes. + */ +static u32 s3c_hsotg_ep0_mps(unsigned int mps) +{ +	switch (mps) { +	case 64: +		return D0EPCTL_MPS_64; +	case 32: +		return D0EPCTL_MPS_32; +	case 16: +		return D0EPCTL_MPS_16; +	case 8: +		return D0EPCTL_MPS_8; +	} + +	/* bad max packet size, warn and return invalid result */ +	WARN_ON(1); +	return (u32)-1; +} + +/** + * s3c_hsotg_set_ep_maxpacket - set endpoint's max-packet field + * @hsotg: The driver state. + * @ep: The index number of the endpoint + * @mps: The maximum packet size in bytes + * + * Configure the maximum packet size for the given endpoint, updating + * the hardware control registers to reflect this. + */ +static void s3c_hsotg_set_ep_maxpacket(struct s3c_hsotg *hsotg, +				       unsigned int ep, unsigned int mps) +{ +	struct s3c_hsotg_ep *hs_ep = &hsotg->eps[ep]; +	void __iomem *regs = hsotg->regs; +	u32 mpsval; +	u32 mcval; +	u32 reg; + +	if (ep == 0) { +		/* EP0 is a special case */ +		mpsval = s3c_hsotg_ep0_mps(mps); +		if (mpsval > 3) +			goto bad_mps; +		hs_ep->ep.maxpacket = mps; +		hs_ep->mc = 1; +	} else { +		mpsval = mps & DXEPCTL_MPS_MASK; +		if (mpsval > 1024) +			goto bad_mps; +		mcval = ((mps >> 11) & 0x3) + 1; +		hs_ep->mc = mcval; +		if (mcval > 3) +			goto bad_mps; +		hs_ep->ep.maxpacket = mpsval; +	} + +	/* +	 * update both the in and out endpoint controldir_ registers, even +	 * if one of the directions may not be in use. +	 */ + +	reg = readl(regs + DIEPCTL(ep)); +	reg &= ~DXEPCTL_MPS_MASK; +	reg |= mpsval; +	writel(reg, regs + DIEPCTL(ep)); + +	if (ep) { +		reg = readl(regs + DOEPCTL(ep)); +		reg &= ~DXEPCTL_MPS_MASK; +		reg |= mpsval; +		writel(reg, regs + DOEPCTL(ep)); +	} + +	return; + +bad_mps: +	dev_err(hsotg->dev, "ep%d: bad mps of %d\n", ep, mps); +} + +/** + * s3c_hsotg_txfifo_flush - flush Tx FIFO + * @hsotg: The driver state + * @idx: The index for the endpoint (0..15) + */ +static void s3c_hsotg_txfifo_flush(struct s3c_hsotg *hsotg, unsigned int idx) +{ +	int timeout; +	int val; + +	writel(GRSTCTL_TXFNUM(idx) | GRSTCTL_TXFFLSH, +		hsotg->regs + GRSTCTL); + +	/* wait until the fifo is flushed */ +	timeout = 100; + +	while (1) { +		val = readl(hsotg->regs + GRSTCTL); + +		if ((val & (GRSTCTL_TXFFLSH)) == 0) +			break; + +		if (--timeout == 0) { +			dev_err(hsotg->dev, +				"%s: timeout flushing fifo (GRSTCTL=%08x)\n", +				__func__, val); +		} + +		udelay(1); +	} +} + +/** + * s3c_hsotg_trytx - check to see if anything needs transmitting + * @hsotg: The driver state + * @hs_ep: The driver endpoint to check. + * + * Check to see if there is a request that has data to send, and if so + * make an attempt to write data into the FIFO. + */ +static int s3c_hsotg_trytx(struct s3c_hsotg *hsotg, +			   struct s3c_hsotg_ep *hs_ep) +{ +	struct s3c_hsotg_req *hs_req = hs_ep->req; + +	if (!hs_ep->dir_in || !hs_req) { +		/** +		 * if request is not enqueued, we disable interrupts +		 * for endpoints, excepting ep0 +		 */ +		if (hs_ep->index != 0) +			s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, +					     hs_ep->dir_in, 0); +		return 0; +	} + +	if (hs_req->req.actual < hs_req->req.length) { +		dev_dbg(hsotg->dev, "trying to write more for ep%d\n", +			hs_ep->index); +		return s3c_hsotg_write_fifo(hsotg, hs_ep, hs_req); +	} + +	return 0; +} + +/** + * s3c_hsotg_complete_in - complete IN transfer + * @hsotg: The device state. + * @hs_ep: The endpoint that has just completed. + * + * An IN transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void s3c_hsotg_complete_in(struct s3c_hsotg *hsotg, +				  struct s3c_hsotg_ep *hs_ep) +{ +	struct s3c_hsotg_req *hs_req = hs_ep->req; +	u32 epsize = readl(hsotg->regs + DIEPTSIZ(hs_ep->index)); +	int size_left, size_done; + +	if (!hs_req) { +		dev_dbg(hsotg->dev, "XferCompl but no req\n"); +		return; +	} + +	/* Finish ZLP handling for IN EP0 transactions */ +	if (hsotg->eps[0].sent_zlp) { +		dev_dbg(hsotg->dev, "zlp packet received\n"); +		s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); +		return; +	} + +	/* +	 * Calculate the size of the transfer by checking how much is left +	 * in the endpoint size register and then working it out from +	 * the amount we loaded for the transfer. +	 * +	 * We do this even for DMA, as the transfer may have incremented +	 * past the end of the buffer (DMA transfers are always 32bit +	 * aligned). +	 */ + +	size_left = DXEPTSIZ_XFERSIZE_GET(epsize); + +	size_done = hs_ep->size_loaded - size_left; +	size_done += hs_ep->last_load; + +	if (hs_req->req.actual != size_done) +		dev_dbg(hsotg->dev, "%s: adjusting size done %d => %d\n", +			__func__, hs_req->req.actual, size_done); + +	hs_req->req.actual = size_done; +	dev_dbg(hsotg->dev, "req->length:%d req->actual:%d req->zero:%d\n", +		hs_req->req.length, hs_req->req.actual, hs_req->req.zero); + +	/* +	 * Check if dealing with Maximum Packet Size(MPS) IN transfer at EP0 +	 * When sent data is a multiple MPS size (e.g. 64B ,128B ,192B +	 * ,256B ... ), after last MPS sized packet send IN ZLP packet to +	 * inform the host that no more data is available. +	 * The state of req.zero member is checked to be sure that the value to +	 * send is smaller than wValue expected from host. +	 * Check req.length to NOT send another ZLP when the current one is +	 * under completion (the one for which this completion has been called). +	 */ +	if (hs_req->req.length && hs_ep->index == 0 && hs_req->req.zero && +	    hs_req->req.length == hs_req->req.actual && +	    !(hs_req->req.length % hs_ep->ep.maxpacket)) { + +		dev_dbg(hsotg->dev, "ep0 zlp IN packet sent\n"); +		s3c_hsotg_send_zlp(hsotg, hs_req); + +		return; +	} + +	if (!size_left && hs_req->req.actual < hs_req->req.length) { +		dev_dbg(hsotg->dev, "%s trying more for req...\n", __func__); +		s3c_hsotg_start_req(hsotg, hs_ep, hs_req, true); +	} else +		s3c_hsotg_complete_request(hsotg, hs_ep, hs_req, 0); +} + +/** + * s3c_hsotg_epint - handle an in/out endpoint interrupt + * @hsotg: The driver state + * @idx: The index for the endpoint (0..15) + * @dir_in: Set if this is an IN endpoint + * + * Process and clear any interrupt pending for an individual endpoint + */ +static void s3c_hsotg_epint(struct s3c_hsotg *hsotg, unsigned int idx, +			    int dir_in) +{ +	struct s3c_hsotg_ep *hs_ep = &hsotg->eps[idx]; +	u32 epint_reg = dir_in ? DIEPINT(idx) : DOEPINT(idx); +	u32 epctl_reg = dir_in ? DIEPCTL(idx) : DOEPCTL(idx); +	u32 epsiz_reg = dir_in ? DIEPTSIZ(idx) : DOEPTSIZ(idx); +	u32 ints; +	u32 ctrl; + +	ints = readl(hsotg->regs + epint_reg); +	ctrl = readl(hsotg->regs + epctl_reg); + +	/* Clear endpoint interrupts */ +	writel(ints, hsotg->regs + epint_reg); + +	dev_dbg(hsotg->dev, "%s: ep%d(%s) DxEPINT=0x%08x\n", +		__func__, idx, dir_in ? "in" : "out", ints); + +	if (ints & DXEPINT_XFERCOMPL) { +		if (hs_ep->isochronous && hs_ep->interval == 1) { +			if (ctrl & DXEPCTL_EOFRNUM) +				ctrl |= DXEPCTL_SETEVENFR; +			else +				ctrl |= DXEPCTL_SETODDFR; +			writel(ctrl, hsotg->regs + epctl_reg); +		} + +		dev_dbg(hsotg->dev, +			"%s: XferCompl: DxEPCTL=0x%08x, DXEPTSIZ=%08x\n", +			__func__, readl(hsotg->regs + epctl_reg), +			readl(hsotg->regs + epsiz_reg)); + +		/* +		 * we get OutDone from the FIFO, so we only need to look +		 * at completing IN requests here +		 */ +		if (dir_in) { +			s3c_hsotg_complete_in(hsotg, hs_ep); + +			if (idx == 0 && !hs_ep->req) +				s3c_hsotg_enqueue_setup(hsotg); +		} else if (using_dma(hsotg)) { +			/* +			 * We're using DMA, we need to fire an OutDone here +			 * as we ignore the RXFIFO. +			 */ + +			s3c_hsotg_handle_outdone(hsotg, idx, false); +		} +	} + +	if (ints & DXEPINT_EPDISBLD) { +		dev_dbg(hsotg->dev, "%s: EPDisbld\n", __func__); + +		if (dir_in) { +			int epctl = readl(hsotg->regs + epctl_reg); + +			s3c_hsotg_txfifo_flush(hsotg, idx); + +			if ((epctl & DXEPCTL_STALL) && +				(epctl & DXEPCTL_EPTYPE_BULK)) { +				int dctl = readl(hsotg->regs + DCTL); + +				dctl |= DCTL_CGNPINNAK; +				writel(dctl, hsotg->regs + DCTL); +			} +		} +	} + +	if (ints & DXEPINT_AHBERR) +		dev_dbg(hsotg->dev, "%s: AHBErr\n", __func__); + +	if (ints & DXEPINT_SETUP) {  /* Setup or Timeout */ +		dev_dbg(hsotg->dev, "%s: Setup/Timeout\n",  __func__); + +		if (using_dma(hsotg) && idx == 0) { +			/* +			 * this is the notification we've received a +			 * setup packet. In non-DMA mode we'd get this +			 * from the RXFIFO, instead we need to process +			 * the setup here. +			 */ + +			if (dir_in) +				WARN_ON_ONCE(1); +			else +				s3c_hsotg_handle_outdone(hsotg, 0, true); +		} +	} + +	if (ints & DXEPINT_BACK2BACKSETUP) +		dev_dbg(hsotg->dev, "%s: B2BSetup/INEPNakEff\n", __func__); + +	if (dir_in && !hs_ep->isochronous) { +		/* not sure if this is important, but we'll clear it anyway */ +		if (ints & DIEPMSK_INTKNTXFEMPMSK) { +			dev_dbg(hsotg->dev, "%s: ep%d: INTknTXFEmpMsk\n", +				__func__, idx); +		} + +		/* this probably means something bad is happening */ +		if (ints & DIEPMSK_INTKNEPMISMSK) { +			dev_warn(hsotg->dev, "%s: ep%d: INTknEP\n", +				 __func__, idx); +		} + +		/* FIFO has space or is empty (see GAHBCFG) */ +		if (hsotg->dedicated_fifos && +		    ints & DIEPMSK_TXFIFOEMPTY) { +			dev_dbg(hsotg->dev, "%s: ep%d: TxFIFOEmpty\n", +				__func__, idx); +			if (!using_dma(hsotg)) +				s3c_hsotg_trytx(hsotg, hs_ep); +		} +	} +} + +/** + * s3c_hsotg_irq_enumdone - Handle EnumDone interrupt (enumeration done) + * @hsotg: The device state. + * + * Handle updating the device settings after the enumeration phase has + * been completed. + */ +static void s3c_hsotg_irq_enumdone(struct s3c_hsotg *hsotg) +{ +	u32 dsts = readl(hsotg->regs + DSTS); +	int ep0_mps = 0, ep_mps; + +	/* +	 * This should signal the finish of the enumeration phase +	 * of the USB handshaking, so we should now know what rate +	 * we connected at. +	 */ + +	dev_dbg(hsotg->dev, "EnumDone (DSTS=0x%08x)\n", dsts); + +	/* +	 * note, since we're limited by the size of transfer on EP0, and +	 * it seems IN transfers must be a even number of packets we do +	 * not advertise a 64byte MPS on EP0. +	 */ + +	/* catch both EnumSpd_FS and EnumSpd_FS48 */ +	switch (dsts & DSTS_ENUMSPD_MASK) { +	case DSTS_ENUMSPD_FS: +	case DSTS_ENUMSPD_FS48: +		hsotg->gadget.speed = USB_SPEED_FULL; +		ep0_mps = EP0_MPS_LIMIT; +		ep_mps = 1023; +		break; + +	case DSTS_ENUMSPD_HS: +		hsotg->gadget.speed = USB_SPEED_HIGH; +		ep0_mps = EP0_MPS_LIMIT; +		ep_mps = 1024; +		break; + +	case DSTS_ENUMSPD_LS: +		hsotg->gadget.speed = USB_SPEED_LOW; +		/* +		 * note, we don't actually support LS in this driver at the +		 * moment, and the documentation seems to imply that it isn't +		 * supported by the PHYs on some of the devices. +		 */ +		break; +	} +	dev_info(hsotg->dev, "new device is %s\n", +		 usb_speed_string(hsotg->gadget.speed)); + +	/* +	 * we should now know the maximum packet size for an +	 * endpoint, so set the endpoints to a default value. +	 */ + +	if (ep0_mps) { +		int i; +		s3c_hsotg_set_ep_maxpacket(hsotg, 0, ep0_mps); +		for (i = 1; i < hsotg->num_of_eps; i++) +			s3c_hsotg_set_ep_maxpacket(hsotg, i, ep_mps); +	} + +	/* ensure after enumeration our EP0 is active */ + +	s3c_hsotg_enqueue_setup(hsotg); + +	dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", +		readl(hsotg->regs + DIEPCTL0), +		readl(hsotg->regs + DOEPCTL0)); +} + +/** + * kill_all_requests - remove all requests from the endpoint's queue + * @hsotg: The device state. + * @ep: The endpoint the requests may be on. + * @result: The result code to use. + * @force: Force removal of any current requests + * + * Go through the requests on the given endpoint and mark them + * completed with the given result code. + */ +static void kill_all_requests(struct s3c_hsotg *hsotg, +			      struct s3c_hsotg_ep *ep, +			      int result, bool force) +{ +	struct s3c_hsotg_req *req, *treq; + +	list_for_each_entry_safe(req, treq, &ep->queue, queue) { +		/* +		 * currently, we can't do much about an already +		 * running request on an in endpoint +		 */ + +		if (ep->req == req && ep->dir_in && !force) +			continue; + +		s3c_hsotg_complete_request(hsotg, ep, req, +					   result); +	} +	if(hsotg->dedicated_fifos) +		if ((readl(hsotg->regs + DTXFSTS(ep->index)) & 0xffff) * 4 < 3072) +			s3c_hsotg_txfifo_flush(hsotg, ep->index); +} + +/** + * s3c_hsotg_disconnect - disconnect service + * @hsotg: The device state. + * + * The device has been disconnected. Remove all current + * transactions and signal the gadget driver that this + * has happened. + */ +static void s3c_hsotg_disconnect(struct s3c_hsotg *hsotg) +{ +	unsigned ep; + +	for (ep = 0; ep < hsotg->num_of_eps; ep++) +		kill_all_requests(hsotg, &hsotg->eps[ep], -ESHUTDOWN, true); + +	call_gadget(hsotg, disconnect); +} + +/** + * s3c_hsotg_irq_fifoempty - TX FIFO empty interrupt handler + * @hsotg: The device state: + * @periodic: True if this is a periodic FIFO interrupt + */ +static void s3c_hsotg_irq_fifoempty(struct s3c_hsotg *hsotg, bool periodic) +{ +	struct s3c_hsotg_ep *ep; +	int epno, ret; + +	/* look through for any more data to transmit */ + +	for (epno = 0; epno < hsotg->num_of_eps; epno++) { +		ep = &hsotg->eps[epno]; + +		if (!ep->dir_in) +			continue; + +		if ((periodic && !ep->periodic) || +		    (!periodic && ep->periodic)) +			continue; + +		ret = s3c_hsotg_trytx(hsotg, ep); +		if (ret < 0) +			break; +	} +} + +/* IRQ flags which will trigger a retry around the IRQ loop */ +#define IRQ_RETRY_MASK (GINTSTS_NPTXFEMP | \ +			GINTSTS_PTXFEMP |  \ +			GINTSTS_RXFLVL) + +/** + * s3c_hsotg_corereset - issue softreset to the core + * @hsotg: The device state + * + * Issue a soft reset to the core, and await the core finishing it. + */ +static int s3c_hsotg_corereset(struct s3c_hsotg *hsotg) +{ +	int timeout; +	u32 grstctl; + +	dev_dbg(hsotg->dev, "resetting core\n"); + +	/* issue soft reset */ +	writel(GRSTCTL_CSFTRST, hsotg->regs + GRSTCTL); + +	timeout = 10000; +	do { +		grstctl = readl(hsotg->regs + GRSTCTL); +	} while ((grstctl & GRSTCTL_CSFTRST) && timeout-- > 0); + +	if (grstctl & GRSTCTL_CSFTRST) { +		dev_err(hsotg->dev, "Failed to get CSftRst asserted\n"); +		return -EINVAL; +	} + +	timeout = 10000; + +	while (1) { +		u32 grstctl = readl(hsotg->regs + GRSTCTL); + +		if (timeout-- < 0) { +			dev_info(hsotg->dev, +				 "%s: reset failed, GRSTCTL=%08x\n", +				 __func__, grstctl); +			return -ETIMEDOUT; +		} + +		if (!(grstctl & GRSTCTL_AHBIDLE)) +			continue; + +		break;		/* reset done */ +	} + +	dev_dbg(hsotg->dev, "reset successful\n"); +	return 0; +} + +/** + * s3c_hsotg_core_init - issue softreset to the core + * @hsotg: The device state + * + * Issue a soft reset to the core, and await the core finishing it. + */ +static void s3c_hsotg_core_init(struct s3c_hsotg *hsotg) +{ +	s3c_hsotg_corereset(hsotg); + +	/* +	 * we must now enable ep0 ready for host detection and then +	 * set configuration. +	 */ + +	/* set the PLL on, remove the HNP/SRP and set the PHY */ +	writel(hsotg->phyif | GUSBCFG_TOUTCAL(7) | +	       (0x5 << 10), hsotg->regs + GUSBCFG); + +	s3c_hsotg_init_fifo(hsotg); + +	__orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + +	writel(1 << 18 | DCFG_DEVSPD_HS,  hsotg->regs + DCFG); + +	/* Clear any pending OTG interrupts */ +	writel(0xffffffff, hsotg->regs + GOTGINT); + +	/* Clear any pending interrupts */ +	writel(0xffffffff, hsotg->regs + GINTSTS); + +	writel(GINTSTS_ERLYSUSP | GINTSTS_SESSREQINT | +		GINTSTS_GOUTNAKEFF | GINTSTS_GINNAKEFF | +		GINTSTS_CONIDSTSCHNG | GINTSTS_USBRST | +		GINTSTS_ENUMDONE | GINTSTS_OTGINT | +		GINTSTS_USBSUSP | GINTSTS_WKUPINT, +		hsotg->regs + GINTMSK); + +	if (using_dma(hsotg)) +		writel(GAHBCFG_GLBL_INTR_EN | GAHBCFG_DMA_EN | +		       GAHBCFG_HBSTLEN_INCR4, +		       hsotg->regs + GAHBCFG); +	else +		writel(((hsotg->dedicated_fifos) ? (GAHBCFG_NP_TXF_EMP_LVL | +						    GAHBCFG_P_TXF_EMP_LVL) : 0) | +		       GAHBCFG_GLBL_INTR_EN, +		       hsotg->regs + GAHBCFG); + +	/* +	 * If INTknTXFEmpMsk is enabled, it's important to disable ep interrupts +	 * when we have no data to transfer. Otherwise we get being flooded by +	 * interrupts. +	 */ + +	writel(((hsotg->dedicated_fifos) ? DIEPMSK_TXFIFOEMPTY | +		DIEPMSK_INTKNTXFEMPMSK : 0) | +		DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK | +		DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | +		DIEPMSK_INTKNEPMISMSK, +		hsotg->regs + DIEPMSK); + +	/* +	 * don't need XferCompl, we get that from RXFIFO in slave mode. In +	 * DMA mode we may need this. +	 */ +	writel((using_dma(hsotg) ? (DIEPMSK_XFERCOMPLMSK | +				    DIEPMSK_TIMEOUTMSK) : 0) | +		DOEPMSK_EPDISBLDMSK | DOEPMSK_AHBERRMSK | +		DOEPMSK_SETUPMSK, +		hsotg->regs + DOEPMSK); + +	writel(0, hsotg->regs + DAINTMSK); + +	dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", +		readl(hsotg->regs + DIEPCTL0), +		readl(hsotg->regs + DOEPCTL0)); + +	/* enable in and out endpoint interrupts */ +	s3c_hsotg_en_gsint(hsotg, GINTSTS_OEPINT | GINTSTS_IEPINT); + +	/* +	 * Enable the RXFIFO when in slave mode, as this is how we collect +	 * the data. In DMA mode, we get events from the FIFO but also +	 * things we cannot process, so do not use it. +	 */ +	if (!using_dma(hsotg)) +		s3c_hsotg_en_gsint(hsotg, GINTSTS_RXFLVL); + +	/* Enable interrupts for EP0 in and out */ +	s3c_hsotg_ctrl_epint(hsotg, 0, 0, 1); +	s3c_hsotg_ctrl_epint(hsotg, 0, 1, 1); + +	__orr32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); +	udelay(10);  /* see openiboot */ +	__bic32(hsotg->regs + DCTL, DCTL_PWRONPRGDONE); + +	dev_dbg(hsotg->dev, "DCTL=0x%08x\n", readl(hsotg->regs + DCTL)); + +	/* +	 * DxEPCTL_USBActEp says RO in manual, but seems to be set by +	 * writing to the EPCTL register.. +	 */ + +	/* set to read 1 8byte packet */ +	writel(DXEPTSIZ_MC(1) | DXEPTSIZ_PKTCNT(1) | +	       DXEPTSIZ_XFERSIZE(8), hsotg->regs + DOEPTSIZ0); + +	writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | +	       DXEPCTL_CNAK | DXEPCTL_EPENA | +	       DXEPCTL_USBACTEP, +	       hsotg->regs + DOEPCTL0); + +	/* enable, but don't activate EP0in */ +	writel(s3c_hsotg_ep0_mps(hsotg->eps[0].ep.maxpacket) | +	       DXEPCTL_USBACTEP, hsotg->regs + DIEPCTL0); + +	s3c_hsotg_enqueue_setup(hsotg); + +	dev_dbg(hsotg->dev, "EP0: DIEPCTL0=0x%08x, DOEPCTL0=0x%08x\n", +		readl(hsotg->regs + DIEPCTL0), +		readl(hsotg->regs + DOEPCTL0)); + +	/* clear global NAKs */ +	writel(DCTL_CGOUTNAK | DCTL_CGNPINNAK, +	       hsotg->regs + DCTL); + +	/* must be at-least 3ms to allow bus to see disconnect */ +	mdelay(3); + +	/* remove the soft-disconnect and let's go */ +	__bic32(hsotg->regs + DCTL, DCTL_SFTDISCON); +} + +/** + * s3c_hsotg_irq - handle device interrupt + * @irq: The IRQ number triggered + * @pw: The pw value when registered the handler. + */ +static irqreturn_t s3c_hsotg_irq(int irq, void *pw) +{ +	struct s3c_hsotg *hsotg = pw; +	int retry_count = 8; +	u32 gintsts; +	u32 gintmsk; + +	spin_lock(&hsotg->lock); +irq_retry: +	gintsts = readl(hsotg->regs + GINTSTS); +	gintmsk = readl(hsotg->regs + GINTMSK); + +	dev_dbg(hsotg->dev, "%s: %08x %08x (%08x) retry %d\n", +		__func__, gintsts, gintsts & gintmsk, gintmsk, retry_count); + +	gintsts &= gintmsk; + +	if (gintsts & GINTSTS_OTGINT) { +		u32 otgint = readl(hsotg->regs + GOTGINT); + +		dev_info(hsotg->dev, "OTGInt: %08x\n", otgint); + +		writel(otgint, hsotg->regs + GOTGINT); +	} + +	if (gintsts & GINTSTS_SESSREQINT) { +		dev_dbg(hsotg->dev, "%s: SessReqInt\n", __func__); +		writel(GINTSTS_SESSREQINT, hsotg->regs + GINTSTS); +	} + +	if (gintsts & GINTSTS_ENUMDONE) { +		writel(GINTSTS_ENUMDONE, hsotg->regs + GINTSTS); + +		s3c_hsotg_irq_enumdone(hsotg); +	} + +	if (gintsts & GINTSTS_CONIDSTSCHNG) { +		dev_dbg(hsotg->dev, "ConIDStsChg (DSTS=0x%08x, GOTCTL=%08x)\n", +			readl(hsotg->regs + DSTS), +			readl(hsotg->regs + GOTGCTL)); + +		writel(GINTSTS_CONIDSTSCHNG, hsotg->regs + GINTSTS); +	} + +	if (gintsts & (GINTSTS_OEPINT | GINTSTS_IEPINT)) { +		u32 daint = readl(hsotg->regs + DAINT); +		u32 daintmsk = readl(hsotg->regs + DAINTMSK); +		u32 daint_out, daint_in; +		int ep; + +		daint &= daintmsk; +		daint_out = daint >> DAINT_OUTEP_SHIFT; +		daint_in = daint & ~(daint_out << DAINT_OUTEP_SHIFT); + +		dev_dbg(hsotg->dev, "%s: daint=%08x\n", __func__, daint); + +		for (ep = 0; ep < 15 && daint_out; ep++, daint_out >>= 1) { +			if (daint_out & 1) +				s3c_hsotg_epint(hsotg, ep, 0); +		} + +		for (ep = 0; ep < 15 && daint_in; ep++, daint_in >>= 1) { +			if (daint_in & 1) +				s3c_hsotg_epint(hsotg, ep, 1); +		} +	} + +	if (gintsts & GINTSTS_USBRST) { + +		u32 usb_status = readl(hsotg->regs + GOTGCTL); + +		dev_info(hsotg->dev, "%s: USBRst\n", __func__); +		dev_dbg(hsotg->dev, "GNPTXSTS=%08x\n", +			readl(hsotg->regs + GNPTXSTS)); + +		writel(GINTSTS_USBRST, hsotg->regs + GINTSTS); + +		if (usb_status & GOTGCTL_BSESVLD) { +			if (time_after(jiffies, hsotg->last_rst + +				       msecs_to_jiffies(200))) { + +				kill_all_requests(hsotg, &hsotg->eps[0], +							  -ECONNRESET, true); + +				s3c_hsotg_core_init(hsotg); +				hsotg->last_rst = jiffies; +			} +		} +	} + +	/* check both FIFOs */ + +	if (gintsts & GINTSTS_NPTXFEMP) { +		dev_dbg(hsotg->dev, "NPTxFEmp\n"); + +		/* +		 * Disable the interrupt to stop it happening again +		 * unless one of these endpoint routines decides that +		 * it needs re-enabling +		 */ + +		s3c_hsotg_disable_gsint(hsotg, GINTSTS_NPTXFEMP); +		s3c_hsotg_irq_fifoempty(hsotg, false); +	} + +	if (gintsts & GINTSTS_PTXFEMP) { +		dev_dbg(hsotg->dev, "PTxFEmp\n"); + +		/* See note in GINTSTS_NPTxFEmp */ + +		s3c_hsotg_disable_gsint(hsotg, GINTSTS_PTXFEMP); +		s3c_hsotg_irq_fifoempty(hsotg, true); +	} + +	if (gintsts & GINTSTS_RXFLVL) { +		/* +		 * note, since GINTSTS_RxFLvl doubles as FIFO-not-empty, +		 * we need to retry s3c_hsotg_handle_rx if this is still +		 * set. +		 */ + +		s3c_hsotg_handle_rx(hsotg); +	} + +	if (gintsts & GINTSTS_MODEMIS) { +		dev_warn(hsotg->dev, "warning, mode mismatch triggered\n"); +		writel(GINTSTS_MODEMIS, hsotg->regs + GINTSTS); +	} + +	if (gintsts & GINTSTS_USBSUSP) { +		dev_info(hsotg->dev, "GINTSTS_USBSusp\n"); +		writel(GINTSTS_USBSUSP, hsotg->regs + GINTSTS); + +		call_gadget(hsotg, suspend); +	} + +	if (gintsts & GINTSTS_WKUPINT) { +		dev_info(hsotg->dev, "GINTSTS_WkUpIn\n"); +		writel(GINTSTS_WKUPINT, hsotg->regs + GINTSTS); + +		call_gadget(hsotg, resume); +	} + +	if (gintsts & GINTSTS_ERLYSUSP) { +		dev_dbg(hsotg->dev, "GINTSTS_ErlySusp\n"); +		writel(GINTSTS_ERLYSUSP, hsotg->regs + GINTSTS); +	} + +	/* +	 * these next two seem to crop-up occasionally causing the core +	 * to shutdown the USB transfer, so try clearing them and logging +	 * the occurrence. +	 */ + +	if (gintsts & GINTSTS_GOUTNAKEFF) { +		dev_info(hsotg->dev, "GOUTNakEff triggered\n"); + +		writel(DCTL_CGOUTNAK, hsotg->regs + DCTL); + +		s3c_hsotg_dump(hsotg); +	} + +	if (gintsts & GINTSTS_GINNAKEFF) { +		dev_info(hsotg->dev, "GINNakEff triggered\n"); + +		writel(DCTL_CGNPINNAK, hsotg->regs + DCTL); + +		s3c_hsotg_dump(hsotg); +	} + +	/* +	 * if we've had fifo events, we should try and go around the +	 * loop again to see if there's any point in returning yet. +	 */ + +	if (gintsts & IRQ_RETRY_MASK && --retry_count > 0) +			goto irq_retry; + +	spin_unlock(&hsotg->lock); + +	return IRQ_HANDLED; +} + +/** + * s3c_hsotg_ep_enable - enable the given endpoint + * @ep: The USB endpint to configure + * @desc: The USB endpoint descriptor to configure with. + * + * This is called from the USB gadget code's usb_ep_enable(). + */ +static int s3c_hsotg_ep_enable(struct usb_ep *ep, +			       const struct usb_endpoint_descriptor *desc) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hsotg = hs_ep->parent; +	unsigned long flags; +	int index = hs_ep->index; +	u32 epctrl_reg; +	u32 epctrl; +	u32 mps; +	int dir_in; +	int ret = 0; + +	dev_dbg(hsotg->dev, +		"%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n", +		__func__, ep->name, desc->bEndpointAddress, desc->bmAttributes, +		desc->wMaxPacketSize, desc->bInterval); + +	/* not to be called for EP0 */ +	WARN_ON(index == 0); + +	dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; +	if (dir_in != hs_ep->dir_in) { +		dev_err(hsotg->dev, "%s: direction mismatch!\n", __func__); +		return -EINVAL; +	} + +	mps = usb_endpoint_maxp(desc); + +	/* note, we handle this here instead of s3c_hsotg_set_ep_maxpacket */ + +	epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); +	epctrl = readl(hsotg->regs + epctrl_reg); + +	dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x from 0x%08x\n", +		__func__, epctrl, epctrl_reg); + +	spin_lock_irqsave(&hsotg->lock, flags); + +	epctrl &= ~(DXEPCTL_EPTYPE_MASK | DXEPCTL_MPS_MASK); +	epctrl |= DXEPCTL_MPS(mps); + +	/* +	 * mark the endpoint as active, otherwise the core may ignore +	 * transactions entirely for this endpoint +	 */ +	epctrl |= DXEPCTL_USBACTEP; + +	/* +	 * set the NAK status on the endpoint, otherwise we might try and +	 * do something with data that we've yet got a request to process +	 * since the RXFIFO will take data for an endpoint even if the +	 * size register hasn't been set. +	 */ + +	epctrl |= DXEPCTL_SNAK; + +	/* update the endpoint state */ +	s3c_hsotg_set_ep_maxpacket(hsotg, hs_ep->index, mps); + +	/* default, set to non-periodic */ +	hs_ep->isochronous = 0; +	hs_ep->periodic = 0; +	hs_ep->halted = 0; +	hs_ep->interval = desc->bInterval; + +	if (hs_ep->interval > 1 && hs_ep->mc > 1) +		dev_err(hsotg->dev, "MC > 1 when interval is not 1\n"); + +	switch (desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { +	case USB_ENDPOINT_XFER_ISOC: +		epctrl |= DXEPCTL_EPTYPE_ISO; +		epctrl |= DXEPCTL_SETEVENFR; +		hs_ep->isochronous = 1; +		if (dir_in) +			hs_ep->periodic = 1; +		break; + +	case USB_ENDPOINT_XFER_BULK: +		epctrl |= DXEPCTL_EPTYPE_BULK; +		break; + +	case USB_ENDPOINT_XFER_INT: +		if (dir_in) { +			/* +			 * Allocate our TxFNum by simply using the index +			 * of the endpoint for the moment. We could do +			 * something better if the host indicates how +			 * many FIFOs we are expecting to use. +			 */ + +			hs_ep->periodic = 1; +			epctrl |= DXEPCTL_TXFNUM(index); +		} + +		epctrl |= DXEPCTL_EPTYPE_INTERRUPT; +		break; + +	case USB_ENDPOINT_XFER_CONTROL: +		epctrl |= DXEPCTL_EPTYPE_CONTROL; +		break; +	} + +	/* +	 * if the hardware has dedicated fifos, we must give each IN EP +	 * a unique tx-fifo even if it is non-periodic. +	 */ +	if (dir_in && hsotg->dedicated_fifos) +		epctrl |= DXEPCTL_TXFNUM(index); + +	/* for non control endpoints, set PID to D0 */ +	if (index) +		epctrl |= DXEPCTL_SETD0PID; + +	dev_dbg(hsotg->dev, "%s: write DxEPCTL=0x%08x\n", +		__func__, epctrl); + +	writel(epctrl, hsotg->regs + epctrl_reg); +	dev_dbg(hsotg->dev, "%s: read DxEPCTL=0x%08x\n", +		__func__, readl(hsotg->regs + epctrl_reg)); + +	/* enable the endpoint interrupt */ +	s3c_hsotg_ctrl_epint(hsotg, index, dir_in, 1); + +	spin_unlock_irqrestore(&hsotg->lock, flags); +	return ret; +} + +/** + * s3c_hsotg_ep_disable - disable given endpoint + * @ep: The endpoint to disable. + */ +static int s3c_hsotg_ep_disable(struct usb_ep *ep) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hsotg = hs_ep->parent; +	int dir_in = hs_ep->dir_in; +	int index = hs_ep->index; +	unsigned long flags; +	u32 epctrl_reg; +	u32 ctrl; + +	dev_info(hsotg->dev, "%s(ep %p)\n", __func__, ep); + +	if (ep == &hsotg->eps[0].ep) { +		dev_err(hsotg->dev, "%s: called for ep0\n", __func__); +		return -EINVAL; +	} + +	epctrl_reg = dir_in ? DIEPCTL(index) : DOEPCTL(index); + +	spin_lock_irqsave(&hsotg->lock, flags); +	/* terminate all requests with shutdown */ +	kill_all_requests(hsotg, hs_ep, -ESHUTDOWN, false); + + +	ctrl = readl(hsotg->regs + epctrl_reg); +	ctrl &= ~DXEPCTL_EPENA; +	ctrl &= ~DXEPCTL_USBACTEP; +	ctrl |= DXEPCTL_SNAK; + +	dev_dbg(hsotg->dev, "%s: DxEPCTL=0x%08x\n", __func__, ctrl); +	writel(ctrl, hsotg->regs + epctrl_reg); + +	/* disable endpoint interrupts */ +	s3c_hsotg_ctrl_epint(hsotg, hs_ep->index, hs_ep->dir_in, 0); + +	spin_unlock_irqrestore(&hsotg->lock, flags); +	return 0; +} + +/** + * on_list - check request is on the given endpoint + * @ep: The endpoint to check. + * @test: The request to test if it is on the endpoint. + */ +static bool on_list(struct s3c_hsotg_ep *ep, struct s3c_hsotg_req *test) +{ +	struct s3c_hsotg_req *req, *treq; + +	list_for_each_entry_safe(req, treq, &ep->queue, queue) { +		if (req == test) +			return true; +	} + +	return false; +} + +/** + * s3c_hsotg_ep_dequeue - dequeue given endpoint + * @ep: The endpoint to dequeue. + * @req: The request to be removed from a queue. + */ +static int s3c_hsotg_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ +	struct s3c_hsotg_req *hs_req = our_req(req); +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hs = hs_ep->parent; +	unsigned long flags; + +	dev_info(hs->dev, "ep_dequeue(%p,%p)\n", ep, req); + +	spin_lock_irqsave(&hs->lock, flags); + +	if (!on_list(hs_ep, hs_req)) { +		spin_unlock_irqrestore(&hs->lock, flags); +		return -EINVAL; +	} + +	s3c_hsotg_complete_request(hs, hs_ep, hs_req, -ECONNRESET); +	spin_unlock_irqrestore(&hs->lock, flags); + +	return 0; +} + +/** + * s3c_hsotg_ep_sethalt - set halt on a given endpoint + * @ep: The endpoint to set halt. + * @value: Set or unset the halt. + */ +static int s3c_hsotg_ep_sethalt(struct usb_ep *ep, int value) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hs = hs_ep->parent; +	int index = hs_ep->index; +	u32 epreg; +	u32 epctl; +	u32 xfertype; + +	dev_info(hs->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value); + +	if (index == 0) { +		if (value) +			s3c_hsotg_stall_ep0(hs); +		else +			dev_warn(hs->dev, +				 "%s: can't clear halt on ep0\n", __func__); +		return 0; +	} + +	/* write both IN and OUT control registers */ + +	epreg = DIEPCTL(index); +	epctl = readl(hs->regs + epreg); + +	if (value) { +		epctl |= DXEPCTL_STALL + DXEPCTL_SNAK; +		if (epctl & DXEPCTL_EPENA) +			epctl |= DXEPCTL_EPDIS; +	} else { +		epctl &= ~DXEPCTL_STALL; +		xfertype = epctl & DXEPCTL_EPTYPE_MASK; +		if (xfertype == DXEPCTL_EPTYPE_BULK || +			xfertype == DXEPCTL_EPTYPE_INTERRUPT) +				epctl |= DXEPCTL_SETD0PID; +	} + +	writel(epctl, hs->regs + epreg); + +	epreg = DOEPCTL(index); +	epctl = readl(hs->regs + epreg); + +	if (value) +		epctl |= DXEPCTL_STALL; +	else { +		epctl &= ~DXEPCTL_STALL; +		xfertype = epctl & DXEPCTL_EPTYPE_MASK; +		if (xfertype == DXEPCTL_EPTYPE_BULK || +			xfertype == DXEPCTL_EPTYPE_INTERRUPT) +				epctl |= DXEPCTL_SETD0PID; +	} + +	writel(epctl, hs->regs + epreg); + +	hs_ep->halted = value; + +	return 0; +} + +/** + * s3c_hsotg_ep_sethalt_lock - set halt on a given endpoint with lock held + * @ep: The endpoint to set halt. + * @value: Set or unset the halt. + */ +static int s3c_hsotg_ep_sethalt_lock(struct usb_ep *ep, int value) +{ +	struct s3c_hsotg_ep *hs_ep = our_ep(ep); +	struct s3c_hsotg *hs = hs_ep->parent; +	unsigned long flags = 0; +	int ret = 0; + +	spin_lock_irqsave(&hs->lock, flags); +	ret = s3c_hsotg_ep_sethalt(ep, value); +	spin_unlock_irqrestore(&hs->lock, flags); + +	return ret; +} + +static struct usb_ep_ops s3c_hsotg_ep_ops = { +	.enable		= s3c_hsotg_ep_enable, +	.disable	= s3c_hsotg_ep_disable, +	.alloc_request	= s3c_hsotg_ep_alloc_request, +	.free_request	= s3c_hsotg_ep_free_request, +	.queue		= s3c_hsotg_ep_queue_lock, +	.dequeue	= s3c_hsotg_ep_dequeue, +	.set_halt	= s3c_hsotg_ep_sethalt_lock, +	/* note, don't believe we have any call for the fifo routines */ +}; + +/** + * s3c_hsotg_phy_enable - enable platform phy dev + * @hsotg: The driver state + * + * A wrapper for platform code responsible for controlling + * low-level USB code + */ +static void s3c_hsotg_phy_enable(struct s3c_hsotg *hsotg) +{ +	struct platform_device *pdev = to_platform_device(hsotg->dev); + +	dev_dbg(hsotg->dev, "pdev 0x%p\n", pdev); + +	if (hsotg->phy) { +		phy_init(hsotg->phy); +		phy_power_on(hsotg->phy); +	} else if (hsotg->uphy) +		usb_phy_init(hsotg->uphy); +	else if (hsotg->plat->phy_init) +		hsotg->plat->phy_init(pdev, hsotg->plat->phy_type); +} + +/** + * s3c_hsotg_phy_disable - disable platform phy dev + * @hsotg: The driver state + * + * A wrapper for platform code responsible for controlling + * low-level USB code + */ +static void s3c_hsotg_phy_disable(struct s3c_hsotg *hsotg) +{ +	struct platform_device *pdev = to_platform_device(hsotg->dev); + +	if (hsotg->phy) { +		phy_power_off(hsotg->phy); +		phy_exit(hsotg->phy); +	} else if (hsotg->uphy) +		usb_phy_shutdown(hsotg->uphy); +	else if (hsotg->plat->phy_exit) +		hsotg->plat->phy_exit(pdev, hsotg->plat->phy_type); +} + +/** + * s3c_hsotg_init - initalize the usb core + * @hsotg: The driver state + */ +static void s3c_hsotg_init(struct s3c_hsotg *hsotg) +{ +	/* unmask subset of endpoint interrupts */ + +	writel(DIEPMSK_TIMEOUTMSK | DIEPMSK_AHBERRMSK | +		DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK, +		hsotg->regs + DIEPMSK); + +	writel(DOEPMSK_SETUPMSK | DOEPMSK_AHBERRMSK | +		DOEPMSK_EPDISBLDMSK | DOEPMSK_XFERCOMPLMSK, +		hsotg->regs + DOEPMSK); + +	writel(0, hsotg->regs + DAINTMSK); + +	/* Be in disconnected state until gadget is registered */ +	__orr32(hsotg->regs + DCTL, DCTL_SFTDISCON); + +	if (0) { +		/* post global nak until we're ready */ +		writel(DCTL_SGNPINNAK | DCTL_SGOUTNAK, +		       hsotg->regs + DCTL); +	} + +	/* setup fifos */ + +	dev_dbg(hsotg->dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", +		readl(hsotg->regs + GRXFSIZ), +		readl(hsotg->regs + GNPTXFSIZ)); + +	s3c_hsotg_init_fifo(hsotg); + +	/* set the PLL on, remove the HNP/SRP and set the PHY */ +	writel(GUSBCFG_PHYIF16 | GUSBCFG_TOUTCAL(7) | (0x5 << 10), +	       hsotg->regs + GUSBCFG); + +	writel(using_dma(hsotg) ? GAHBCFG_DMA_EN : 0x0, +	       hsotg->regs + GAHBCFG); +} + +/** + * s3c_hsotg_udc_start - prepare the udc for work + * @gadget: The usb gadget state + * @driver: The usb gadget driver + * + * Perform initialization to prepare udc device and driver + * to work. + */ +static int s3c_hsotg_udc_start(struct usb_gadget *gadget, +			   struct usb_gadget_driver *driver) +{ +	struct s3c_hsotg *hsotg = to_hsotg(gadget); +	int ret; + +	if (!hsotg) { +		pr_err("%s: called with no device\n", __func__); +		return -ENODEV; +	} + +	if (!driver) { +		dev_err(hsotg->dev, "%s: no driver\n", __func__); +		return -EINVAL; +	} + +	if (driver->max_speed < USB_SPEED_FULL) +		dev_err(hsotg->dev, "%s: bad speed\n", __func__); + +	if (!driver->setup) { +		dev_err(hsotg->dev, "%s: missing entry points\n", __func__); +		return -EINVAL; +	} + +	WARN_ON(hsotg->driver); + +	driver->driver.bus = NULL; +	hsotg->driver = driver; +	hsotg->gadget.dev.of_node = hsotg->dev->of_node; +	hsotg->gadget.speed = USB_SPEED_UNKNOWN; + +	ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), +				    hsotg->supplies); +	if (ret) { +		dev_err(hsotg->dev, "failed to enable supplies: %d\n", ret); +		goto err; +	} + +	hsotg->last_rst = jiffies; +	dev_info(hsotg->dev, "bound driver %s\n", driver->driver.name); +	return 0; + +err: +	hsotg->driver = NULL; +	return ret; +} + +/** + * s3c_hsotg_udc_stop - stop the udc + * @gadget: The usb gadget state + * @driver: The usb gadget driver + * + * Stop udc hw block and stay tunned for future transmissions + */ +static int s3c_hsotg_udc_stop(struct usb_gadget *gadget, +			  struct usb_gadget_driver *driver) +{ +	struct s3c_hsotg *hsotg = to_hsotg(gadget); +	unsigned long flags = 0; +	int ep; + +	if (!hsotg) +		return -ENODEV; + +	/* all endpoints should be shutdown */ +	for (ep = 0; ep < hsotg->num_of_eps; ep++) +		s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + +	spin_lock_irqsave(&hsotg->lock, flags); + +	s3c_hsotg_phy_disable(hsotg); + +	if (!driver) +		hsotg->driver = NULL; + +	hsotg->gadget.speed = USB_SPEED_UNKNOWN; + +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), hsotg->supplies); + +	return 0; +} + +/** + * s3c_hsotg_gadget_getframe - read the frame number + * @gadget: The usb gadget state + * + * Read the {micro} frame number + */ +static int s3c_hsotg_gadget_getframe(struct usb_gadget *gadget) +{ +	return s3c_hsotg_read_frameno(to_hsotg(gadget)); +} + +/** + * s3c_hsotg_pullup - connect/disconnect the USB PHY + * @gadget: The usb gadget state + * @is_on: Current state of the USB PHY + * + * Connect/Disconnect the USB PHY pullup + */ +static int s3c_hsotg_pullup(struct usb_gadget *gadget, int is_on) +{ +	struct s3c_hsotg *hsotg = to_hsotg(gadget); +	unsigned long flags = 0; + +	dev_dbg(hsotg->dev, "%s: is_in: %d\n", __func__, is_on); + +	spin_lock_irqsave(&hsotg->lock, flags); +	if (is_on) { +		s3c_hsotg_phy_enable(hsotg); +		s3c_hsotg_core_init(hsotg); +	} else { +		s3c_hsotg_disconnect(hsotg); +		s3c_hsotg_phy_disable(hsotg); +	} + +	hsotg->gadget.speed = USB_SPEED_UNKNOWN; +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	return 0; +} + +static const struct usb_gadget_ops s3c_hsotg_gadget_ops = { +	.get_frame	= s3c_hsotg_gadget_getframe, +	.udc_start		= s3c_hsotg_udc_start, +	.udc_stop		= s3c_hsotg_udc_stop, +	.pullup                 = s3c_hsotg_pullup, +}; + +/** + * s3c_hsotg_initep - initialise a single endpoint + * @hsotg: The device state. + * @hs_ep: The endpoint to be initialised. + * @epnum: The endpoint number + * + * Initialise the given endpoint (as part of the probe and device state + * creation) to give to the gadget driver. Setup the endpoint name, any + * direction information and other state that may be required. + */ +static void s3c_hsotg_initep(struct s3c_hsotg *hsotg, +				       struct s3c_hsotg_ep *hs_ep, +				       int epnum) +{ +	u32 ptxfifo; +	char *dir; + +	if (epnum == 0) +		dir = ""; +	else if ((epnum % 2) == 0) { +		dir = "out"; +	} else { +		dir = "in"; +		hs_ep->dir_in = 1; +	} + +	hs_ep->index = epnum; + +	snprintf(hs_ep->name, sizeof(hs_ep->name), "ep%d%s", epnum, dir); + +	INIT_LIST_HEAD(&hs_ep->queue); +	INIT_LIST_HEAD(&hs_ep->ep.ep_list); + +	/* add to the list of endpoints known by the gadget driver */ +	if (epnum) +		list_add_tail(&hs_ep->ep.ep_list, &hsotg->gadget.ep_list); + +	hs_ep->parent = hsotg; +	hs_ep->ep.name = hs_ep->name; +	usb_ep_set_maxpacket_limit(&hs_ep->ep, epnum ? 1024 : EP0_MPS_LIMIT); +	hs_ep->ep.ops = &s3c_hsotg_ep_ops; + +	/* +	 * Read the FIFO size for the Periodic TX FIFO, even if we're +	 * an OUT endpoint, we may as well do this if in future the +	 * code is changed to make each endpoint's direction changeable. +	 */ + +	ptxfifo = readl(hsotg->regs + DPTXFSIZN(epnum)); +	hs_ep->fifo_size = FIFOSIZE_DEPTH_GET(ptxfifo) * 4; + +	/* +	 * if we're using dma, we need to set the next-endpoint pointer +	 * to be something valid. +	 */ + +	if (using_dma(hsotg)) { +		u32 next = DXEPCTL_NEXTEP((epnum + 1) % 15); +		writel(next, hsotg->regs + DIEPCTL(epnum)); +		writel(next, hsotg->regs + DOEPCTL(epnum)); +	} +} + +/** + * s3c_hsotg_hw_cfg - read HW configuration registers + * @param: The device state + * + * Read the USB core HW configuration registers + */ +static void s3c_hsotg_hw_cfg(struct s3c_hsotg *hsotg) +{ +	u32 cfg2, cfg4; +	/* check hardware configuration */ + +	cfg2 = readl(hsotg->regs + 0x48); +	hsotg->num_of_eps = (cfg2 >> 10) & 0xF; + +	dev_info(hsotg->dev, "EPs:%d\n", hsotg->num_of_eps); + +	cfg4 = readl(hsotg->regs + 0x50); +	hsotg->dedicated_fifos = (cfg4 >> 25) & 1; + +	dev_info(hsotg->dev, "%s fifos\n", +		 hsotg->dedicated_fifos ? "dedicated" : "shared"); +} + +/** + * s3c_hsotg_dump - dump state of the udc + * @param: The device state + */ +static void s3c_hsotg_dump(struct s3c_hsotg *hsotg) +{ +#ifdef DEBUG +	struct device *dev = hsotg->dev; +	void __iomem *regs = hsotg->regs; +	u32 val; +	int idx; + +	dev_info(dev, "DCFG=0x%08x, DCTL=0x%08x, DIEPMSK=%08x\n", +		 readl(regs + DCFG), readl(regs + DCTL), +		 readl(regs + DIEPMSK)); + +	dev_info(dev, "GAHBCFG=0x%08x, 0x44=0x%08x\n", +		 readl(regs + GAHBCFG), readl(regs + 0x44)); + +	dev_info(dev, "GRXFSIZ=0x%08x, GNPTXFSIZ=0x%08x\n", +		 readl(regs + GRXFSIZ), readl(regs + GNPTXFSIZ)); + +	/* show periodic fifo settings */ + +	for (idx = 1; idx <= 15; idx++) { +		val = readl(regs + DPTXFSIZN(idx)); +		dev_info(dev, "DPTx[%d] FSize=%d, StAddr=0x%08x\n", idx, +			 val >> FIFOSIZE_DEPTH_SHIFT, +			 val & FIFOSIZE_STARTADDR_MASK); +	} + +	for (idx = 0; idx < 15; idx++) { +		dev_info(dev, +			 "ep%d-in: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", idx, +			 readl(regs + DIEPCTL(idx)), +			 readl(regs + DIEPTSIZ(idx)), +			 readl(regs + DIEPDMA(idx))); + +		val = readl(regs + DOEPCTL(idx)); +		dev_info(dev, +			 "ep%d-out: EPCTL=0x%08x, SIZ=0x%08x, DMA=0x%08x\n", +			 idx, readl(regs + DOEPCTL(idx)), +			 readl(regs + DOEPTSIZ(idx)), +			 readl(regs + DOEPDMA(idx))); + +	} + +	dev_info(dev, "DVBUSDIS=0x%08x, DVBUSPULSE=%08x\n", +		 readl(regs + DVBUSDIS), readl(regs + DVBUSPULSE)); +#endif +} + +/** + * state_show - debugfs: show overall driver and device state. + * @seq: The seq file to write to. + * @v: Unused parameter. + * + * This debugfs entry shows the overall state of the hardware and + * some general information about each of the endpoints available + * to the system. + */ +static int state_show(struct seq_file *seq, void *v) +{ +	struct s3c_hsotg *hsotg = seq->private; +	void __iomem *regs = hsotg->regs; +	int idx; + +	seq_printf(seq, "DCFG=0x%08x, DCTL=0x%08x, DSTS=0x%08x\n", +		 readl(regs + DCFG), +		 readl(regs + DCTL), +		 readl(regs + DSTS)); + +	seq_printf(seq, "DIEPMSK=0x%08x, DOEPMASK=0x%08x\n", +		   readl(regs + DIEPMSK), readl(regs + DOEPMSK)); + +	seq_printf(seq, "GINTMSK=0x%08x, GINTSTS=0x%08x\n", +		   readl(regs + GINTMSK), +		   readl(regs + GINTSTS)); + +	seq_printf(seq, "DAINTMSK=0x%08x, DAINT=0x%08x\n", +		   readl(regs + DAINTMSK), +		   readl(regs + DAINT)); + +	seq_printf(seq, "GNPTXSTS=0x%08x, GRXSTSR=%08x\n", +		   readl(regs + GNPTXSTS), +		   readl(regs + GRXSTSR)); + +	seq_puts(seq, "\nEndpoint status:\n"); + +	for (idx = 0; idx < 15; idx++) { +		u32 in, out; + +		in = readl(regs + DIEPCTL(idx)); +		out = readl(regs + DOEPCTL(idx)); + +		seq_printf(seq, "ep%d: DIEPCTL=0x%08x, DOEPCTL=0x%08x", +			   idx, in, out); + +		in = readl(regs + DIEPTSIZ(idx)); +		out = readl(regs + DOEPTSIZ(idx)); + +		seq_printf(seq, ", DIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x", +			   in, out); + +		seq_puts(seq, "\n"); +	} + +	return 0; +} + +static int state_open(struct inode *inode, struct file *file) +{ +	return single_open(file, state_show, inode->i_private); +} + +static const struct file_operations state_fops = { +	.owner		= THIS_MODULE, +	.open		= state_open, +	.read		= seq_read, +	.llseek		= seq_lseek, +	.release	= single_release, +}; + +/** + * fifo_show - debugfs: show the fifo information + * @seq: The seq_file to write data to. + * @v: Unused parameter. + * + * Show the FIFO information for the overall fifo and all the + * periodic transmission FIFOs. + */ +static int fifo_show(struct seq_file *seq, void *v) +{ +	struct s3c_hsotg *hsotg = seq->private; +	void __iomem *regs = hsotg->regs; +	u32 val; +	int idx; + +	seq_puts(seq, "Non-periodic FIFOs:\n"); +	seq_printf(seq, "RXFIFO: Size %d\n", readl(regs + GRXFSIZ)); + +	val = readl(regs + GNPTXFSIZ); +	seq_printf(seq, "NPTXFIFO: Size %d, Start 0x%08x\n", +		   val >> FIFOSIZE_DEPTH_SHIFT, +		   val & FIFOSIZE_DEPTH_MASK); + +	seq_puts(seq, "\nPeriodic TXFIFOs:\n"); + +	for (idx = 1; idx <= 15; idx++) { +		val = readl(regs + DPTXFSIZN(idx)); + +		seq_printf(seq, "\tDPTXFIFO%2d: Size %d, Start 0x%08x\n", idx, +			   val >> FIFOSIZE_DEPTH_SHIFT, +			   val & FIFOSIZE_STARTADDR_MASK); +	} + +	return 0; +} + +static int fifo_open(struct inode *inode, struct file *file) +{ +	return single_open(file, fifo_show, inode->i_private); +} + +static const struct file_operations fifo_fops = { +	.owner		= THIS_MODULE, +	.open		= fifo_open, +	.read		= seq_read, +	.llseek		= seq_lseek, +	.release	= single_release, +}; + + +static const char *decode_direction(int is_in) +{ +	return is_in ? "in" : "out"; +} + +/** + * ep_show - debugfs: show the state of an endpoint. + * @seq: The seq_file to write data to. + * @v: Unused parameter. + * + * This debugfs entry shows the state of the given endpoint (one is + * registered for each available). + */ +static int ep_show(struct seq_file *seq, void *v) +{ +	struct s3c_hsotg_ep *ep = seq->private; +	struct s3c_hsotg *hsotg = ep->parent; +	struct s3c_hsotg_req *req; +	void __iomem *regs = hsotg->regs; +	int index = ep->index; +	int show_limit = 15; +	unsigned long flags; + +	seq_printf(seq, "Endpoint index %d, named %s,  dir %s:\n", +		   ep->index, ep->ep.name, decode_direction(ep->dir_in)); + +	/* first show the register state */ + +	seq_printf(seq, "\tDIEPCTL=0x%08x, DOEPCTL=0x%08x\n", +		   readl(regs + DIEPCTL(index)), +		   readl(regs + DOEPCTL(index))); + +	seq_printf(seq, "\tDIEPDMA=0x%08x, DOEPDMA=0x%08x\n", +		   readl(regs + DIEPDMA(index)), +		   readl(regs + DOEPDMA(index))); + +	seq_printf(seq, "\tDIEPINT=0x%08x, DOEPINT=0x%08x\n", +		   readl(regs + DIEPINT(index)), +		   readl(regs + DOEPINT(index))); + +	seq_printf(seq, "\tDIEPTSIZ=0x%08x, DOEPTSIZ=0x%08x\n", +		   readl(regs + DIEPTSIZ(index)), +		   readl(regs + DOEPTSIZ(index))); + +	seq_puts(seq, "\n"); +	seq_printf(seq, "mps %d\n", ep->ep.maxpacket); +	seq_printf(seq, "total_data=%ld\n", ep->total_data); + +	seq_printf(seq, "request list (%p,%p):\n", +		   ep->queue.next, ep->queue.prev); + +	spin_lock_irqsave(&hsotg->lock, flags); + +	list_for_each_entry(req, &ep->queue, queue) { +		if (--show_limit < 0) { +			seq_puts(seq, "not showing more requests...\n"); +			break; +		} + +		seq_printf(seq, "%c req %p: %d bytes @%p, ", +			   req == ep->req ? '*' : ' ', +			   req, req->req.length, req->req.buf); +		seq_printf(seq, "%d done, res %d\n", +			   req->req.actual, req->req.status); +	} + +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	return 0; +} + +static int ep_open(struct inode *inode, struct file *file) +{ +	return single_open(file, ep_show, inode->i_private); +} + +static const struct file_operations ep_fops = { +	.owner		= THIS_MODULE, +	.open		= ep_open, +	.read		= seq_read, +	.llseek		= seq_lseek, +	.release	= single_release, +}; + +/** + * s3c_hsotg_create_debug - create debugfs directory and files + * @hsotg: The driver state + * + * Create the debugfs files to allow the user to get information + * about the state of the system. The directory name is created + * with the same name as the device itself, in case we end up + * with multiple blocks in future systems. + */ +static void s3c_hsotg_create_debug(struct s3c_hsotg *hsotg) +{ +	struct dentry *root; +	unsigned epidx; + +	root = debugfs_create_dir(dev_name(hsotg->dev), NULL); +	hsotg->debug_root = root; +	if (IS_ERR(root)) { +		dev_err(hsotg->dev, "cannot create debug root\n"); +		return; +	} + +	/* create general state file */ + +	hsotg->debug_file = debugfs_create_file("state", 0444, root, +						hsotg, &state_fops); + +	if (IS_ERR(hsotg->debug_file)) +		dev_err(hsotg->dev, "%s: failed to create state\n", __func__); + +	hsotg->debug_fifo = debugfs_create_file("fifo", 0444, root, +						hsotg, &fifo_fops); + +	if (IS_ERR(hsotg->debug_fifo)) +		dev_err(hsotg->dev, "%s: failed to create fifo\n", __func__); + +	/* create one file for each endpoint */ + +	for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { +		struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; + +		ep->debugfs = debugfs_create_file(ep->name, 0444, +						  root, ep, &ep_fops); + +		if (IS_ERR(ep->debugfs)) +			dev_err(hsotg->dev, "failed to create %s debug file\n", +				ep->name); +	} +} + +/** + * s3c_hsotg_delete_debug - cleanup debugfs entries + * @hsotg: The driver state + * + * Cleanup (remove) the debugfs files for use on module exit. + */ +static void s3c_hsotg_delete_debug(struct s3c_hsotg *hsotg) +{ +	unsigned epidx; + +	for (epidx = 0; epidx < hsotg->num_of_eps; epidx++) { +		struct s3c_hsotg_ep *ep = &hsotg->eps[epidx]; +		debugfs_remove(ep->debugfs); +	} + +	debugfs_remove(hsotg->debug_file); +	debugfs_remove(hsotg->debug_fifo); +	debugfs_remove(hsotg->debug_root); +} + +/** + * s3c_hsotg_probe - probe function for hsotg driver + * @pdev: The platform information for the driver + */ + +static int s3c_hsotg_probe(struct platform_device *pdev) +{ +	struct s3c_hsotg_plat *plat = dev_get_platdata(&pdev->dev); +	struct phy *phy; +	struct usb_phy *uphy; +	struct device *dev = &pdev->dev; +	struct s3c_hsotg_ep *eps; +	struct s3c_hsotg *hsotg; +	struct resource *res; +	int epnum; +	int ret; +	int i; + +	hsotg = devm_kzalloc(&pdev->dev, sizeof(struct s3c_hsotg), GFP_KERNEL); +	if (!hsotg) { +		dev_err(dev, "cannot get memory\n"); +		return -ENOMEM; +	} + +	/* +	 * Attempt to find a generic PHY, then look for an old style +	 * USB PHY, finally fall back to pdata +	 */ +	phy = devm_phy_get(&pdev->dev, "usb2-phy"); +	if (IS_ERR(phy)) { +		uphy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); +		if (IS_ERR(uphy)) { +			/* Fallback for pdata */ +			plat = dev_get_platdata(&pdev->dev); +			if (!plat) { +				dev_err(&pdev->dev, +				"no platform data or transceiver defined\n"); +				return -EPROBE_DEFER; +			} +			hsotg->plat = plat; +		} else +			hsotg->uphy = uphy; +	} else +		hsotg->phy = phy; + +	hsotg->dev = dev; + +	hsotg->clk = devm_clk_get(&pdev->dev, "otg"); +	if (IS_ERR(hsotg->clk)) { +		dev_err(dev, "cannot get otg clock\n"); +		return PTR_ERR(hsotg->clk); +	} + +	platform_set_drvdata(pdev, hsotg); + +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + +	hsotg->regs = devm_ioremap_resource(&pdev->dev, res); +	if (IS_ERR(hsotg->regs)) { +		ret = PTR_ERR(hsotg->regs); +		goto err_clk; +	} + +	ret = platform_get_irq(pdev, 0); +	if (ret < 0) { +		dev_err(dev, "cannot find IRQ\n"); +		goto err_clk; +	} + +	spin_lock_init(&hsotg->lock); + +	hsotg->irq = ret; + +	ret = devm_request_irq(&pdev->dev, hsotg->irq, s3c_hsotg_irq, 0, +				dev_name(dev), hsotg); +	if (ret < 0) { +		dev_err(dev, "cannot claim IRQ\n"); +		goto err_clk; +	} + +	dev_info(dev, "regs %p, irq %d\n", hsotg->regs, hsotg->irq); + +	hsotg->gadget.max_speed = USB_SPEED_HIGH; +	hsotg->gadget.ops = &s3c_hsotg_gadget_ops; +	hsotg->gadget.name = dev_name(dev); + +	/* reset the system */ + +	clk_prepare_enable(hsotg->clk); + +	/* regulators */ + +	for (i = 0; i < ARRAY_SIZE(hsotg->supplies); i++) +		hsotg->supplies[i].supply = s3c_hsotg_supply_names[i]; + +	ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hsotg->supplies), +				 hsotg->supplies); +	if (ret) { +		dev_err(dev, "failed to request supplies: %d\n", ret); +		goto err_clk; +	} + +	ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), +				    hsotg->supplies); + +	if (ret) { +		dev_err(hsotg->dev, "failed to enable supplies: %d\n", ret); +		goto err_supplies; +	} + +	/* Set default UTMI width */ +	hsotg->phyif = GUSBCFG_PHYIF16; + +	/* +	 * If using the generic PHY framework, check if the PHY bus +	 * width is 8-bit and set the phyif appropriately. +	 */ +	if (hsotg->phy && (phy_get_bus_width(phy) == 8)) +		hsotg->phyif = GUSBCFG_PHYIF8; + +	if (hsotg->phy) +		phy_init(hsotg->phy); + +	/* usb phy enable */ +	s3c_hsotg_phy_enable(hsotg); + +	s3c_hsotg_corereset(hsotg); +	s3c_hsotg_init(hsotg); +	s3c_hsotg_hw_cfg(hsotg); + +	/* hsotg->num_of_eps holds number of EPs other than ep0 */ + +	if (hsotg->num_of_eps == 0) { +		dev_err(dev, "wrong number of EPs (zero)\n"); +		ret = -EINVAL; +		goto err_supplies; +	} + +	eps = kcalloc(hsotg->num_of_eps + 1, sizeof(struct s3c_hsotg_ep), +		      GFP_KERNEL); +	if (!eps) { +		dev_err(dev, "cannot get memory\n"); +		ret = -ENOMEM; +		goto err_supplies; +	} + +	hsotg->eps = eps; + +	/* setup endpoint information */ + +	INIT_LIST_HEAD(&hsotg->gadget.ep_list); +	hsotg->gadget.ep0 = &hsotg->eps[0].ep; + +	/* allocate EP0 request */ + +	hsotg->ctrl_req = s3c_hsotg_ep_alloc_request(&hsotg->eps[0].ep, +						     GFP_KERNEL); +	if (!hsotg->ctrl_req) { +		dev_err(dev, "failed to allocate ctrl req\n"); +		ret = -ENOMEM; +		goto err_ep_mem; +	} + +	/* initialise the endpoints now the core has been initialised */ +	for (epnum = 0; epnum < hsotg->num_of_eps; epnum++) +		s3c_hsotg_initep(hsotg, &hsotg->eps[epnum], epnum); + +	/* disable power and clock */ + +	ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), +				    hsotg->supplies); +	if (ret) { +		dev_err(hsotg->dev, "failed to disable supplies: %d\n", ret); +		goto err_ep_mem; +	} + +	s3c_hsotg_phy_disable(hsotg); + +	ret = usb_add_gadget_udc(&pdev->dev, &hsotg->gadget); +	if (ret) +		goto err_ep_mem; + +	s3c_hsotg_create_debug(hsotg); + +	s3c_hsotg_dump(hsotg); + +	return 0; + +err_ep_mem: +	kfree(eps); +err_supplies: +	s3c_hsotg_phy_disable(hsotg); +err_clk: +	clk_disable_unprepare(hsotg->clk); + +	return ret; +} + +/** + * s3c_hsotg_remove - remove function for hsotg driver + * @pdev: The platform information for the driver + */ +static int s3c_hsotg_remove(struct platform_device *pdev) +{ +	struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); + +	usb_del_gadget_udc(&hsotg->gadget); + +	s3c_hsotg_delete_debug(hsotg); + +	if (hsotg->driver) { +		/* should have been done already by driver model core */ +		usb_gadget_unregister_driver(hsotg->driver); +	} + +	s3c_hsotg_phy_disable(hsotg); +	if (hsotg->phy) +		phy_exit(hsotg->phy); +	clk_disable_unprepare(hsotg->clk); + +	return 0; +} + +static int s3c_hsotg_suspend(struct platform_device *pdev, pm_message_t state) +{ +	struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); +	unsigned long flags; +	int ret = 0; + +	if (hsotg->driver) +		dev_info(hsotg->dev, "suspending usb gadget %s\n", +			 hsotg->driver->driver.name); + +	spin_lock_irqsave(&hsotg->lock, flags); +	s3c_hsotg_disconnect(hsotg); +	s3c_hsotg_phy_disable(hsotg); +	hsotg->gadget.speed = USB_SPEED_UNKNOWN; +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	if (hsotg->driver) { +		int ep; +		for (ep = 0; ep < hsotg->num_of_eps; ep++) +			s3c_hsotg_ep_disable(&hsotg->eps[ep].ep); + +		ret = regulator_bulk_disable(ARRAY_SIZE(hsotg->supplies), +					     hsotg->supplies); +	} + +	return ret; +} + +static int s3c_hsotg_resume(struct platform_device *pdev) +{ +	struct s3c_hsotg *hsotg = platform_get_drvdata(pdev); +	unsigned long flags; +	int ret = 0; + +	if (hsotg->driver) { +		dev_info(hsotg->dev, "resuming usb gadget %s\n", +			 hsotg->driver->driver.name); +		ret = regulator_bulk_enable(ARRAY_SIZE(hsotg->supplies), +				      hsotg->supplies); +	} + +	spin_lock_irqsave(&hsotg->lock, flags); +	hsotg->last_rst = jiffies; +	s3c_hsotg_phy_enable(hsotg); +	s3c_hsotg_core_init(hsotg); +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	return ret; +} + +#ifdef CONFIG_OF +static const struct of_device_id s3c_hsotg_of_ids[] = { +	{ .compatible = "samsung,s3c6400-hsotg", }, +	{ .compatible = "snps,dwc2", }, +	{ /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, s3c_hsotg_of_ids); +#endif + +static struct platform_driver s3c_hsotg_driver = { +	.driver		= { +		.name	= "s3c-hsotg", +		.owner	= THIS_MODULE, +		.of_match_table = of_match_ptr(s3c_hsotg_of_ids), +	}, +	.probe		= s3c_hsotg_probe, +	.remove		= s3c_hsotg_remove, +	.suspend	= s3c_hsotg_suspend, +	.resume		= s3c_hsotg_resume, +}; + +module_platform_driver(s3c_hsotg_driver); + +MODULE_DESCRIPTION("Samsung S3C USB High-speed/OtG device"); +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c-hsotg"); diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c new file mode 100644 index 00000000000..4d918ed8d34 --- /dev/null +++ b/drivers/usb/dwc2/hcd.c @@ -0,0 +1,2981 @@ +/* + * hcd.c - DesignWare HS OTG Controller host-mode routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the core HCD code, and implements the Linux hc_driver + * API + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +/** + * dwc2_dump_channel_info() - Prints the state of a host channel + * + * @hsotg: Programming view of DWC_otg controller + * @chan:  Pointer to the channel to dump + * + * Must be called with interrupt disabled and spinlock held + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +static void dwc2_dump_channel_info(struct dwc2_hsotg *hsotg, +				   struct dwc2_host_chan *chan) +{ +#ifdef VERBOSE_DEBUG +	int num_channels = hsotg->core_params->host_channels; +	struct dwc2_qh *qh; +	u32 hcchar; +	u32 hcsplt; +	u32 hctsiz; +	u32 hc_dma; +	int i; + +	if (chan == NULL) +		return; + +	hcchar = readl(hsotg->regs + HCCHAR(chan->hc_num)); +	hcsplt = readl(hsotg->regs + HCSPLT(chan->hc_num)); +	hctsiz = readl(hsotg->regs + HCTSIZ(chan->hc_num)); +	hc_dma = readl(hsotg->regs + HCDMA(chan->hc_num)); + +	dev_dbg(hsotg->dev, "  Assigned to channel %p:\n", chan); +	dev_dbg(hsotg->dev, "    hcchar 0x%08x, hcsplt 0x%08x\n", +		hcchar, hcsplt); +	dev_dbg(hsotg->dev, "    hctsiz 0x%08x, hc_dma 0x%08x\n", +		hctsiz, hc_dma); +	dev_dbg(hsotg->dev, "    dev_addr: %d, ep_num: %d, ep_is_in: %d\n", +		chan->dev_addr, chan->ep_num, chan->ep_is_in); +	dev_dbg(hsotg->dev, "    ep_type: %d\n", chan->ep_type); +	dev_dbg(hsotg->dev, "    max_packet: %d\n", chan->max_packet); +	dev_dbg(hsotg->dev, "    data_pid_start: %d\n", chan->data_pid_start); +	dev_dbg(hsotg->dev, "    xfer_started: %d\n", chan->xfer_started); +	dev_dbg(hsotg->dev, "    halt_status: %d\n", chan->halt_status); +	dev_dbg(hsotg->dev, "    xfer_buf: %p\n", chan->xfer_buf); +	dev_dbg(hsotg->dev, "    xfer_dma: %08lx\n", +		(unsigned long)chan->xfer_dma); +	dev_dbg(hsotg->dev, "    xfer_len: %d\n", chan->xfer_len); +	dev_dbg(hsotg->dev, "    qh: %p\n", chan->qh); +	dev_dbg(hsotg->dev, "  NP inactive sched:\n"); +	list_for_each_entry(qh, &hsotg->non_periodic_sched_inactive, +			    qh_list_entry) +		dev_dbg(hsotg->dev, "    %p\n", qh); +	dev_dbg(hsotg->dev, "  NP active sched:\n"); +	list_for_each_entry(qh, &hsotg->non_periodic_sched_active, +			    qh_list_entry) +		dev_dbg(hsotg->dev, "    %p\n", qh); +	dev_dbg(hsotg->dev, "  Channels:\n"); +	for (i = 0; i < num_channels; i++) { +		struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i]; + +		dev_dbg(hsotg->dev, "    %2d: %p\n", i, chan); +	} +#endif /* VERBOSE_DEBUG */ +} + +/* + * Processes all the URBs in a single list of QHs. Completes them with + * -ETIMEDOUT and frees the QTD. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_kill_urbs_in_qh_list(struct dwc2_hsotg *hsotg, +				      struct list_head *qh_list) +{ +	struct dwc2_qh *qh, *qh_tmp; +	struct dwc2_qtd *qtd, *qtd_tmp; + +	list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { +		list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, +					 qtd_list_entry) { +			dwc2_host_complete(hsotg, qtd, -ETIMEDOUT); +			dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); +		} +	} +} + +static void dwc2_qh_list_free(struct dwc2_hsotg *hsotg, +			      struct list_head *qh_list) +{ +	struct dwc2_qtd *qtd, *qtd_tmp; +	struct dwc2_qh *qh, *qh_tmp; +	unsigned long flags; + +	if (!qh_list->next) +		/* The list hasn't been initialized yet */ +		return; + +	spin_lock_irqsave(&hsotg->lock, flags); + +	/* Ensure there are no QTDs or URBs left */ +	dwc2_kill_urbs_in_qh_list(hsotg, qh_list); + +	list_for_each_entry_safe(qh, qh_tmp, qh_list, qh_list_entry) { +		dwc2_hcd_qh_unlink(hsotg, qh); + +		/* Free each QTD in the QH's QTD list */ +		list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, +					 qtd_list_entry) +			dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + +		spin_unlock_irqrestore(&hsotg->lock, flags); +		dwc2_hcd_qh_free(hsotg, qh); +		spin_lock_irqsave(&hsotg->lock, flags); +	} + +	spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_kill_all_urbs(struct dwc2_hsotg *hsotg) +{ +	dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_inactive); +	dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->non_periodic_sched_active); +	dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_inactive); +	dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_ready); +	dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_assigned); +	dwc2_kill_urbs_in_qh_list(hsotg, &hsotg->periodic_sched_queued); +} + +/** + * dwc2_hcd_start() - Starts the HCD when switching to Host mode + * + * @hsotg: Pointer to struct dwc2_hsotg + */ +void dwc2_hcd_start(struct dwc2_hsotg *hsotg) +{ +	u32 hprt0; + +	if (hsotg->op_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 = dwc2_read_hprt0(hsotg); +		hprt0 |= HPRT0_RST; +		writel(hprt0, hsotg->regs + HPRT0); +	} + +	queue_delayed_work(hsotg->wq_otg, &hsotg->start_work, +			   msecs_to_jiffies(50)); +} + +/* Must be called with interrupt disabled and spinlock held */ +static void dwc2_hcd_cleanup_channels(struct dwc2_hsotg *hsotg) +{ +	int num_channels = hsotg->core_params->host_channels; +	struct dwc2_host_chan *channel; +	u32 hcchar; +	int i; + +	if (hsotg->core_params->dma_enable <= 0) { +		/* Flush out any channel requests in slave mode */ +		for (i = 0; i < num_channels; i++) { +			channel = hsotg->hc_ptr_array[i]; +			if (!list_empty(&channel->hc_list_entry)) +				continue; +			hcchar = readl(hsotg->regs + HCCHAR(i)); +			if (hcchar & HCCHAR_CHENA) { +				hcchar &= ~(HCCHAR_CHENA | HCCHAR_EPDIR); +				hcchar |= HCCHAR_CHDIS; +				writel(hcchar, hsotg->regs + HCCHAR(i)); +			} +		} +	} + +	for (i = 0; i < num_channels; i++) { +		channel = hsotg->hc_ptr_array[i]; +		if (!list_empty(&channel->hc_list_entry)) +			continue; +		hcchar = readl(hsotg->regs + HCCHAR(i)); +		if (hcchar & HCCHAR_CHENA) { +			/* Halt the channel */ +			hcchar |= HCCHAR_CHDIS; +			writel(hcchar, hsotg->regs + HCCHAR(i)); +		} + +		dwc2_hc_cleanup(hsotg, channel); +		list_add_tail(&channel->hc_list_entry, &hsotg->free_hc_list); +		/* +		 * Added for Descriptor DMA to prevent channel double cleanup in +		 * release_channel_ddma(), which is called from ep_disable when +		 * device disconnects +		 */ +		channel->qh = NULL; +	} +} + +/** + * dwc2_hcd_disconnect() - Handles disconnect of the HCD + * + * @hsotg: Pointer to struct dwc2_hsotg + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg) +{ +	u32 intr; + +	/* Set status flags for the hub driver */ +	hsotg->flags.b.port_connect_status_change = 1; +	hsotg->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 = readl(hsotg->regs + GINTMSK); +	intr &= ~(GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT); +	writel(intr, hsotg->regs + GINTMSK); +	intr = GINTSTS_NPTXFEMP | GINTSTS_PTXFEMP | GINTSTS_HCHINT; +	writel(intr, hsotg->regs + GINTSTS); + +	/* +	 * 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 (dwc2_is_device_mode(hsotg)) { +		if (hsotg->op_state != OTG_STATE_A_SUSPEND) { +			dev_dbg(hsotg->dev, "Disconnect: PortPower off\n"); +			writel(0, hsotg->regs + HPRT0); +		} + +		dwc2_disable_host_interrupts(hsotg); +	} + +	/* Respond with an error status to all URBs in the schedule */ +	dwc2_kill_all_urbs(hsotg); + +	if (dwc2_is_host_mode(hsotg)) +		/* Clean up any host channels that were in use */ +		dwc2_hcd_cleanup_channels(hsotg); + +	dwc2_host_disconnect(hsotg); +} + +/** + * dwc2_hcd_rem_wakeup() - Handles Remote Wakeup + * + * @hsotg: Pointer to struct dwc2_hsotg + */ +static void dwc2_hcd_rem_wakeup(struct dwc2_hsotg *hsotg) +{ +	if (hsotg->lx_state == DWC2_L2) +		hsotg->flags.b.port_suspend_change = 1; +	else +		hsotg->flags.b.port_l1_change = 1; +} + +/** + * dwc2_hcd_stop() - Halts the DWC_otg host mode operations in a clean manner + * + * @hsotg: Pointer to struct dwc2_hsotg + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_stop(struct dwc2_hsotg *hsotg) +{ +	dev_dbg(hsotg->dev, "DWC OTG HCD STOP\n"); + +	/* +	 * 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 all host-specific interrupts */ +	dwc2_disable_host_interrupts(hsotg); + +	/* Turn off the vbus power */ +	dev_dbg(hsotg->dev, "PortPower off\n"); +	writel(0, hsotg->regs + HPRT0); +} + +static int dwc2_hcd_urb_enqueue(struct dwc2_hsotg *hsotg, +				struct dwc2_hcd_urb *urb, void **ep_handle, +				gfp_t mem_flags) +{ +	struct dwc2_qtd *qtd; +	unsigned long flags; +	u32 intr_mask; +	int retval; +	int dev_speed; + +	if (!hsotg->flags.b.port_connect_status) { +		/* No longer connected */ +		dev_err(hsotg->dev, "Not connected\n"); +		return -ENODEV; +	} + +	dev_speed = dwc2_host_get_speed(hsotg, urb->priv); + +	/* Some configurations cannot support LS traffic on a FS root port */ +	if ((dev_speed == USB_SPEED_LOW) && +	    (hsotg->hw_params.fs_phy_type == GHWCFG2_FS_PHY_TYPE_DEDICATED) && +	    (hsotg->hw_params.hs_phy_type == GHWCFG2_HS_PHY_TYPE_UTMI)) { +		u32 hprt0 = readl(hsotg->regs + HPRT0); +		u32 prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + +		if (prtspd == HPRT0_SPD_FULL_SPEED) +			return -ENODEV; +	} + +	qtd = kzalloc(sizeof(*qtd), mem_flags); +	if (!qtd) +		return -ENOMEM; + +	dwc2_hcd_qtd_init(qtd, urb); +	retval = dwc2_hcd_qtd_add(hsotg, qtd, (struct dwc2_qh **)ep_handle, +				  mem_flags); +	if (retval) { +		dev_err(hsotg->dev, +			"DWC OTG HCD URB Enqueue failed adding QTD. Error status %d\n", +			retval); +		kfree(qtd); +		return retval; +	} + +	intr_mask = readl(hsotg->regs + GINTMSK); +	if (!(intr_mask & GINTSTS_SOF)) { +		enum dwc2_transaction_type tr_type; + +		if (qtd->qh->ep_type == USB_ENDPOINT_XFER_BULK && +		    !(qtd->urb->flags & URB_GIVEBACK_ASAP)) +			/* +			 * Do not schedule SG transactions until qtd has +			 * URB_GIVEBACK_ASAP set +			 */ +			return 0; + +		spin_lock_irqsave(&hsotg->lock, flags); +		tr_type = dwc2_hcd_select_transactions(hsotg); +		if (tr_type != DWC2_TRANSACTION_NONE) +			dwc2_hcd_queue_transactions(hsotg, tr_type); +		spin_unlock_irqrestore(&hsotg->lock, flags); +	} + +	return 0; +} + +/* Must be called with interrupt disabled and spinlock held */ +static int dwc2_hcd_urb_dequeue(struct dwc2_hsotg *hsotg, +				struct dwc2_hcd_urb *urb) +{ +	struct dwc2_qh *qh; +	struct dwc2_qtd *urb_qtd; + +	urb_qtd = urb->qtd; +	if (!urb_qtd) { +		dev_dbg(hsotg->dev, "## Urb QTD is NULL ##\n"); +		return -EINVAL; +	} + +	qh = urb_qtd->qh; +	if (!qh) { +		dev_dbg(hsotg->dev, "## Urb QTD QH is NULL ##\n"); +		return -EINVAL; +	} + +	urb->priv = NULL; + +	if (urb_qtd->in_process && qh->channel) { +		dwc2_dump_channel_info(hsotg, qh->channel); + +		/* The QTD is in process (it has been assigned to a channel) */ +		if (hsotg->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. +			 */ +			dwc2_hc_halt(hsotg, qh->channel, +				     DWC2_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. +	 */ +	if (hsotg->core_params->dma_desc_enable <= 0) { +		u8 in_process = urb_qtd->in_process; + +		dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); +		if (in_process) { +			dwc2_hcd_qh_deactivate(hsotg, qh, 0); +			qh->channel = NULL; +		} else if (list_empty(&qh->qtd_list)) { +			dwc2_hcd_qh_unlink(hsotg, qh); +		} +	} else { +		dwc2_hcd_qtd_unlink_and_free(hsotg, urb_qtd, qh); +	} + +	return 0; +} + +/* Must NOT be called with interrupt disabled or spinlock held */ +static int dwc2_hcd_endpoint_disable(struct dwc2_hsotg *hsotg, +				     struct usb_host_endpoint *ep, int retry) +{ +	struct dwc2_qtd *qtd, *qtd_tmp; +	struct dwc2_qh *qh; +	unsigned long flags; +	int rc; + +	spin_lock_irqsave(&hsotg->lock, flags); + +	qh = ep->hcpriv; +	if (!qh) { +		rc = -EINVAL; +		goto err; +	} + +	while (!list_empty(&qh->qtd_list) && retry--) { +		if (retry == 0) { +			dev_err(hsotg->dev, +				"## timeout in dwc2_hcd_endpoint_disable() ##\n"); +			rc = -EBUSY; +			goto err; +		} + +		spin_unlock_irqrestore(&hsotg->lock, flags); +		usleep_range(20000, 40000); +		spin_lock_irqsave(&hsotg->lock, flags); +		qh = ep->hcpriv; +		if (!qh) { +			rc = -EINVAL; +			goto err; +		} +	} + +	dwc2_hcd_qh_unlink(hsotg, qh); + +	/* Free each QTD in the QH's QTD list */ +	list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) +		dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + +	ep->hcpriv = NULL; +	spin_unlock_irqrestore(&hsotg->lock, flags); +	dwc2_hcd_qh_free(hsotg, qh); + +	return 0; + +err: +	ep->hcpriv = NULL; +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	return rc; +} + +/* Must be called with interrupt disabled and spinlock held */ +static int dwc2_hcd_endpoint_reset(struct dwc2_hsotg *hsotg, +				   struct usb_host_endpoint *ep) +{ +	struct dwc2_qh *qh = ep->hcpriv; + +	if (!qh) +		return -EINVAL; + +	qh->data_toggle = DWC2_HC_PID_DATA0; + +	return 0; +} + +/* + * Initializes dynamic portions of the DWC_otg HCD state + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_hcd_reinit(struct dwc2_hsotg *hsotg) +{ +	struct dwc2_host_chan *chan, *chan_tmp; +	int num_channels; +	int i; + +	hsotg->flags.d32 = 0; +	hsotg->non_periodic_qh_ptr = &hsotg->non_periodic_sched_active; + +	if (hsotg->core_params->uframe_sched > 0) { +		hsotg->available_host_channels = +			hsotg->core_params->host_channels; +	} else { +		hsotg->non_periodic_channels = 0; +		hsotg->periodic_channels = 0; +	} + +	/* +	 * Put all channels in the free channel list and clean up channel +	 * states +	 */ +	list_for_each_entry_safe(chan, chan_tmp, &hsotg->free_hc_list, +				 hc_list_entry) +		list_del_init(&chan->hc_list_entry); + +	num_channels = hsotg->core_params->host_channels; +	for (i = 0; i < num_channels; i++) { +		chan = hsotg->hc_ptr_array[i]; +		list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); +		dwc2_hc_cleanup(hsotg, chan); +	} + +	/* Initialize the DWC core for host mode operation */ +	dwc2_core_host_init(hsotg); +} + +static void dwc2_hc_init_split(struct dwc2_hsotg *hsotg, +			       struct dwc2_host_chan *chan, +			       struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) +{ +	int hub_addr, hub_port; + +	chan->do_split = 1; +	chan->xact_pos = qtd->isoc_split_pos; +	chan->complete_split = qtd->complete_split; +	dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); +	chan->hub_addr = (u8)hub_addr; +	chan->hub_port = (u8)hub_port; +} + +static void *dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg, +			       struct dwc2_host_chan *chan, +			       struct dwc2_qtd *qtd, void *bufptr) +{ +	struct dwc2_hcd_urb *urb = qtd->urb; +	struct dwc2_hcd_iso_packet_desc *frame_desc; + +	switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) { +	case USB_ENDPOINT_XFER_CONTROL: +		chan->ep_type = USB_ENDPOINT_XFER_CONTROL; + +		switch (qtd->control_phase) { +		case DWC2_CONTROL_SETUP: +			dev_vdbg(hsotg->dev, "  Control setup transaction\n"); +			chan->do_ping = 0; +			chan->ep_is_in = 0; +			chan->data_pid_start = DWC2_HC_PID_SETUP; +			if (hsotg->core_params->dma_enable > 0) +				chan->xfer_dma = urb->setup_dma; +			else +				chan->xfer_buf = urb->setup_packet; +			chan->xfer_len = 8; +			bufptr = NULL; +			break; + +		case DWC2_CONTROL_DATA: +			dev_vdbg(hsotg->dev, "  Control data transaction\n"); +			chan->data_pid_start = qtd->data_toggle; +			break; + +		case DWC2_CONTROL_STATUS: +			/* +			 * Direction is opposite of data direction or IN if no +			 * data +			 */ +			dev_vdbg(hsotg->dev, "  Control status transaction\n"); +			if (urb->length == 0) +				chan->ep_is_in = 1; +			else +				chan->ep_is_in = +					dwc2_hcd_is_pipe_out(&urb->pipe_info); +			if (chan->ep_is_in) +				chan->do_ping = 0; +			chan->data_pid_start = DWC2_HC_PID_DATA1; +			chan->xfer_len = 0; +			if (hsotg->core_params->dma_enable > 0) +				chan->xfer_dma = hsotg->status_buf_dma; +			else +				chan->xfer_buf = hsotg->status_buf; +			bufptr = NULL; +			break; +		} +		break; + +	case USB_ENDPOINT_XFER_BULK: +		chan->ep_type = USB_ENDPOINT_XFER_BULK; +		break; + +	case USB_ENDPOINT_XFER_INT: +		chan->ep_type = USB_ENDPOINT_XFER_INT; +		break; + +	case USB_ENDPOINT_XFER_ISOC: +		chan->ep_type = USB_ENDPOINT_XFER_ISOC; +		if (hsotg->core_params->dma_desc_enable > 0) +			break; + +		frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; +		frame_desc->status = 0; + +		if (hsotg->core_params->dma_enable > 0) { +			chan->xfer_dma = urb->dma; +			chan->xfer_dma += frame_desc->offset + +					qtd->isoc_split_offset; +		} else { +			chan->xfer_buf = urb->buf; +			chan->xfer_buf += frame_desc->offset + +					qtd->isoc_split_offset; +		} + +		chan->xfer_len = frame_desc->length - qtd->isoc_split_offset; + +		/* For non-dword aligned buffers */ +		if (hsotg->core_params->dma_enable > 0 && +		    (chan->xfer_dma & 0x3)) +			bufptr = (u8 *)urb->buf + frame_desc->offset + +					qtd->isoc_split_offset; +		else +			bufptr = NULL; + +		if (chan->xact_pos == DWC2_HCSPLT_XACTPOS_ALL) { +			if (chan->xfer_len <= 188) +				chan->xact_pos = DWC2_HCSPLT_XACTPOS_ALL; +			else +				chan->xact_pos = DWC2_HCSPLT_XACTPOS_BEGIN; +		} +		break; +	} + +	return bufptr; +} + +static int dwc2_hc_setup_align_buf(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +				   struct dwc2_host_chan *chan, void *bufptr) +{ +	u32 buf_size; + +	if (chan->ep_type != USB_ENDPOINT_XFER_ISOC) +		buf_size = hsotg->core_params->max_transfer_size; +	else +		buf_size = 4096; + +	if (!qh->dw_align_buf) { +		qh->dw_align_buf = dma_alloc_coherent(hsotg->dev, buf_size, +						      &qh->dw_align_buf_dma, +						      GFP_ATOMIC); +		if (!qh->dw_align_buf) +			return -ENOMEM; +	} + +	if (!chan->ep_is_in && chan->xfer_len) { +		dma_sync_single_for_cpu(hsotg->dev, chan->xfer_dma, buf_size, +					DMA_TO_DEVICE); +		memcpy(qh->dw_align_buf, bufptr, chan->xfer_len); +		dma_sync_single_for_device(hsotg->dev, chan->xfer_dma, buf_size, +					   DMA_TO_DEVICE); +	} + +	chan->align_buf = qh->dw_align_buf_dma; +	return 0; +} + +/** + * dwc2_assign_and_init_hc() - 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. + * + * @hsotg: The HCD state structure + * @qh:    Transactions from the first QTD for this QH are selected and assigned + *         to a free host channel + */ +static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	struct dwc2_host_chan *chan; +	struct dwc2_hcd_urb *urb; +	struct dwc2_qtd *qtd; +	void *bufptr = NULL; + +	if (dbg_qh(qh)) +		dev_vdbg(hsotg->dev, "%s(%p,%p)\n", __func__, hsotg, qh); + +	if (list_empty(&qh->qtd_list)) { +		dev_dbg(hsotg->dev, "No QTDs in QH list\n"); +		return -ENOMEM; +	} + +	if (list_empty(&hsotg->free_hc_list)) { +		dev_dbg(hsotg->dev, "No free channel to assign\n"); +		return -ENOMEM; +	} + +	chan = list_first_entry(&hsotg->free_hc_list, struct dwc2_host_chan, +				hc_list_entry); + +	/* Remove host channel from free list */ +	list_del_init(&chan->hc_list_entry); + +	qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry); +	urb = qtd->urb; +	qh->channel = chan; +	qtd->in_process = 1; + +	/* +	 * Use usb_pipedevice to determine device address. This address is +	 * 0 before the SET_ADDRESS command and the correct address afterward. +	 */ +	chan->dev_addr = dwc2_hcd_get_dev_addr(&urb->pipe_info); +	chan->ep_num = dwc2_hcd_get_ep_num(&urb->pipe_info); +	chan->speed = qh->dev_speed; +	chan->max_packet = dwc2_max_packet(qh->maxp); + +	chan->xfer_started = 0; +	chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS; +	chan->error_state = (qtd->error_count > 0); +	chan->halt_on_queue = 0; +	chan->halt_pending = 0; +	chan->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. +	 */ + +	chan->ep_is_in = (dwc2_hcd_is_pipe_in(&urb->pipe_info) != 0); +	if (chan->ep_is_in) +		chan->do_ping = 0; +	else +		chan->do_ping = qh->ping_state; + +	chan->data_pid_start = qh->data_toggle; +	chan->multi_count = 1; + +	if (urb->actual_length > urb->length && +		!dwc2_hcd_is_pipe_in(&urb->pipe_info)) +		urb->actual_length = urb->length; + +	if (hsotg->core_params->dma_enable > 0) { +		chan->xfer_dma = urb->dma + urb->actual_length; + +		/* For non-dword aligned case */ +		if (hsotg->core_params->dma_desc_enable <= 0 && +		    (chan->xfer_dma & 0x3)) +			bufptr = (u8 *)urb->buf + urb->actual_length; +	} else { +		chan->xfer_buf = (u8 *)urb->buf + urb->actual_length; +	} + +	chan->xfer_len = urb->length - urb->actual_length; +	chan->xfer_count = 0; + +	/* Set the split attributes if required */ +	if (qh->do_split) +		dwc2_hc_init_split(hsotg, chan, qtd, urb); +	else +		chan->do_split = 0; + +	/* Set the transfer attributes */ +	bufptr = dwc2_hc_init_xfer(hsotg, chan, qtd, bufptr); + +	/* Non DWORD-aligned buffer case */ +	if (bufptr) { +		dev_vdbg(hsotg->dev, "Non-aligned buffer\n"); +		if (dwc2_hc_setup_align_buf(hsotg, qh, chan, bufptr)) { +			dev_err(hsotg->dev, +				"%s: Failed to allocate memory to handle non-dword aligned buffer\n", +				__func__); +			/* Add channel back to free list */ +			chan->align_buf = 0; +			chan->multi_count = 0; +			list_add_tail(&chan->hc_list_entry, +				      &hsotg->free_hc_list); +			qtd->in_process = 0; +			qh->channel = NULL; +			return -ENOMEM; +		} +	} else { +		chan->align_buf = 0; +	} + +	if (chan->ep_type == USB_ENDPOINT_XFER_INT || +	    chan->ep_type == USB_ENDPOINT_XFER_ISOC) +		/* +		 * This value may be modified when the transfer is started +		 * to reflect the actual transfer length +		 */ +		chan->multi_count = dwc2_hb_mult(qh->maxp); + +	if (hsotg->core_params->dma_desc_enable > 0) +		chan->desc_list_addr = qh->desc_list_dma; + +	dwc2_hc_init(hsotg, chan); +	chan->qh = qh; + +	return 0; +} + +/** + * dwc2_hcd_select_transactions() - Selects transactions from the HCD transfer + * schedule and assigns them to available host channels. Called from the HCD + * interrupt handler functions. + * + * @hsotg: The HCD state structure + * + * Return: The types of new transactions that were assigned to host channels + */ +enum dwc2_transaction_type dwc2_hcd_select_transactions( +		struct dwc2_hsotg *hsotg) +{ +	enum dwc2_transaction_type ret_val = DWC2_TRANSACTION_NONE; +	struct list_head *qh_ptr; +	struct dwc2_qh *qh; +	int num_channels; + +#ifdef DWC2_DEBUG_SOF +	dev_vdbg(hsotg->dev, "  Select Transactions\n"); +#endif + +	/* Process entries in the periodic ready list */ +	qh_ptr = hsotg->periodic_sched_ready.next; +	while (qh_ptr != &hsotg->periodic_sched_ready) { +		if (list_empty(&hsotg->free_hc_list)) +			break; +		if (hsotg->core_params->uframe_sched > 0) { +			if (hsotg->available_host_channels <= 1) +				break; +			hsotg->available_host_channels--; +		} +		qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); +		if (dwc2_assign_and_init_hc(hsotg, qh)) +			break; + +		/* +		 * Move the QH from the periodic ready schedule to the +		 * periodic assigned schedule +		 */ +		qh_ptr = qh_ptr->next; +		list_move(&qh->qh_list_entry, &hsotg->periodic_sched_assigned); +		ret_val = DWC2_TRANSACTION_PERIODIC; +	} + +	/* +	 * 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. +	 */ +	num_channels = hsotg->core_params->host_channels; +	qh_ptr = hsotg->non_periodic_sched_inactive.next; +	while (qh_ptr != &hsotg->non_periodic_sched_inactive) { +		if (hsotg->core_params->uframe_sched <= 0 && +		    hsotg->non_periodic_channels >= num_channels - +						hsotg->periodic_channels) +			break; +		if (list_empty(&hsotg->free_hc_list)) +			break; +		qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); +		if (hsotg->core_params->uframe_sched > 0) { +			if (hsotg->available_host_channels < 1) +				break; +			hsotg->available_host_channels--; +		} + +		if (dwc2_assign_and_init_hc(hsotg, qh)) +			break; + +		/* +		 * 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, +			  &hsotg->non_periodic_sched_active); + +		if (ret_val == DWC2_TRANSACTION_NONE) +			ret_val = DWC2_TRANSACTION_NON_PERIODIC; +		else +			ret_val = DWC2_TRANSACTION_ALL; + +		if (hsotg->core_params->uframe_sched <= 0) +			hsotg->non_periodic_channels++; +	} + +	return ret_val; +} + +/** + * dwc2_queue_transaction() - Attempts to queue a single transaction request for + * a host channel associated with either a periodic or non-periodic transfer + * + * @hsotg: The HCD state structure + * @chan:  Host channel descriptor associated with either a periodic or + *         non-periodic transfer + * @fifo_dwords_avail: Number of DWORDs available in the periodic Tx FIFO + *                     for periodic transfers or the non-periodic Tx FIFO + *                     for non-periodic transfers + * + * Return: 1 if a request is queued and more requests may be needed to + * complete the transfer, 0 if no more requests are required for this + * transfer, -1 if there is insufficient space in the Tx FIFO + * + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static int dwc2_queue_transaction(struct dwc2_hsotg *hsotg, +				  struct dwc2_host_chan *chan, +				  u16 fifo_dwords_avail) +{ +	int retval = 0; + +	if (hsotg->core_params->dma_enable > 0) { +		if (hsotg->core_params->dma_desc_enable > 0) { +			if (!chan->xfer_started || +			    chan->ep_type == USB_ENDPOINT_XFER_ISOC) { +				dwc2_hcd_start_xfer_ddma(hsotg, chan->qh); +				chan->qh->ping_state = 0; +			} +		} else if (!chan->xfer_started) { +			dwc2_hc_start_transfer(hsotg, chan); +			chan->qh->ping_state = 0; +		} +	} else if (chan->halt_pending) { +		/* Don't queue a request if the channel has been halted */ +	} else if (chan->halt_on_queue) { +		dwc2_hc_halt(hsotg, chan, chan->halt_status); +	} else if (chan->do_ping) { +		if (!chan->xfer_started) +			dwc2_hc_start_transfer(hsotg, chan); +	} else if (!chan->ep_is_in || +		   chan->data_pid_start == DWC2_HC_PID_SETUP) { +		if ((fifo_dwords_avail * 4) >= chan->max_packet) { +			if (!chan->xfer_started) { +				dwc2_hc_start_transfer(hsotg, chan); +				retval = 1; +			} else { +				retval = dwc2_hc_continue_transfer(hsotg, chan); +			} +		} else { +			retval = -1; +		} +	} else { +		if (!chan->xfer_started) { +			dwc2_hc_start_transfer(hsotg, chan); +			retval = 1; +		} else { +			retval = dwc2_hc_continue_transfer(hsotg, chan); +		} +	} + +	return retval; +} + +/* + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_process_periodic_channels(struct dwc2_hsotg *hsotg) +{ +	struct list_head *qh_ptr; +	struct dwc2_qh *qh; +	u32 tx_status; +	u32 fspcavail; +	u32 gintmsk; +	int status; +	int no_queue_space = 0; +	int no_fifo_space = 0; +	u32 qspcavail; + +	if (dbg_perio()) +		dev_vdbg(hsotg->dev, "Queue periodic transactions\n"); + +	tx_status = readl(hsotg->regs + HPTXSTS); +	qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> +		    TXSTS_QSPCAVAIL_SHIFT; +	fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> +		    TXSTS_FSPCAVAIL_SHIFT; + +	if (dbg_perio()) { +		dev_vdbg(hsotg->dev, "  P Tx Req Queue Space Avail (before queue): %d\n", +			 qspcavail); +		dev_vdbg(hsotg->dev, "  P Tx FIFO Space Avail (before queue): %d\n", +			 fspcavail); +	} + +	qh_ptr = hsotg->periodic_sched_assigned.next; +	while (qh_ptr != &hsotg->periodic_sched_assigned) { +		tx_status = readl(hsotg->regs + HPTXSTS); +		qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> +			    TXSTS_QSPCAVAIL_SHIFT; +		if (qspcavail == 0) { +			no_queue_space = 1; +			break; +		} + +		qh = list_entry(qh_ptr, struct dwc2_qh, qh_list_entry); +		if (!qh->channel) { +			qh_ptr = qh_ptr->next; +			continue; +		} + +		/* Make sure EP's TT buffer is clean before queueing qtds */ +		if (qh->tt_buffer_dirty) { +			qh_ptr = qh_ptr->next; +			continue; +		} + +		/* +		 * 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 (hsotg->core_params->dma_enable <= 0 && +				qh->channel->multi_count > 1) +			hsotg->queuing_high_bandwidth = 1; + +		fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> +			    TXSTS_FSPCAVAIL_SHIFT; +		status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); +		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 (hsotg->core_params->dma_enable > 0 || 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, +				  &hsotg->periodic_sched_queued); + +			/* done queuing high bandwidth */ +			hsotg->queuing_high_bandwidth = 0; +		} +	} + +	if (hsotg->core_params->dma_enable <= 0) { +		tx_status = readl(hsotg->regs + HPTXSTS); +		qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> +			    TXSTS_QSPCAVAIL_SHIFT; +		fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> +			    TXSTS_FSPCAVAIL_SHIFT; +		if (dbg_perio()) { +			dev_vdbg(hsotg->dev, +				 "  P Tx Req Queue Space Avail (after queue): %d\n", +				 qspcavail); +			dev_vdbg(hsotg->dev, +				 "  P Tx FIFO Space Avail (after queue): %d\n", +				 fspcavail); +		} + +		if (!list_empty(&hsotg->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.) +			 */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk |= GINTSTS_PTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} 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. +			 */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk &= ~GINTSTS_PTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} +	} +} + +/* + * 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. + * + * Must be called with interrupt disabled and spinlock held + */ +static void dwc2_process_non_periodic_channels(struct dwc2_hsotg *hsotg) +{ +	struct list_head *orig_qh_ptr; +	struct dwc2_qh *qh; +	u32 tx_status; +	u32 qspcavail; +	u32 fspcavail; +	u32 gintmsk; +	int status; +	int no_queue_space = 0; +	int no_fifo_space = 0; +	int more_to_do = 0; + +	dev_vdbg(hsotg->dev, "Queue non-periodic transactions\n"); + +	tx_status = readl(hsotg->regs + GNPTXSTS); +	qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> +		    TXSTS_QSPCAVAIL_SHIFT; +	fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> +		    TXSTS_FSPCAVAIL_SHIFT; +	dev_vdbg(hsotg->dev, "  NP Tx Req Queue Space Avail (before queue): %d\n", +		 qspcavail); +	dev_vdbg(hsotg->dev, "  NP Tx FIFO Space Avail (before queue): %d\n", +		 fspcavail); + +	/* +	 * Keep track of the starting point. Skip over the start-of-list +	 * entry. +	 */ +	if (hsotg->non_periodic_qh_ptr == &hsotg->non_periodic_sched_active) +		hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; +	orig_qh_ptr = hsotg->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 = readl(hsotg->regs + GNPTXSTS); +		qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> +			    TXSTS_QSPCAVAIL_SHIFT; +		if (hsotg->core_params->dma_enable <= 0 && qspcavail == 0) { +			no_queue_space = 1; +			break; +		} + +		qh = list_entry(hsotg->non_periodic_qh_ptr, struct dwc2_qh, +				qh_list_entry); +		if (!qh->channel) +			goto next; + +		/* Make sure EP's TT buffer is clean before queueing qtds */ +		if (qh->tt_buffer_dirty) +			goto next; + +		fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> +			    TXSTS_FSPCAVAIL_SHIFT; +		status = dwc2_queue_transaction(hsotg, qh->channel, fspcavail); + +		if (status > 0) { +			more_to_do = 1; +		} else if (status < 0) { +			no_fifo_space = 1; +			break; +		} +next: +		/* Advance to next QH, skipping start-of-list entry */ +		hsotg->non_periodic_qh_ptr = hsotg->non_periodic_qh_ptr->next; +		if (hsotg->non_periodic_qh_ptr == +				&hsotg->non_periodic_sched_active) +			hsotg->non_periodic_qh_ptr = +					hsotg->non_periodic_qh_ptr->next; +	} while (hsotg->non_periodic_qh_ptr != orig_qh_ptr); + +	if (hsotg->core_params->dma_enable <= 0) { +		tx_status = readl(hsotg->regs + GNPTXSTS); +		qspcavail = (tx_status & TXSTS_QSPCAVAIL_MASK) >> +			    TXSTS_QSPCAVAIL_SHIFT; +		fspcavail = (tx_status & TXSTS_FSPCAVAIL_MASK) >> +			    TXSTS_FSPCAVAIL_SHIFT; +		dev_vdbg(hsotg->dev, +			 "  NP Tx Req Queue Space Avail (after queue): %d\n", +			 qspcavail); +		dev_vdbg(hsotg->dev, +			 "  NP Tx FIFO Space Avail (after queue): %d\n", +			 fspcavail); + +		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.) +			 */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk |= GINTSTS_NPTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} 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. +			 */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk &= ~GINTSTS_NPTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} +	} +} + +/** + * dwc2_hcd_queue_transactions() - Processes the currently active host channels + * and queues transactions for these channels to the DWC_otg controller. Called + * from the HCD interrupt handler functions. + * + * @hsotg:   The HCD state structure + * @tr_type: The type(s) of transactions to queue (non-periodic, periodic, + *           or both) + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, +				 enum dwc2_transaction_type tr_type) +{ +#ifdef DWC2_DEBUG_SOF +	dev_vdbg(hsotg->dev, "Queue Transactions\n"); +#endif +	/* Process host channels associated with periodic transfers */ +	if ((tr_type == DWC2_TRANSACTION_PERIODIC || +	     tr_type == DWC2_TRANSACTION_ALL) && +	    !list_empty(&hsotg->periodic_sched_assigned)) +		dwc2_process_periodic_channels(hsotg); + +	/* Process host channels associated with non-periodic transfers */ +	if (tr_type == DWC2_TRANSACTION_NON_PERIODIC || +	    tr_type == DWC2_TRANSACTION_ALL) { +		if (!list_empty(&hsotg->non_periodic_sched_active)) { +			dwc2_process_non_periodic_channels(hsotg); +		} else { +			/* +			 * Ensure NP Tx FIFO empty interrupt is disabled when +			 * there are no non-periodic transfers to process +			 */ +			u32 gintmsk = readl(hsotg->regs + GINTMSK); + +			gintmsk &= ~GINTSTS_NPTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} +	} +} + +static void dwc2_conn_id_status_change(struct work_struct *work) +{ +	struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, +						wf_otg); +	u32 count = 0; +	u32 gotgctl; + +	dev_dbg(hsotg->dev, "%s()\n", __func__); + +	gotgctl = readl(hsotg->regs + GOTGCTL); +	dev_dbg(hsotg->dev, "gotgctl=%0x\n", gotgctl); +	dev_dbg(hsotg->dev, "gotgctl.b.conidsts=%d\n", +		!!(gotgctl & GOTGCTL_CONID_B)); + +	/* B-Device connector (Device Mode) */ +	if (gotgctl & GOTGCTL_CONID_B) { +		/* Wait for switch to device mode */ +		dev_dbg(hsotg->dev, "connId B\n"); +		while (!dwc2_is_device_mode(hsotg)) { +			dev_info(hsotg->dev, +				 "Waiting for Peripheral Mode, Mode=%s\n", +				 dwc2_is_host_mode(hsotg) ? "Host" : +				 "Peripheral"); +			usleep_range(20000, 40000); +			if (++count > 250) +				break; +		} +		if (count > 250) +			dev_err(hsotg->dev, +				"Connection id status change timed out\n"); +		hsotg->op_state = OTG_STATE_B_PERIPHERAL; +		dwc2_core_init(hsotg, false, -1); +		dwc2_enable_global_interrupts(hsotg); +	} else { +		/* A-Device connector (Host Mode) */ +		dev_dbg(hsotg->dev, "connId A\n"); +		while (!dwc2_is_host_mode(hsotg)) { +			dev_info(hsotg->dev, "Waiting for Host Mode, Mode=%s\n", +				 dwc2_is_host_mode(hsotg) ? +				 "Host" : "Peripheral"); +			usleep_range(20000, 40000); +			if (++count > 250) +				break; +		} +		if (count > 250) +			dev_err(hsotg->dev, +				"Connection id status change timed out\n"); +		hsotg->op_state = OTG_STATE_A_HOST; + +		/* Initialize the Core for Host mode */ +		dwc2_core_init(hsotg, false, -1); +		dwc2_enable_global_interrupts(hsotg); +		dwc2_hcd_start(hsotg); +	} +} + +static void dwc2_wakeup_detected(unsigned long data) +{ +	struct dwc2_hsotg *hsotg = (struct dwc2_hsotg *)data; +	u32 hprt0; + +	dev_dbg(hsotg->dev, "%s()\n", __func__); + +	/* +	 * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms +	 * so that OPT tests pass with all PHYs.) +	 */ +	hprt0 = dwc2_read_hprt0(hsotg); +	dev_dbg(hsotg->dev, "Resume: HPRT0=%0x\n", hprt0); +	hprt0 &= ~HPRT0_RES; +	writel(hprt0, hsotg->regs + HPRT0); +	dev_dbg(hsotg->dev, "Clear Resume: HPRT0=%0x\n", +		readl(hsotg->regs + HPRT0)); + +	dwc2_hcd_rem_wakeup(hsotg); + +	/* Change to L0 state */ +	hsotg->lx_state = DWC2_L0; +} + +static int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg) +{ +	struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + +	return hcd->self.b_hnp_enable; +} + +/* Must NOT be called with interrupt disabled or spinlock held */ +static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +{ +	unsigned long flags; +	u32 hprt0; +	u32 pcgctl; +	u32 gotgctl; + +	dev_dbg(hsotg->dev, "%s()\n", __func__); + +	spin_lock_irqsave(&hsotg->lock, flags); + +	if (windex == hsotg->otg_port && dwc2_host_is_b_hnp_enabled(hsotg)) { +		gotgctl = readl(hsotg->regs + GOTGCTL); +		gotgctl |= GOTGCTL_HSTSETHNPEN; +		writel(gotgctl, hsotg->regs + GOTGCTL); +		hsotg->op_state = OTG_STATE_A_SUSPEND; +	} + +	hprt0 = dwc2_read_hprt0(hsotg); +	hprt0 |= HPRT0_SUSP; +	writel(hprt0, hsotg->regs + HPRT0); + +	/* Update lx_state */ +	hsotg->lx_state = DWC2_L2; + +	/* Suspend the Phy Clock */ +	pcgctl = readl(hsotg->regs + PCGCTL); +	pcgctl |= PCGCTL_STOPPCLK; +	writel(pcgctl, hsotg->regs + PCGCTL); +	udelay(10); + +	/* For HNP the bus must be suspended for at least 200ms */ +	if (dwc2_host_is_b_hnp_enabled(hsotg)) { +		pcgctl = readl(hsotg->regs + PCGCTL); +		pcgctl &= ~PCGCTL_STOPPCLK; +		writel(pcgctl, hsotg->regs + PCGCTL); + +		spin_unlock_irqrestore(&hsotg->lock, flags); + +		usleep_range(200000, 250000); +	} else { +		spin_unlock_irqrestore(&hsotg->lock, flags); +	} +} + +/* Handles hub class-specific requests */ +static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, +				u16 wvalue, u16 windex, char *buf, u16 wlength) +{ +	struct usb_hub_descriptor *hub_desc; +	int retval = 0; +	u32 hprt0; +	u32 port_status; +	u32 speed; +	u32 pcgctl; + +	switch (typereq) { +	case ClearHubFeature: +		dev_dbg(hsotg->dev, "ClearHubFeature %1xh\n", wvalue); + +		switch (wvalue) { +		case C_HUB_LOCAL_POWER: +		case C_HUB_OVER_CURRENT: +			/* Nothing required here */ +			break; + +		default: +			retval = -EINVAL; +			dev_err(hsotg->dev, +				"ClearHubFeature request %1xh unknown\n", +				wvalue); +		} +		break; + +	case ClearPortFeature: +		if (wvalue != USB_PORT_FEAT_L1) +			if (!windex || windex > 1) +				goto error; +		switch (wvalue) { +		case USB_PORT_FEAT_ENABLE: +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_ENABLE\n"); +			hprt0 = dwc2_read_hprt0(hsotg); +			hprt0 |= HPRT0_ENA; +			writel(hprt0, hsotg->regs + HPRT0); +			break; + +		case USB_PORT_FEAT_SUSPEND: +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); +			writel(0, hsotg->regs + PCGCTL); +			usleep_range(20000, 40000); + +			hprt0 = dwc2_read_hprt0(hsotg); +			hprt0 |= HPRT0_RES; +			writel(hprt0, hsotg->regs + HPRT0); +			hprt0 &= ~HPRT0_SUSP; +			usleep_range(100000, 150000); + +			hprt0 &= ~HPRT0_RES; +			writel(hprt0, hsotg->regs + HPRT0); +			break; + +		case USB_PORT_FEAT_POWER: +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_POWER\n"); +			hprt0 = dwc2_read_hprt0(hsotg); +			hprt0 &= ~HPRT0_PWR; +			writel(hprt0, hsotg->regs + HPRT0); +			break; + +		case USB_PORT_FEAT_INDICATOR: +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_INDICATOR\n"); +			/* Port indicator not supported */ +			break; + +		case USB_PORT_FEAT_C_CONNECTION: +			/* +			 * Clears driver's internal Connect Status Change flag +			 */ +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n"); +			hsotg->flags.b.port_connect_status_change = 0; +			break; + +		case USB_PORT_FEAT_C_RESET: +			/* Clears driver's internal Port Reset Change flag */ +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_C_RESET\n"); +			hsotg->flags.b.port_reset_change = 0; +			break; + +		case USB_PORT_FEAT_C_ENABLE: +			/* +			 * Clears the driver's internal Port Enable/Disable +			 * Change flag +			 */ +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_C_ENABLE\n"); +			hsotg->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 +			 */ +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n"); +			hsotg->flags.b.port_suspend_change = 0; +			break; + +		case USB_PORT_FEAT_C_PORT_L1: +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_C_PORT_L1\n"); +			hsotg->flags.b.port_l1_change = 0; +			break; + +		case USB_PORT_FEAT_C_OVER_CURRENT: +			dev_dbg(hsotg->dev, +				"ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n"); +			hsotg->flags.b.port_over_current_change = 0; +			break; + +		default: +			retval = -EINVAL; +			dev_err(hsotg->dev, +				"ClearPortFeature request %1xh unknown or unsupported\n", +				wvalue); +		} +		break; + +	case GetHubDescriptor: +		dev_dbg(hsotg->dev, "GetHubDescriptor\n"); +		hub_desc = (struct usb_hub_descriptor *)buf; +		hub_desc->bDescLength = 9; +		hub_desc->bDescriptorType = 0x29; +		hub_desc->bNbrPorts = 1; +		hub_desc->wHubCharacteristics = cpu_to_le16(0x08); +		hub_desc->bPwrOn2PwrGood = 1; +		hub_desc->bHubContrCurrent = 0; +		hub_desc->u.hs.DeviceRemovable[0] = 0; +		hub_desc->u.hs.DeviceRemovable[1] = 0xff; +		break; + +	case GetHubStatus: +		dev_dbg(hsotg->dev, "GetHubStatus\n"); +		memset(buf, 0, 4); +		break; + +	case GetPortStatus: +		dev_vdbg(hsotg->dev, +			 "GetPortStatus wIndex=0x%04x flags=0x%08x\n", windex, +			 hsotg->flags.d32); +		if (!windex || windex > 1) +			goto error; + +		port_status = 0; +		if (hsotg->flags.b.port_connect_status_change) +			port_status |= USB_PORT_STAT_C_CONNECTION << 16; +		if (hsotg->flags.b.port_enable_change) +			port_status |= USB_PORT_STAT_C_ENABLE << 16; +		if (hsotg->flags.b.port_suspend_change) +			port_status |= USB_PORT_STAT_C_SUSPEND << 16; +		if (hsotg->flags.b.port_l1_change) +			port_status |= USB_PORT_STAT_C_L1 << 16; +		if (hsotg->flags.b.port_reset_change) +			port_status |= USB_PORT_STAT_C_RESET << 16; +		if (hsotg->flags.b.port_over_current_change) { +			dev_warn(hsotg->dev, "Overcurrent change detected\n"); +			port_status |= USB_PORT_STAT_C_OVERCURRENT << 16; +		} + +		if (!hsotg->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 = readl(hsotg->regs + HPRT0); +		dev_vdbg(hsotg->dev, "  HPRT0: 0x%08x\n", hprt0); + +		if (hprt0 & HPRT0_CONNSTS) +			port_status |= USB_PORT_STAT_CONNECTION; +		if (hprt0 & HPRT0_ENA) +			port_status |= USB_PORT_STAT_ENABLE; +		if (hprt0 & HPRT0_SUSP) +			port_status |= USB_PORT_STAT_SUSPEND; +		if (hprt0 & HPRT0_OVRCURRACT) +			port_status |= USB_PORT_STAT_OVERCURRENT; +		if (hprt0 & HPRT0_RST) +			port_status |= USB_PORT_STAT_RESET; +		if (hprt0 & HPRT0_PWR) +			port_status |= USB_PORT_STAT_POWER; + +		speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; +		if (speed == HPRT0_SPD_HIGH_SPEED) +			port_status |= USB_PORT_STAT_HIGH_SPEED; +		else if (speed == HPRT0_SPD_LOW_SPEED) +			port_status |= USB_PORT_STAT_LOW_SPEED; + +		if (hprt0 & HPRT0_TSTCTL_MASK) +			port_status |= USB_PORT_STAT_TEST; +		/* USB_PORT_FEAT_INDICATOR unsupported always 0 */ + +		dev_vdbg(hsotg->dev, "port_status=%08x\n", port_status); +		*(__le32 *)buf = cpu_to_le32(port_status); +		break; + +	case SetHubFeature: +		dev_dbg(hsotg->dev, "SetHubFeature\n"); +		/* No HUB features supported */ +		break; + +	case SetPortFeature: +		dev_dbg(hsotg->dev, "SetPortFeature\n"); +		if (wvalue != USB_PORT_FEAT_TEST && (!windex || windex > 1)) +			goto error; + +		if (!hsotg->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; +		} + +		switch (wvalue) { +		case USB_PORT_FEAT_SUSPEND: +			dev_dbg(hsotg->dev, +				"SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); +			if (windex != hsotg->otg_port) +				goto error; +			dwc2_port_suspend(hsotg, windex); +			break; + +		case USB_PORT_FEAT_POWER: +			dev_dbg(hsotg->dev, +				"SetPortFeature - USB_PORT_FEAT_POWER\n"); +			hprt0 = dwc2_read_hprt0(hsotg); +			hprt0 |= HPRT0_PWR; +			writel(hprt0, hsotg->regs + HPRT0); +			break; + +		case USB_PORT_FEAT_RESET: +			hprt0 = dwc2_read_hprt0(hsotg); +			dev_dbg(hsotg->dev, +				"SetPortFeature - USB_PORT_FEAT_RESET\n"); +			pcgctl = readl(hsotg->regs + PCGCTL); +			pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK); +			writel(pcgctl, hsotg->regs + PCGCTL); +			/* ??? Original driver does this */ +			writel(0, hsotg->regs + PCGCTL); + +			hprt0 = dwc2_read_hprt0(hsotg); +			/* Clear suspend bit if resetting from suspend state */ +			hprt0 &= ~HPRT0_SUSP; + +			/* +			 * 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 (!dwc2_hcd_is_b_host(hsotg)) { +				hprt0 |= HPRT0_PWR | HPRT0_RST; +				dev_dbg(hsotg->dev, +					"In host mode, hprt0=%08x\n", hprt0); +				writel(hprt0, hsotg->regs + HPRT0); +			} + +			/* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */ +			usleep_range(50000, 70000); +			hprt0 &= ~HPRT0_RST; +			writel(hprt0, hsotg->regs + HPRT0); +			hsotg->lx_state = DWC2_L0; /* Now back to On state */ +			break; + +		case USB_PORT_FEAT_INDICATOR: +			dev_dbg(hsotg->dev, +				"SetPortFeature - USB_PORT_FEAT_INDICATOR\n"); +			/* Not supported */ +			break; + +		default: +			retval = -EINVAL; +			dev_err(hsotg->dev, +				"SetPortFeature %1xh unknown or unsupported\n", +				wvalue); +			break; +		} +		break; + +	default: +error: +		retval = -EINVAL; +		dev_dbg(hsotg->dev, +			"Unknown hub control request: %1xh wIndex: %1xh wValue: %1xh\n", +			typereq, windex, wvalue); +		break; +	} + +	return retval; +} + +static int dwc2_hcd_is_status_changed(struct dwc2_hsotg *hsotg, int port) +{ +	int retval; + +	if (port != 1) +		return -EINVAL; + +	retval = (hsotg->flags.b.port_connect_status_change || +		  hsotg->flags.b.port_reset_change || +		  hsotg->flags.b.port_enable_change || +		  hsotg->flags.b.port_suspend_change || +		  hsotg->flags.b.port_over_current_change); + +	if (retval) { +		dev_dbg(hsotg->dev, +			"DWC OTG HCD HUB STATUS DATA: Root port status changed\n"); +		dev_dbg(hsotg->dev, "  port_connect_status_change: %d\n", +			hsotg->flags.b.port_connect_status_change); +		dev_dbg(hsotg->dev, "  port_reset_change: %d\n", +			hsotg->flags.b.port_reset_change); +		dev_dbg(hsotg->dev, "  port_enable_change: %d\n", +			hsotg->flags.b.port_enable_change); +		dev_dbg(hsotg->dev, "  port_suspend_change: %d\n", +			hsotg->flags.b.port_suspend_change); +		dev_dbg(hsotg->dev, "  port_over_current_change: %d\n", +			hsotg->flags.b.port_over_current_change); +	} + +	return retval; +} + +int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg) +{ +	u32 hfnum = readl(hsotg->regs + HFNUM); + +#ifdef DWC2_DEBUG_SOF +	dev_vdbg(hsotg->dev, "DWC OTG HCD GET FRAME NUMBER %d\n", +		 (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT); +#endif +	return (hfnum & HFNUM_FRNUM_MASK) >> HFNUM_FRNUM_SHIFT; +} + +int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg) +{ +	return hsotg->op_state == OTG_STATE_B_HOST; +} + +static struct dwc2_hcd_urb *dwc2_hcd_urb_alloc(struct dwc2_hsotg *hsotg, +					       int iso_desc_count, +					       gfp_t mem_flags) +{ +	struct dwc2_hcd_urb *urb; +	u32 size = sizeof(*urb) + iso_desc_count * +		   sizeof(struct dwc2_hcd_iso_packet_desc); + +	urb = kzalloc(size, mem_flags); +	if (urb) +		urb->packet_count = iso_desc_count; +	return urb; +} + +static void dwc2_hcd_urb_set_pipeinfo(struct dwc2_hsotg *hsotg, +				      struct dwc2_hcd_urb *urb, u8 dev_addr, +				      u8 ep_num, u8 ep_type, u8 ep_dir, u16 mps) +{ +	if (dbg_perio() || +	    ep_type == USB_ENDPOINT_XFER_BULK || +	    ep_type == USB_ENDPOINT_XFER_CONTROL) +		dev_vdbg(hsotg->dev, +			 "addr=%d, ep_num=%d, ep_dir=%1x, ep_type=%1x, mps=%d\n", +			 dev_addr, ep_num, ep_dir, ep_type, mps); +	urb->pipe_info.dev_addr = dev_addr; +	urb->pipe_info.ep_num = ep_num; +	urb->pipe_info.pipe_type = ep_type; +	urb->pipe_info.pipe_dir = ep_dir; +	urb->pipe_info.mps = mps; +} + +/* + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg) +{ +#ifdef DEBUG +	struct dwc2_host_chan *chan; +	struct dwc2_hcd_urb *urb; +	struct dwc2_qtd *qtd; +	int num_channels; +	u32 np_tx_status; +	u32 p_tx_status; +	int i; + +	num_channels = hsotg->core_params->host_channels; +	dev_dbg(hsotg->dev, "\n"); +	dev_dbg(hsotg->dev, +		"************************************************************\n"); +	dev_dbg(hsotg->dev, "HCD State:\n"); +	dev_dbg(hsotg->dev, "  Num channels: %d\n", num_channels); + +	for (i = 0; i < num_channels; i++) { +		chan = hsotg->hc_ptr_array[i]; +		dev_dbg(hsotg->dev, "  Channel %d:\n", i); +		dev_dbg(hsotg->dev, +			"    dev_addr: %d, ep_num: %d, ep_is_in: %d\n", +			chan->dev_addr, chan->ep_num, chan->ep_is_in); +		dev_dbg(hsotg->dev, "    speed: %d\n", chan->speed); +		dev_dbg(hsotg->dev, "    ep_type: %d\n", chan->ep_type); +		dev_dbg(hsotg->dev, "    max_packet: %d\n", chan->max_packet); +		dev_dbg(hsotg->dev, "    data_pid_start: %d\n", +			chan->data_pid_start); +		dev_dbg(hsotg->dev, "    multi_count: %d\n", chan->multi_count); +		dev_dbg(hsotg->dev, "    xfer_started: %d\n", +			chan->xfer_started); +		dev_dbg(hsotg->dev, "    xfer_buf: %p\n", chan->xfer_buf); +		dev_dbg(hsotg->dev, "    xfer_dma: %08lx\n", +			(unsigned long)chan->xfer_dma); +		dev_dbg(hsotg->dev, "    xfer_len: %d\n", chan->xfer_len); +		dev_dbg(hsotg->dev, "    xfer_count: %d\n", chan->xfer_count); +		dev_dbg(hsotg->dev, "    halt_on_queue: %d\n", +			chan->halt_on_queue); +		dev_dbg(hsotg->dev, "    halt_pending: %d\n", +			chan->halt_pending); +		dev_dbg(hsotg->dev, "    halt_status: %d\n", chan->halt_status); +		dev_dbg(hsotg->dev, "    do_split: %d\n", chan->do_split); +		dev_dbg(hsotg->dev, "    complete_split: %d\n", +			chan->complete_split); +		dev_dbg(hsotg->dev, "    hub_addr: %d\n", chan->hub_addr); +		dev_dbg(hsotg->dev, "    hub_port: %d\n", chan->hub_port); +		dev_dbg(hsotg->dev, "    xact_pos: %d\n", chan->xact_pos); +		dev_dbg(hsotg->dev, "    requests: %d\n", chan->requests); +		dev_dbg(hsotg->dev, "    qh: %p\n", chan->qh); + +		if (chan->xfer_started) { +			u32 hfnum, hcchar, hctsiz, hcint, hcintmsk; + +			hfnum = readl(hsotg->regs + HFNUM); +			hcchar = readl(hsotg->regs + HCCHAR(i)); +			hctsiz = readl(hsotg->regs + HCTSIZ(i)); +			hcint = readl(hsotg->regs + HCINT(i)); +			hcintmsk = readl(hsotg->regs + HCINTMSK(i)); +			dev_dbg(hsotg->dev, "    hfnum: 0x%08x\n", hfnum); +			dev_dbg(hsotg->dev, "    hcchar: 0x%08x\n", hcchar); +			dev_dbg(hsotg->dev, "    hctsiz: 0x%08x\n", hctsiz); +			dev_dbg(hsotg->dev, "    hcint: 0x%08x\n", hcint); +			dev_dbg(hsotg->dev, "    hcintmsk: 0x%08x\n", hcintmsk); +		} + +		if (!(chan->xfer_started && chan->qh)) +			continue; + +		list_for_each_entry(qtd, &chan->qh->qtd_list, qtd_list_entry) { +			if (!qtd->in_process) +				break; +			urb = qtd->urb; +			dev_dbg(hsotg->dev, "    URB Info:\n"); +			dev_dbg(hsotg->dev, "      qtd: %p, urb: %p\n", +				qtd, urb); +			if (urb) { +				dev_dbg(hsotg->dev, +					"      Dev: %d, EP: %d %s\n", +					dwc2_hcd_get_dev_addr(&urb->pipe_info), +					dwc2_hcd_get_ep_num(&urb->pipe_info), +					dwc2_hcd_is_pipe_in(&urb->pipe_info) ? +					"IN" : "OUT"); +				dev_dbg(hsotg->dev, +					"      Max packet size: %d\n", +					dwc2_hcd_get_mps(&urb->pipe_info)); +				dev_dbg(hsotg->dev, +					"      transfer_buffer: %p\n", +					urb->buf); +				dev_dbg(hsotg->dev, +					"      transfer_dma: %08lx\n", +					(unsigned long)urb->dma); +				dev_dbg(hsotg->dev, +					"      transfer_buffer_length: %d\n", +					urb->length); +				dev_dbg(hsotg->dev, "      actual_length: %d\n", +					urb->actual_length); +			} +		} +	} + +	dev_dbg(hsotg->dev, "  non_periodic_channels: %d\n", +		hsotg->non_periodic_channels); +	dev_dbg(hsotg->dev, "  periodic_channels: %d\n", +		hsotg->periodic_channels); +	dev_dbg(hsotg->dev, "  periodic_usecs: %d\n", hsotg->periodic_usecs); +	np_tx_status = readl(hsotg->regs + GNPTXSTS); +	dev_dbg(hsotg->dev, "  NP Tx Req Queue Space Avail: %d\n", +		(np_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); +	dev_dbg(hsotg->dev, "  NP Tx FIFO Space Avail: %d\n", +		(np_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); +	p_tx_status = readl(hsotg->regs + HPTXSTS); +	dev_dbg(hsotg->dev, "  P Tx Req Queue Space Avail: %d\n", +		(p_tx_status & TXSTS_QSPCAVAIL_MASK) >> TXSTS_QSPCAVAIL_SHIFT); +	dev_dbg(hsotg->dev, "  P Tx FIFO Space Avail: %d\n", +		(p_tx_status & TXSTS_FSPCAVAIL_MASK) >> TXSTS_FSPCAVAIL_SHIFT); +	dwc2_hcd_dump_frrem(hsotg); +	dwc2_dump_global_registers(hsotg); +	dwc2_dump_host_registers(hsotg); +	dev_dbg(hsotg->dev, +		"************************************************************\n"); +	dev_dbg(hsotg->dev, "\n"); +#endif +} + +/* + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg) +{ +#ifdef DWC2_DUMP_FRREM +	dev_dbg(hsotg->dev, "Frame remaining at SOF:\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->frrem_samples, hsotg->frrem_accum, +		hsotg->frrem_samples > 0 ? +		hsotg->frrem_accum / hsotg->frrem_samples : 0); +	dev_dbg(hsotg->dev, "\n"); +	dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 7):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_7_samples, +		hsotg->hfnum_7_frrem_accum, +		hsotg->hfnum_7_samples > 0 ? +		hsotg->hfnum_7_frrem_accum / hsotg->hfnum_7_samples : 0); +	dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 0):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_0_samples, +		hsotg->hfnum_0_frrem_accum, +		hsotg->hfnum_0_samples > 0 ? +		hsotg->hfnum_0_frrem_accum / hsotg->hfnum_0_samples : 0); +	dev_dbg(hsotg->dev, "Frame remaining at start_transfer (uframe 1-6):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_other_samples, +		hsotg->hfnum_other_frrem_accum, +		hsotg->hfnum_other_samples > 0 ? +		hsotg->hfnum_other_frrem_accum / hsotg->hfnum_other_samples : +		0); +	dev_dbg(hsotg->dev, "\n"); +	dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 7):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_7_samples_a, hsotg->hfnum_7_frrem_accum_a, +		hsotg->hfnum_7_samples_a > 0 ? +		hsotg->hfnum_7_frrem_accum_a / hsotg->hfnum_7_samples_a : 0); +	dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 0):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_0_samples_a, hsotg->hfnum_0_frrem_accum_a, +		hsotg->hfnum_0_samples_a > 0 ? +		hsotg->hfnum_0_frrem_accum_a / hsotg->hfnum_0_samples_a : 0); +	dev_dbg(hsotg->dev, "Frame remaining at sample point A (uframe 1-6):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_other_samples_a, hsotg->hfnum_other_frrem_accum_a, +		hsotg->hfnum_other_samples_a > 0 ? +		hsotg->hfnum_other_frrem_accum_a / hsotg->hfnum_other_samples_a +		: 0); +	dev_dbg(hsotg->dev, "\n"); +	dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 7):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_7_samples_b, hsotg->hfnum_7_frrem_accum_b, +		hsotg->hfnum_7_samples_b > 0 ? +		hsotg->hfnum_7_frrem_accum_b / hsotg->hfnum_7_samples_b : 0); +	dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 0):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_0_samples_b, hsotg->hfnum_0_frrem_accum_b, +		(hsotg->hfnum_0_samples_b > 0) ? +		hsotg->hfnum_0_frrem_accum_b / hsotg->hfnum_0_samples_b : 0); +	dev_dbg(hsotg->dev, "Frame remaining at sample point B (uframe 1-6):\n"); +	dev_dbg(hsotg->dev, "  samples %u, accum %llu, avg %llu\n", +		hsotg->hfnum_other_samples_b, hsotg->hfnum_other_frrem_accum_b, +		(hsotg->hfnum_other_samples_b > 0) ? +		hsotg->hfnum_other_frrem_accum_b / hsotg->hfnum_other_samples_b +		: 0); +#endif +} + +struct wrapper_priv_data { +	struct dwc2_hsotg *hsotg; +}; + +/* Gets the dwc2_hsotg from a usb_hcd */ +static struct dwc2_hsotg *dwc2_hcd_to_hsotg(struct usb_hcd *hcd) +{ +	struct wrapper_priv_data *p; + +	p = (struct wrapper_priv_data *) &hcd->hcd_priv; +	return p->hsotg; +} + +static int _dwc2_hcd_start(struct usb_hcd *hcd); + +void dwc2_host_start(struct dwc2_hsotg *hsotg) +{ +	struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + +	hcd->self.is_b_host = dwc2_hcd_is_b_host(hsotg); +	_dwc2_hcd_start(hcd); +} + +void dwc2_host_disconnect(struct dwc2_hsotg *hsotg) +{ +	struct usb_hcd *hcd = dwc2_hsotg_to_hcd(hsotg); + +	hcd->self.is_b_host = 0; +} + +void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, int *hub_addr, +			int *hub_port) +{ +	struct urb *urb = context; + +	if (urb->dev->tt) +		*hub_addr = urb->dev->tt->hub->devnum; +	else +		*hub_addr = 0; +	*hub_port = urb->dev->ttport; +} + +int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context) +{ +	struct urb *urb = context; + +	return urb->dev->speed; +} + +static void dwc2_allocate_bus_bandwidth(struct usb_hcd *hcd, u16 bw, +					struct urb *urb) +{ +	struct usb_bus *bus = hcd_to_bus(hcd); + +	if (urb->interval) +		bus->bandwidth_allocated += bw / urb->interval; +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) +		bus->bandwidth_isoc_reqs++; +	else +		bus->bandwidth_int_reqs++; +} + +static void dwc2_free_bus_bandwidth(struct usb_hcd *hcd, u16 bw, +				    struct urb *urb) +{ +	struct usb_bus *bus = hcd_to_bus(hcd); + +	if (urb->interval) +		bus->bandwidth_allocated -= bw / urb->interval; +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) +		bus->bandwidth_isoc_reqs--; +	else +		bus->bandwidth_int_reqs--; +} + +/* + * Sets the final status of an URB and returns it to the upper layer. Any + * required cleanup of the URB is performed. + * + * Must be called with interrupt disabled and spinlock held + */ +void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, +			int status) +{ +	struct urb *urb; +	int i; + +	if (!qtd) { +		dev_dbg(hsotg->dev, "## %s: qtd is NULL ##\n", __func__); +		return; +	} + +	if (!qtd->urb) { +		dev_dbg(hsotg->dev, "## %s: qtd->urb is NULL ##\n", __func__); +		return; +	} + +	urb = qtd->urb->priv; +	if (!urb) { +		dev_dbg(hsotg->dev, "## %s: urb->priv is NULL ##\n", __func__); +		return; +	} + +	urb->actual_length = dwc2_hcd_urb_get_actual_length(qtd->urb); + +	if (dbg_urb(urb)) +		dev_vdbg(hsotg->dev, +			 "%s: urb %p device %d ep %d-%s status %d actual %d\n", +			 __func__, urb, usb_pipedevice(urb->pipe), +			 usb_pipeendpoint(urb->pipe), +			 usb_pipein(urb->pipe) ? "IN" : "OUT", status, +			 urb->actual_length); + +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS && dbg_perio()) { +		for (i = 0; i < urb->number_of_packets; i++) +			dev_vdbg(hsotg->dev, " ISO Desc %d status %d\n", +				 i, urb->iso_frame_desc[i].status); +	} + +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { +		urb->error_count = dwc2_hcd_urb_get_error_count(qtd->urb); +		for (i = 0; i < urb->number_of_packets; ++i) { +			urb->iso_frame_desc[i].actual_length = +				dwc2_hcd_urb_get_iso_desc_actual_length( +						qtd->urb, i); +			urb->iso_frame_desc[i].status = +				dwc2_hcd_urb_get_iso_desc_status(qtd->urb, i); +		} +	} + +	urb->status = status; +	if (!status) { +		if ((urb->transfer_flags & URB_SHORT_NOT_OK) && +		    urb->actual_length < urb->transfer_buffer_length) +			urb->status = -EREMOTEIO; +	} + +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS || +	    usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { +		struct usb_host_endpoint *ep = urb->ep; + +		if (ep) +			dwc2_free_bus_bandwidth(dwc2_hsotg_to_hcd(hsotg), +					dwc2_hcd_get_ep_bandwidth(hsotg, ep), +					urb); +	} + +	usb_hcd_unlink_urb_from_ep(dwc2_hsotg_to_hcd(hsotg), urb); +	urb->hcpriv = NULL; +	kfree(qtd->urb); +	qtd->urb = NULL; + +	spin_unlock(&hsotg->lock); +	usb_hcd_giveback_urb(dwc2_hsotg_to_hcd(hsotg), urb, status); +	spin_lock(&hsotg->lock); +} + +/* + * Work queue function for starting the HCD when A-Cable is connected + */ +static void dwc2_hcd_start_func(struct work_struct *work) +{ +	struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, +						start_work.work); + +	dev_dbg(hsotg->dev, "%s() %p\n", __func__, hsotg); +	dwc2_host_start(hsotg); +} + +/* + * Reset work queue function + */ +static void dwc2_hcd_reset_func(struct work_struct *work) +{ +	struct dwc2_hsotg *hsotg = container_of(work, struct dwc2_hsotg, +						reset_work.work); +	u32 hprt0; + +	dev_dbg(hsotg->dev, "USB RESET function called\n"); +	hprt0 = dwc2_read_hprt0(hsotg); +	hprt0 &= ~HPRT0_RST; +	writel(hprt0, hsotg->regs + HPRT0); +	hsotg->flags.b.port_reset_change = 1; +} + +/* + * ========================================================================= + *  Linux HC Driver Functions + * ========================================================================= + */ + +/* + * 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 _dwc2_hcd_start(struct usb_hcd *hcd) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	struct usb_bus *bus = hcd_to_bus(hcd); +	unsigned long flags; + +	dev_dbg(hsotg->dev, "DWC OTG HCD START\n"); + +	spin_lock_irqsave(&hsotg->lock, flags); + +	hcd->state = HC_STATE_RUNNING; + +	if (dwc2_is_device_mode(hsotg)) { +		spin_unlock_irqrestore(&hsotg->lock, flags); +		return 0;	/* why 0 ?? */ +	} + +	dwc2_hcd_reinit(hsotg); + +	/* Initialize and connect root hub if one is not already attached */ +	if (bus->root_hub) { +		dev_dbg(hsotg->dev, "DWC OTG HCD Has Root Hub\n"); +		/* Inform the HUB driver to resume */ +		usb_hcd_resume_root_hub(hcd); +	} + +	spin_unlock_irqrestore(&hsotg->lock, flags); +	return 0; +} + +/* + * Halts the DWC_otg host mode operations in a clean manner. USB transfers are + * stopped. + */ +static void _dwc2_hcd_stop(struct usb_hcd *hcd) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	unsigned long flags; + +	spin_lock_irqsave(&hsotg->lock, flags); +	dwc2_hcd_stop(hsotg); +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	usleep_range(1000, 3000); +} + +/* Returns the current frame number */ +static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + +	return dwc2_hcd_get_frame_number(hsotg); +} + +static void dwc2_dump_urb_info(struct usb_hcd *hcd, struct urb *urb, +			       char *fn_name) +{ +#ifdef VERBOSE_DEBUG +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	char *pipetype; +	char *speed; + +	dev_vdbg(hsotg->dev, "%s, urb %p\n", fn_name, urb); +	dev_vdbg(hsotg->dev, "  Device address: %d\n", +		 usb_pipedevice(urb->pipe)); +	dev_vdbg(hsotg->dev, "  Endpoint: %d, %s\n", +		 usb_pipeendpoint(urb->pipe), +		 usb_pipein(urb->pipe) ? "IN" : "OUT"); + +	switch (usb_pipetype(urb->pipe)) { +	case PIPE_CONTROL: +		pipetype = "CONTROL"; +		break; +	case PIPE_BULK: +		pipetype = "BULK"; +		break; +	case PIPE_INTERRUPT: +		pipetype = "INTERRUPT"; +		break; +	case PIPE_ISOCHRONOUS: +		pipetype = "ISOCHRONOUS"; +		break; +	default: +		pipetype = "UNKNOWN"; +		break; +	} + +	dev_vdbg(hsotg->dev, "  Endpoint type: %s %s (%s)\n", pipetype, +		 usb_urb_dir_in(urb) ? "IN" : "OUT", usb_pipein(urb->pipe) ? +		 "IN" : "OUT"); + +	switch (urb->dev->speed) { +	case USB_SPEED_HIGH: +		speed = "HIGH"; +		break; +	case USB_SPEED_FULL: +		speed = "FULL"; +		break; +	case USB_SPEED_LOW: +		speed = "LOW"; +		break; +	default: +		speed = "UNKNOWN"; +		break; +	} + +	dev_vdbg(hsotg->dev, "  Speed: %s\n", speed); +	dev_vdbg(hsotg->dev, "  Max packet size: %d\n", +		 usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe))); +	dev_vdbg(hsotg->dev, "  Data buffer length: %d\n", +		 urb->transfer_buffer_length); +	dev_vdbg(hsotg->dev, "  Transfer buffer: %p, Transfer DMA: %08lx\n", +		 urb->transfer_buffer, (unsigned long)urb->transfer_dma); +	dev_vdbg(hsotg->dev, "  Setup buffer: %p, Setup DMA: %08lx\n", +		 urb->setup_packet, (unsigned long)urb->setup_dma); +	dev_vdbg(hsotg->dev, "  Interval: %d\n", urb->interval); + +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) { +		int i; + +		for (i = 0; i < urb->number_of_packets; i++) { +			dev_vdbg(hsotg->dev, "  ISO Desc %d:\n", i); +			dev_vdbg(hsotg->dev, "    offset: %d, length %d\n", +				 urb->iso_frame_desc[i].offset, +				 urb->iso_frame_desc[i].length); +		} +	} +#endif +} + +/* + * 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 _dwc2_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, +				 gfp_t mem_flags) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	struct usb_host_endpoint *ep = urb->ep; +	struct dwc2_hcd_urb *dwc2_urb; +	int i; +	int retval; +	int alloc_bandwidth = 0; +	u8 ep_type = 0; +	u32 tflags = 0; +	void *buf; +	unsigned long flags; + +	if (dbg_urb(urb)) { +		dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n"); +		dwc2_dump_urb_info(hcd, urb, "urb_enqueue"); +	} + +	if (ep == NULL) +		return -EINVAL; + +	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS || +	    usb_pipetype(urb->pipe) == PIPE_INTERRUPT) { +		spin_lock_irqsave(&hsotg->lock, flags); +		if (!dwc2_hcd_is_bandwidth_allocated(hsotg, ep)) +			alloc_bandwidth = 1; +		spin_unlock_irqrestore(&hsotg->lock, flags); +	} + +	switch (usb_pipetype(urb->pipe)) { +	case PIPE_CONTROL: +		ep_type = USB_ENDPOINT_XFER_CONTROL; +		break; +	case PIPE_ISOCHRONOUS: +		ep_type = USB_ENDPOINT_XFER_ISOC; +		break; +	case PIPE_BULK: +		ep_type = USB_ENDPOINT_XFER_BULK; +		break; +	case PIPE_INTERRUPT: +		ep_type = USB_ENDPOINT_XFER_INT; +		break; +	default: +		dev_warn(hsotg->dev, "Wrong ep type\n"); +	} + +	dwc2_urb = dwc2_hcd_urb_alloc(hsotg, urb->number_of_packets, +				      mem_flags); +	if (!dwc2_urb) +		return -ENOMEM; + +	dwc2_hcd_urb_set_pipeinfo(hsotg, dwc2_urb, usb_pipedevice(urb->pipe), +				  usb_pipeendpoint(urb->pipe), ep_type, +				  usb_pipein(urb->pipe), +				  usb_maxpacket(urb->dev, urb->pipe, +						!(usb_pipein(urb->pipe)))); + +	buf = urb->transfer_buffer; + +	if (hcd->self.uses_dma) { +		if (!buf && (urb->transfer_dma & 3)) { +			dev_err(hsotg->dev, +				"%s: unaligned transfer with no transfer_buffer", +				__func__); +			retval = -EINVAL; +			goto fail1; +		} +	} + +	if (!(urb->transfer_flags & URB_NO_INTERRUPT)) +		tflags |= URB_GIVEBACK_ASAP; +	if (urb->transfer_flags & URB_ZERO_PACKET) +		tflags |= URB_SEND_ZERO_PACKET; + +	dwc2_urb->priv = urb; +	dwc2_urb->buf = buf; +	dwc2_urb->dma = urb->transfer_dma; +	dwc2_urb->length = urb->transfer_buffer_length; +	dwc2_urb->setup_packet = urb->setup_packet; +	dwc2_urb->setup_dma = urb->setup_dma; +	dwc2_urb->flags = tflags; +	dwc2_urb->interval = urb->interval; +	dwc2_urb->status = -EINPROGRESS; + +	for (i = 0; i < urb->number_of_packets; ++i) +		dwc2_hcd_urb_set_iso_desc_params(dwc2_urb, i, +						 urb->iso_frame_desc[i].offset, +						 urb->iso_frame_desc[i].length); + +	urb->hcpriv = dwc2_urb; + +	spin_lock_irqsave(&hsotg->lock, flags); +	retval = usb_hcd_link_urb_to_ep(hcd, urb); +	spin_unlock_irqrestore(&hsotg->lock, flags); +	if (retval) +		goto fail1; + +	retval = dwc2_hcd_urb_enqueue(hsotg, dwc2_urb, &ep->hcpriv, mem_flags); +	if (retval) +		goto fail2; + +	if (alloc_bandwidth) { +		spin_lock_irqsave(&hsotg->lock, flags); +		dwc2_allocate_bus_bandwidth(hcd, +				dwc2_hcd_get_ep_bandwidth(hsotg, ep), +				urb); +		spin_unlock_irqrestore(&hsotg->lock, flags); +	} + +	return 0; + +fail2: +	spin_lock_irqsave(&hsotg->lock, flags); +	dwc2_urb->priv = NULL; +	usb_hcd_unlink_urb_from_ep(hcd, urb); +	spin_unlock_irqrestore(&hsotg->lock, flags); +fail1: +	urb->hcpriv = NULL; +	kfree(dwc2_urb); + +	return retval; +} + +/* + * Aborts/cancels a USB transfer request. Always returns 0 to indicate success. + */ +static int _dwc2_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, +				 int status) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	int rc; +	unsigned long flags; + +	dev_dbg(hsotg->dev, "DWC OTG HCD URB Dequeue\n"); +	dwc2_dump_urb_info(hcd, urb, "urb_dequeue"); + +	spin_lock_irqsave(&hsotg->lock, flags); + +	rc = usb_hcd_check_unlink_urb(hcd, urb, status); +	if (rc) +		goto out; + +	if (!urb->hcpriv) { +		dev_dbg(hsotg->dev, "## urb->hcpriv is NULL ##\n"); +		goto out; +	} + +	rc = dwc2_hcd_urb_dequeue(hsotg, urb->hcpriv); + +	usb_hcd_unlink_urb_from_ep(hcd, urb); + +	kfree(urb->hcpriv); +	urb->hcpriv = NULL; + +	/* Higher layer software sets URB status */ +	spin_unlock(&hsotg->lock); +	usb_hcd_giveback_urb(hcd, urb, status); +	spin_lock(&hsotg->lock); + +	dev_dbg(hsotg->dev, "Called usb_hcd_giveback_urb()\n"); +	dev_dbg(hsotg->dev, "  urb->status = %d\n", urb->status); +out: +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	return rc; +} + +/* + * 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 _dwc2_hcd_endpoint_disable(struct usb_hcd *hcd, +				       struct usb_host_endpoint *ep) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + +	dev_dbg(hsotg->dev, +		"DWC OTG HCD EP DISABLE: bEndpointAddress=0x%02x, ep->hcpriv=%p\n", +		ep->desc.bEndpointAddress, ep->hcpriv); +	dwc2_hcd_endpoint_disable(hsotg, ep, 250); +} + +/* + * Resets endpoint specific parameter values, in current version used to reset + * the data toggle (as a WA). This function can be called from usb_clear_halt + * routine. + */ +static void _dwc2_hcd_endpoint_reset(struct usb_hcd *hcd, +				     struct usb_host_endpoint *ep) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	unsigned long flags; + +	dev_dbg(hsotg->dev, +		"DWC OTG HCD EP RESET: bEndpointAddress=0x%02x\n", +		ep->desc.bEndpointAddress); + +	spin_lock_irqsave(&hsotg->lock, flags); +	dwc2_hcd_endpoint_reset(hsotg, ep); +	spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * 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 _dwc2_hcd_irq(struct usb_hcd *hcd) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + +	return dwc2_handle_hcd_intr(hsotg); +} + +/* + * 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 _dwc2_hcd_hub_status_data(struct usb_hcd *hcd, char *buf) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); + +	buf[0] = dwc2_hcd_is_status_changed(hsotg, 1) << 1; +	return buf[0] != 0; +} + +/* Handles hub class-specific requests */ +static int _dwc2_hcd_hub_control(struct usb_hcd *hcd, u16 typereq, u16 wvalue, +				 u16 windex, char *buf, u16 wlength) +{ +	int retval = dwc2_hcd_hub_control(dwc2_hcd_to_hsotg(hcd), typereq, +					  wvalue, windex, buf, wlength); +	return retval; +} + +/* Handles hub TT buffer clear completions */ +static void _dwc2_hcd_clear_tt_buffer_complete(struct usb_hcd *hcd, +					       struct usb_host_endpoint *ep) +{ +	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); +	struct dwc2_qh *qh; +	unsigned long flags; + +	qh = ep->hcpriv; +	if (!qh) +		return; + +	spin_lock_irqsave(&hsotg->lock, flags); +	qh->tt_buffer_dirty = 0; + +	if (hsotg->flags.b.port_connect_status) +		dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_ALL); + +	spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static struct hc_driver dwc2_hc_driver = { +	.description = "dwc2_hsotg", +	.product_desc = "DWC OTG Controller", +	.hcd_priv_size = sizeof(struct wrapper_priv_data), + +	.irq = _dwc2_hcd_irq, +	.flags = HCD_MEMORY | HCD_USB2, + +	.start = _dwc2_hcd_start, +	.stop = _dwc2_hcd_stop, +	.urb_enqueue = _dwc2_hcd_urb_enqueue, +	.urb_dequeue = _dwc2_hcd_urb_dequeue, +	.endpoint_disable = _dwc2_hcd_endpoint_disable, +	.endpoint_reset = _dwc2_hcd_endpoint_reset, +	.get_frame_number = _dwc2_hcd_get_frame_number, + +	.hub_status_data = _dwc2_hcd_hub_status_data, +	.hub_control = _dwc2_hcd_hub_control, +	.clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete, +}; + +/* + * Frees secondary storage associated with the dwc2_hsotg structure contained + * in the struct usb_hcd field + */ +static void dwc2_hcd_free(struct dwc2_hsotg *hsotg) +{ +	u32 ahbcfg; +	u32 dctl; +	int i; + +	dev_dbg(hsotg->dev, "DWC OTG HCD FREE\n"); + +	/* Free memory for QH/QTD lists */ +	dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_inactive); +	dwc2_qh_list_free(hsotg, &hsotg->non_periodic_sched_active); +	dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_inactive); +	dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_ready); +	dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_assigned); +	dwc2_qh_list_free(hsotg, &hsotg->periodic_sched_queued); + +	/* Free memory for the host channels */ +	for (i = 0; i < MAX_EPS_CHANNELS; i++) { +		struct dwc2_host_chan *chan = hsotg->hc_ptr_array[i]; + +		if (chan != NULL) { +			dev_dbg(hsotg->dev, "HCD Free channel #%i, chan=%p\n", +				i, chan); +			hsotg->hc_ptr_array[i] = NULL; +			kfree(chan); +		} +	} + +	if (hsotg->core_params->dma_enable > 0) { +		if (hsotg->status_buf) { +			dma_free_coherent(hsotg->dev, DWC2_HCD_STATUS_BUF_SIZE, +					  hsotg->status_buf, +					  hsotg->status_buf_dma); +			hsotg->status_buf = NULL; +		} +	} else { +		kfree(hsotg->status_buf); +		hsotg->status_buf = NULL; +	} + +	ahbcfg = readl(hsotg->regs + GAHBCFG); + +	/* Disable all interrupts */ +	ahbcfg &= ~GAHBCFG_GLBL_INTR_EN; +	writel(ahbcfg, hsotg->regs + GAHBCFG); +	writel(0, hsotg->regs + GINTMSK); + +	if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_3_00a) { +		dctl = readl(hsotg->regs + DCTL); +		dctl |= DCTL_SFTDISCON; +		writel(dctl, hsotg->regs + DCTL); +	} + +	if (hsotg->wq_otg) { +		if (!cancel_work_sync(&hsotg->wf_otg)) +			flush_workqueue(hsotg->wq_otg); +		destroy_workqueue(hsotg->wq_otg); +	} + +	kfree(hsotg->core_params); +	hsotg->core_params = NULL; +	del_timer(&hsotg->wkp_timer); +} + +static void dwc2_hcd_release(struct dwc2_hsotg *hsotg) +{ +	/* Turn off all host-specific interrupts */ +	dwc2_disable_host_interrupts(hsotg); + +	dwc2_hcd_free(hsotg); +} + +/* + * Sets all parameters to the given value. + * + * Assumes that the dwc2_core_params struct contains only integers. + */ +void dwc2_set_all_params(struct dwc2_core_params *params, int value) +{ +	int *p = (int *)params; +	size_t size = sizeof(*params) / sizeof(*p); +	int i; + +	for (i = 0; i < size; i++) +		p[i] = value; +} +EXPORT_SYMBOL_GPL(dwc2_set_all_params); + +/* + * Initializes the HCD. This function allocates memory for and initializes the + * static parts of the usb_hcd and dwc2_hsotg 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 dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq, +		  const struct dwc2_core_params *params) +{ +	struct usb_hcd *hcd; +	struct dwc2_host_chan *channel; +	u32 hcfg; +	int i, num_channels; +	int retval; + +	dev_dbg(hsotg->dev, "DWC OTG HCD INIT\n"); + +	/* Detect config values from hardware */ +	retval = dwc2_get_hwparams(hsotg); + +	if (retval) +		return retval; + +	retval = -ENOMEM; + +	hcfg = readl(hsotg->regs + HCFG); +	dev_dbg(hsotg->dev, "hcfg=%08x\n", hcfg); + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS +	hsotg->frame_num_array = kzalloc(sizeof(*hsotg->frame_num_array) * +					 FRAME_NUM_ARRAY_SIZE, GFP_KERNEL); +	if (!hsotg->frame_num_array) +		goto error1; +	hsotg->last_frame_num_array = kzalloc( +			sizeof(*hsotg->last_frame_num_array) * +			FRAME_NUM_ARRAY_SIZE, GFP_KERNEL); +	if (!hsotg->last_frame_num_array) +		goto error1; +	hsotg->last_frame_num = HFNUM_MAX_FRNUM; +#endif + +	hsotg->core_params = kzalloc(sizeof(*hsotg->core_params), GFP_KERNEL); +	if (!hsotg->core_params) +		goto error1; + +	dwc2_set_all_params(hsotg->core_params, -1); + +	/* Validate parameter values */ +	dwc2_set_parameters(hsotg, params); + +	/* Check if the bus driver or platform code has setup a dma_mask */ +	if (hsotg->core_params->dma_enable > 0 && +	    hsotg->dev->dma_mask == NULL) { +		dev_warn(hsotg->dev, +			 "dma_mask not set, disabling DMA\n"); +		hsotg->core_params->dma_enable = 0; +		hsotg->core_params->dma_desc_enable = 0; +	} + +	/* Set device flags indicating whether the HCD supports DMA */ +	if (hsotg->core_params->dma_enable > 0) { +		if (dma_set_mask(hsotg->dev, DMA_BIT_MASK(32)) < 0) +			dev_warn(hsotg->dev, "can't set DMA mask\n"); +		if (dma_set_coherent_mask(hsotg->dev, DMA_BIT_MASK(32)) < 0) +			dev_warn(hsotg->dev, "can't set coherent DMA mask\n"); +	} + +	hcd = usb_create_hcd(&dwc2_hc_driver, hsotg->dev, dev_name(hsotg->dev)); +	if (!hcd) +		goto error1; + +	if (hsotg->core_params->dma_enable <= 0) +		hcd->self.uses_dma = 0; + +	hcd->has_tt = 1; + +	spin_lock_init(&hsotg->lock); +	((struct wrapper_priv_data *) &hcd->hcd_priv)->hsotg = hsotg; +	hsotg->priv = hcd; + +	/* +	 * Disable the global interrupt until all the interrupt handlers are +	 * installed +	 */ +	dwc2_disable_global_interrupts(hsotg); + +	/* Initialize the DWC_otg core, and select the Phy type */ +	retval = dwc2_core_init(hsotg, true, irq); +	if (retval) +		goto error2; + +	/* Create new workqueue and init work */ +	retval = -ENOMEM; +	hsotg->wq_otg = create_singlethread_workqueue("dwc2"); +	if (!hsotg->wq_otg) { +		dev_err(hsotg->dev, "Failed to create workqueue\n"); +		goto error2; +	} +	INIT_WORK(&hsotg->wf_otg, dwc2_conn_id_status_change); + +	setup_timer(&hsotg->wkp_timer, dwc2_wakeup_detected, +		    (unsigned long)hsotg); + +	/* Initialize the non-periodic schedule */ +	INIT_LIST_HEAD(&hsotg->non_periodic_sched_inactive); +	INIT_LIST_HEAD(&hsotg->non_periodic_sched_active); + +	/* Initialize the periodic schedule */ +	INIT_LIST_HEAD(&hsotg->periodic_sched_inactive); +	INIT_LIST_HEAD(&hsotg->periodic_sched_ready); +	INIT_LIST_HEAD(&hsotg->periodic_sched_assigned); +	INIT_LIST_HEAD(&hsotg->periodic_sched_queued); + +	/* +	 * Create a host channel descriptor for each host channel implemented +	 * in the controller. Initialize the channel descriptor array. +	 */ +	INIT_LIST_HEAD(&hsotg->free_hc_list); +	num_channels = hsotg->core_params->host_channels; +	memset(&hsotg->hc_ptr_array[0], 0, sizeof(hsotg->hc_ptr_array)); + +	for (i = 0; i < num_channels; i++) { +		channel = kzalloc(sizeof(*channel), GFP_KERNEL); +		if (channel == NULL) +			goto error3; +		channel->hc_num = i; +		hsotg->hc_ptr_array[i] = channel; +	} + +	if (hsotg->core_params->uframe_sched > 0) +		dwc2_hcd_init_usecs(hsotg); + +	/* Initialize hsotg start work */ +	INIT_DELAYED_WORK(&hsotg->start_work, dwc2_hcd_start_func); + +	/* Initialize port reset work */ +	INIT_DELAYED_WORK(&hsotg->reset_work, dwc2_hcd_reset_func); + +	/* +	 * 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 (hsotg->core_params->dma_enable > 0) +		hsotg->status_buf = dma_alloc_coherent(hsotg->dev, +					DWC2_HCD_STATUS_BUF_SIZE, +					&hsotg->status_buf_dma, GFP_KERNEL); +	else +		hsotg->status_buf = kzalloc(DWC2_HCD_STATUS_BUF_SIZE, +					  GFP_KERNEL); + +	if (!hsotg->status_buf) +		goto error3; + +	hsotg->otg_port = 1; +	hsotg->frame_list = NULL; +	hsotg->frame_list_dma = 0; +	hsotg->periodic_qh_count = 0; + +	/* Initiate lx_state to L3 disconnected state */ +	hsotg->lx_state = DWC2_L3; + +	hcd->self.otg_port = hsotg->otg_port; + +	/* Don't support SG list at this point */ +	hcd->self.sg_tablesize = 0; + +	/* +	 * 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 hcd_start method. +	 */ +	retval = usb_add_hcd(hcd, irq, IRQF_SHARED); +	if (retval < 0) +		goto error3; + +	device_wakeup_enable(hcd->self.controller); + +	dwc2_hcd_dump_state(hsotg); + +	dwc2_enable_global_interrupts(hsotg); + +	return 0; + +error3: +	dwc2_hcd_release(hsotg); +error2: +	usb_put_hcd(hcd); +error1: +	kfree(hsotg->core_params); + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS +	kfree(hsotg->last_frame_num_array); +	kfree(hsotg->frame_num_array); +#endif + +	dev_err(hsotg->dev, "%s() FAILED, returning %d\n", __func__, retval); +	return retval; +} +EXPORT_SYMBOL_GPL(dwc2_hcd_init); + +/* + * Removes the HCD. + * Frees memory and resources associated with the HCD and deregisters the bus. + */ +void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) +{ +	struct usb_hcd *hcd; + +	dev_dbg(hsotg->dev, "DWC OTG HCD REMOVE\n"); + +	hcd = dwc2_hsotg_to_hcd(hsotg); +	dev_dbg(hsotg->dev, "hsotg->hcd = %p\n", hcd); + +	if (!hcd) { +		dev_dbg(hsotg->dev, "%s: dwc2_hsotg_to_hcd(hsotg) NULL!\n", +			__func__); +		return; +	} + +	usb_remove_hcd(hcd); +	hsotg->priv = NULL; +	dwc2_hcd_release(hsotg); +	usb_put_hcd(hcd); + +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS +	kfree(hsotg->last_frame_num_array); +	kfree(hsotg->frame_num_array); +#endif +} +EXPORT_SYMBOL_GPL(dwc2_hcd_remove); diff --git a/drivers/usb/dwc2/hcd.h b/drivers/usb/dwc2/hcd.h new file mode 100644 index 00000000000..fdc6d489084 --- /dev/null +++ b/drivers/usb/dwc2/hcd.h @@ -0,0 +1,769 @@ +/* + * hcd.h - DesignWare HS OTG Controller host-mode declarations + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef __DWC2_HCD_H__ +#define __DWC2_HCD_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. + */ + +struct dwc2_qh; + +/** + * struct dwc2_host_chan - Software host channel descriptor + * + * @hc_num:             Host channel number, used for register address lookup + * @dev_addr:           Address of the device + * @ep_num:             Endpoint of the device + * @ep_is_in:           Endpoint direction + * @speed:              Device speed. One of the following values: + *                       - USB_SPEED_LOW + *                       - USB_SPEED_FULL + *                       - USB_SPEED_HIGH + * @ep_type:            Endpoint type. One of the following values: + *                       - USB_ENDPOINT_XFER_CONTROL: 0 + *                       - USB_ENDPOINT_XFER_ISOC:    1 + *                       - USB_ENDPOINT_XFER_BULK:    2 + *                       - USB_ENDPOINT_XFER_INTR:    3 + * @max_packet:         Max packet size in bytes + * @data_pid_start:     PID for initial transaction. + *                       0: DATA0 + *                       1: DATA2 + *                       2: DATA1 + *                       3: MDATA (non-Control EP), + *                          SETUP (Control EP) + * @multi_count:        Number of additional periodic transactions per + *                      (micro)frame + * @xfer_buf:           Pointer to current transfer buffer position + * @xfer_dma:           DMA address of xfer_buf + * @align_buf:          In Buffer DMA mode this will be used if xfer_buf is not + *                      DWORD aligned + * @xfer_len:           Total number of bytes to transfer + * @xfer_count:         Number of bytes transferred so far + * @start_pkt_count:    Packet count at start of transfer + * @xfer_started:       True if the transfer has been started + * @ping:               True if a PING request should be issued on this channel + * @error_state:        True if the error count for this transaction is non-zero + * @halt_on_queue:      True if 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. + * @halt_pending:       True if the host channel has been halted, but the core + *                      is not finished flushing queued requests + * @do_split:           Enable split for the channel + * @complete_split:     Enable complete split + * @hub_addr:           Address of high speed hub for the split + * @hub_port:           Port of the low/full speed device for the split + * @xact_pos:           Split transaction position. One of the following values: + *                       - DWC2_HCSPLT_XACTPOS_MID + *                       - DWC2_HCSPLT_XACTPOS_BEGIN + *                       - DWC2_HCSPLT_XACTPOS_END + *                       - DWC2_HCSPLT_XACTPOS_ALL + * @requests:           Number of requests issued for this channel since it was + *                      assigned to the current transfer (not counting PINGs) + * @schinfo:            Scheduling micro-frame bitmap + * @ntd:                Number of transfer descriptors for the transfer + * @halt_status:        Reason for halting the host channel + * @hcint               Contents of the HCINT register when the interrupt came + * @qh:                 QH for the transfer being processed by this channel + * @hc_list_entry:      For linking to list of host channels + * @desc_list_addr:     Current QH's descriptor list DMA address + * + * 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 dwc2_host_chan { +	u8 hc_num; + +	unsigned dev_addr:7; +	unsigned ep_num:4; +	unsigned ep_is_in:1; +	unsigned speed:4; +	unsigned ep_type:2; +	unsigned max_packet:11; +	unsigned data_pid_start:2; +#define DWC2_HC_PID_DATA0	TSIZ_SC_MC_PID_DATA0 +#define DWC2_HC_PID_DATA2	TSIZ_SC_MC_PID_DATA2 +#define DWC2_HC_PID_DATA1	TSIZ_SC_MC_PID_DATA1 +#define DWC2_HC_PID_MDATA	TSIZ_SC_MC_PID_MDATA +#define DWC2_HC_PID_SETUP	TSIZ_SC_MC_PID_SETUP + +	unsigned multi_count:2; + +	u8 *xfer_buf; +	dma_addr_t xfer_dma; +	dma_addr_t align_buf; +	u32 xfer_len; +	u32 xfer_count; +	u16 start_pkt_count; +	u8 xfer_started; +	u8 do_ping; +	u8 error_state; +	u8 halt_on_queue; +	u8 halt_pending; +	u8 do_split; +	u8 complete_split; +	u8 hub_addr; +	u8 hub_port; +	u8 xact_pos; +#define DWC2_HCSPLT_XACTPOS_MID	HCSPLT_XACTPOS_MID +#define DWC2_HCSPLT_XACTPOS_END	HCSPLT_XACTPOS_END +#define DWC2_HCSPLT_XACTPOS_BEGIN HCSPLT_XACTPOS_BEGIN +#define DWC2_HCSPLT_XACTPOS_ALL	HCSPLT_XACTPOS_ALL + +	u8 requests; +	u8 schinfo; +	u16 ntd; +	enum dwc2_halt_status halt_status; +	u32 hcint; +	struct dwc2_qh *qh; +	struct list_head hc_list_entry; +	dma_addr_t desc_list_addr; +}; + +struct dwc2_hcd_pipe_info { +	u8 dev_addr; +	u8 ep_num; +	u8 pipe_type; +	u8 pipe_dir; +	u16 mps; +}; + +struct dwc2_hcd_iso_packet_desc { +	u32 offset; +	u32 length; +	u32 actual_length; +	u32 status; +}; + +struct dwc2_qtd; + +struct dwc2_hcd_urb { +	void *priv; +	struct dwc2_qtd *qtd; +	void *buf; +	dma_addr_t dma; +	void *setup_packet; +	dma_addr_t setup_dma; +	u32 length; +	u32 actual_length; +	u32 status; +	u32 error_count; +	u32 packet_count; +	u32 flags; +	u16 interval; +	struct dwc2_hcd_pipe_info pipe_info; +	struct dwc2_hcd_iso_packet_desc iso_descs[0]; +}; + +/* Phases for control transfers */ +enum dwc2_control_phase { +	DWC2_CONTROL_SETUP, +	DWC2_CONTROL_DATA, +	DWC2_CONTROL_STATUS, +}; + +/* Transaction types */ +enum dwc2_transaction_type { +	DWC2_TRANSACTION_NONE, +	DWC2_TRANSACTION_PERIODIC, +	DWC2_TRANSACTION_NON_PERIODIC, +	DWC2_TRANSACTION_ALL, +}; + +/** + * struct dwc2_qh - Software queue head structure + * + * @ep_type:            Endpoint type. One of the following values: + *                       - USB_ENDPOINT_XFER_CONTROL + *                       - USB_ENDPOINT_XFER_BULK + *                       - USB_ENDPOINT_XFER_INT + *                       - USB_ENDPOINT_XFER_ISOC + * @ep_is_in:           Endpoint direction + * @maxp:               Value from wMaxPacketSize field of Endpoint Descriptor + * @dev_speed:          Device speed. One of the following values: + *                       - USB_SPEED_LOW + *                       - USB_SPEED_FULL + *                       - USB_SPEED_HIGH + * @data_toggle:        Determines the PID of the next data packet for + *                      non-controltransfers. Ignored for control transfers. + *                      One of the following values: + *                       - DWC2_HC_PID_DATA0 + *                       - DWC2_HC_PID_DATA1 + * @ping_state:         Ping state + * @do_split:           Full/low speed endpoint on high-speed hub requires split + * @td_first:           Index of first activated isochronous transfer descriptor + * @td_last:            Index of last activated isochronous transfer descriptor + * @usecs:              Bandwidth in microseconds per (micro)frame + * @interval:           Interval between transfers in (micro)frames + * @sched_frame:        (Micro)frame to initialize a periodic transfer. + *                      The transfer executes in the following (micro)frame. + * @frame_usecs:        Internal variable used by the microframe scheduler + * @start_split_frame:  (Micro)frame at which last start split was initialized + * @ntd:                Actual number of transfer descriptors in a list + * @dw_align_buf:       Used instead of original buffer if its physical address + *                      is not dword-aligned + * @dw_align_buf_dma:   DMA address for align_buf + * @qtd_list:           List of QTDs for this QH + * @channel:            Host channel currently processing transfers for this QH + * @qh_list_entry:      Entry for QH in either the periodic or non-periodic + *                      schedule + * @desc_list:          List of transfer descriptors + * @desc_list_dma:      Physical address of desc_list + * @n_bytes:            Xfer Bytes array. Each element corresponds to a transfer + *                      descriptor and indicates original XferSize value for the + *                      descriptor + * @tt_buffer_dirty     True if clear_tt_buffer_complete is pending + * + * 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 dwc2_qh { +	u8 ep_type; +	u8 ep_is_in; +	u16 maxp; +	u8 dev_speed; +	u8 data_toggle; +	u8 ping_state; +	u8 do_split; +	u8 td_first; +	u8 td_last; +	u16 usecs; +	u16 interval; +	u16 sched_frame; +	u16 frame_usecs[8]; +	u16 start_split_frame; +	u16 ntd; +	u8 *dw_align_buf; +	dma_addr_t dw_align_buf_dma; +	struct list_head qtd_list; +	struct dwc2_host_chan *channel; +	struct list_head qh_list_entry; +	struct dwc2_hcd_dma_desc *desc_list; +	dma_addr_t desc_list_dma; +	u32 *n_bytes; +	unsigned tt_buffer_dirty:1; +}; + +/** + * struct dwc2_qtd - Software queue transfer descriptor (QTD) + * + * @control_phase:      Current phase for control transfers (Setup, Data, or + *                      Status) + * @in_process:         Indicates if this QTD is currently processed by HW + * @data_toggle:        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: + *                       - DWC2_HC_PID_DATA0 + *                       - DWC2_HC_PID_DATA1 + * @complete_split:     Keeps track of the current split type for FS/LS + *                      endpoints on a HS Hub + * @isoc_split_pos:     Position of the ISOC split in full/low speed + * @isoc_frame_index:   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. + * @isoc_split_offset:  Position of the ISOC split in the buffer for the + *                      current frame + * @ssplit_out_xfer_count: How many bytes transferred during SSPLIT OUT + * @error_count:        Holds the number of bus errors that have occurred for + *                      a transaction within this transfer + * @n_desc:             Number of DMA descriptors for this QTD + * @isoc_frame_index_last: Last activated frame (packet) index, used in + *                      descriptor DMA mode only + * @urb:                URB for this transfer + * @qh:                 Queue head for this QTD + * @qtd_list_entry:     For linking to the QH's list of QTDs + * + * 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 dwc2_qtd { +	enum dwc2_control_phase control_phase; +	u8 in_process; +	u8 data_toggle; +	u8 complete_split; +	u8 isoc_split_pos; +	u16 isoc_frame_index; +	u16 isoc_split_offset; +	u32 ssplit_out_xfer_count; +	u8 error_count; +	u8 n_desc; +	u16 isoc_frame_index_last; +	struct dwc2_hcd_urb *urb; +	struct dwc2_qh *qh; +	struct list_head qtd_list_entry; +}; + +#ifdef DEBUG +struct hc_xfer_info { +	struct dwc2_hsotg *hsotg; +	struct dwc2_host_chan *chan; +}; +#endif + +/* Gets the struct usb_hcd that contains a struct dwc2_hsotg */ +static inline struct usb_hcd *dwc2_hsotg_to_hcd(struct dwc2_hsotg *hsotg) +{ +	return (struct usb_hcd *)hsotg->priv; +} + +/* + * Inline used to disable one channel interrupt. Channel interrupts are + * disabled when the channel is halted or released by the interrupt handler. + * There is no need to handle further interrupts of that type until the + * channel is re-assigned. In fact, subsequent handling may cause crashes + * because the channel structures are cleaned up when the channel is released. + */ +static inline void disable_hc_int(struct dwc2_hsotg *hsotg, int chnum, u32 intr) +{ +	u32 mask = readl(hsotg->regs + HCINTMSK(chnum)); + +	mask &= ~intr; +	writel(mask, hsotg->regs + HCINTMSK(chnum)); +} + +/* + * Returns the mode of operation, host or device + */ +static inline int dwc2_is_host_mode(struct dwc2_hsotg *hsotg) +{ +	return (readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) != 0; +} +static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg) +{ +	return (readl(hsotg->regs + GINTSTS) & GINTSTS_CURMODE_HOST) == 0; +} + +/* + * 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 written back. + */ +static inline u32 dwc2_read_hprt0(struct dwc2_hsotg *hsotg) +{ +	u32 hprt0 = readl(hsotg->regs + HPRT0); + +	hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG); +	return hprt0; +} + +static inline u8 dwc2_hcd_get_ep_num(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->ep_num; +} + +static inline u8 dwc2_hcd_get_pipe_type(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->pipe_type; +} + +static inline u16 dwc2_hcd_get_mps(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->mps; +} + +static inline u8 dwc2_hcd_get_dev_addr(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->dev_addr; +} + +static inline u8 dwc2_hcd_is_pipe_isoc(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->pipe_type == USB_ENDPOINT_XFER_ISOC; +} + +static inline u8 dwc2_hcd_is_pipe_int(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->pipe_type == USB_ENDPOINT_XFER_INT; +} + +static inline u8 dwc2_hcd_is_pipe_bulk(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->pipe_type == USB_ENDPOINT_XFER_BULK; +} + +static inline u8 dwc2_hcd_is_pipe_control(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->pipe_type == USB_ENDPOINT_XFER_CONTROL; +} + +static inline u8 dwc2_hcd_is_pipe_in(struct dwc2_hcd_pipe_info *pipe) +{ +	return pipe->pipe_dir == USB_DIR_IN; +} + +static inline u8 dwc2_hcd_is_pipe_out(struct dwc2_hcd_pipe_info *pipe) +{ +	return !dwc2_hcd_is_pipe_in(pipe); +} + +extern int dwc2_hcd_init(struct dwc2_hsotg *hsotg, int irq, +			 const struct dwc2_core_params *params); +extern void dwc2_hcd_remove(struct dwc2_hsotg *hsotg); +extern void dwc2_set_parameters(struct dwc2_hsotg *hsotg, +				const struct dwc2_core_params *params); +extern void dwc2_set_all_params(struct dwc2_core_params *params, int value); +extern int dwc2_get_hwparams(struct dwc2_hsotg *hsotg); + +/* Transaction Execution Functions */ +extern enum dwc2_transaction_type dwc2_hcd_select_transactions( +						struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_queue_transactions(struct dwc2_hsotg *hsotg, +					enum dwc2_transaction_type tr_type); + +/* Schedule Queue Functions */ +/* Implemented in hcd_queue.c */ +extern void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); +extern int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); +extern void dwc2_hcd_qh_unlink(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); +extern void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +				   int sched_csplit); + +extern void dwc2_hcd_qtd_init(struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb); +extern int dwc2_hcd_qtd_add(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, +			    struct dwc2_qh **qh, gfp_t mem_flags); + +/* Unlinks and frees a QTD */ +static inline void dwc2_hcd_qtd_unlink_and_free(struct dwc2_hsotg *hsotg, +						struct dwc2_qtd *qtd, +						struct dwc2_qh *qh) +{ +	list_del(&qtd->qtd_list_entry); +	kfree(qtd); +} + +/* Descriptor DMA support functions */ +extern void dwc2_hcd_start_xfer_ddma(struct dwc2_hsotg *hsotg, +				     struct dwc2_qh *qh); +extern void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, +					struct dwc2_host_chan *chan, int chnum, +					enum dwc2_halt_status halt_status); + +extern int dwc2_hcd_qh_init_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +				 gfp_t mem_flags); +extern void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh); + +/* Check if QH is non-periodic */ +#define dwc2_qh_is_non_per(_qh_ptr_) \ +	((_qh_ptr_)->ep_type == USB_ENDPOINT_XFER_BULK || \ +	 (_qh_ptr_)->ep_type == USB_ENDPOINT_XFER_CONTROL) + +#ifdef CONFIG_USB_DWC2_DEBUG_PERIODIC +static inline bool dbg_hc(struct dwc2_host_chan *hc) { return true; } +static inline bool dbg_qh(struct dwc2_qh *qh) { return true; } +static inline bool dbg_urb(struct urb *urb) { return true; } +static inline bool dbg_perio(void) { return true; } +#else /* !CONFIG_USB_DWC2_DEBUG_PERIODIC */ +static inline bool dbg_hc(struct dwc2_host_chan *hc) +{ +	return hc->ep_type == USB_ENDPOINT_XFER_BULK || +	       hc->ep_type == USB_ENDPOINT_XFER_CONTROL; +} + +static inline bool dbg_qh(struct dwc2_qh *qh) +{ +	return qh->ep_type == USB_ENDPOINT_XFER_BULK || +	       qh->ep_type == USB_ENDPOINT_XFER_CONTROL; +} + +static inline bool dbg_urb(struct urb *urb) +{ +	return usb_pipetype(urb->pipe) == PIPE_BULK || +	       usb_pipetype(urb->pipe) == PIPE_CONTROL; +} + +static inline bool dbg_perio(void) { return false; } +#endif + +/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */ +#define dwc2_hb_mult(wmaxpacketsize) (1 + (((wmaxpacketsize) >> 11) & 0x03)) + +/* Packet size for any kind of endpoint descriptor */ +#define dwc2_max_packet(wmaxpacketsize) ((wmaxpacketsize) & 0x07ff) + +/* + * Returns true if frame1 is less than or equal to frame2. The comparison is + * done modulo HFNUM_MAX_FRNUM. This accounts for the rollover of the + * frame number when the max frame number is reached. + */ +static inline int dwc2_frame_num_le(u16 frame1, u16 frame2) +{ +	return ((frame2 - frame1) & HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM >> 1); +} + +/* + * Returns true if frame1 is greater than frame2. The comparison is done + * modulo HFNUM_MAX_FRNUM. This accounts for the rollover of the frame + * number when the max frame number is reached. + */ +static inline int dwc2_frame_num_gt(u16 frame1, u16 frame2) +{ +	return (frame1 != frame2) && +	       ((frame1 - frame2) & HFNUM_MAX_FRNUM) < (HFNUM_MAX_FRNUM >> 1); +} + +/* + * Increments frame by the amount specified by inc. The addition is done + * modulo HFNUM_MAX_FRNUM. Returns the incremented value. + */ +static inline u16 dwc2_frame_num_inc(u16 frame, u16 inc) +{ +	return (frame + inc) & HFNUM_MAX_FRNUM; +} + +static inline u16 dwc2_full_frame_num(u16 frame) +{ +	return (frame & HFNUM_MAX_FRNUM) >> 3; +} + +static inline u16 dwc2_micro_frame_num(u16 frame) +{ +	return frame & 0x7; +} + +/* + * Returns the Core Interrupt Status register contents, ANDed with the Core + * Interrupt Mask register contents + */ +static inline u32 dwc2_read_core_intr(struct dwc2_hsotg *hsotg) +{ +	return readl(hsotg->regs + GINTSTS) & readl(hsotg->regs + GINTMSK); +} + +static inline u32 dwc2_hcd_urb_get_status(struct dwc2_hcd_urb *dwc2_urb) +{ +	return dwc2_urb->status; +} + +static inline u32 dwc2_hcd_urb_get_actual_length( +		struct dwc2_hcd_urb *dwc2_urb) +{ +	return dwc2_urb->actual_length; +} + +static inline u32 dwc2_hcd_urb_get_error_count(struct dwc2_hcd_urb *dwc2_urb) +{ +	return dwc2_urb->error_count; +} + +static inline void dwc2_hcd_urb_set_iso_desc_params( +		struct dwc2_hcd_urb *dwc2_urb, int desc_num, u32 offset, +		u32 length) +{ +	dwc2_urb->iso_descs[desc_num].offset = offset; +	dwc2_urb->iso_descs[desc_num].length = length; +} + +static inline u32 dwc2_hcd_urb_get_iso_desc_status( +		struct dwc2_hcd_urb *dwc2_urb, int desc_num) +{ +	return dwc2_urb->iso_descs[desc_num].status; +} + +static inline u32 dwc2_hcd_urb_get_iso_desc_actual_length( +		struct dwc2_hcd_urb *dwc2_urb, int desc_num) +{ +	return dwc2_urb->iso_descs[desc_num].actual_length; +} + +static inline int dwc2_hcd_is_bandwidth_allocated(struct dwc2_hsotg *hsotg, +						  struct usb_host_endpoint *ep) +{ +	struct dwc2_qh *qh = ep->hcpriv; + +	if (qh && !list_empty(&qh->qh_list_entry)) +		return 1; + +	return 0; +} + +static inline u16 dwc2_hcd_get_ep_bandwidth(struct dwc2_hsotg *hsotg, +					    struct usb_host_endpoint *ep) +{ +	struct dwc2_qh *qh = ep->hcpriv; + +	if (!qh) { +		WARN_ON(1); +		return 0; +	} + +	return qh->usecs; +} + +extern void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, +				      struct dwc2_host_chan *chan, int chnum, +				      struct dwc2_qtd *qtd); + +/* HCD Core API */ + +/** + * dwc2_handle_hcd_intr() - Called on every hardware interrupt + * + * @hsotg: The DWC2 HCD + * + * Returns IRQ_HANDLED if interrupt is handled + * Return IRQ_NONE if interrupt is not handled + */ +extern irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_stop() - Halts the DWC_otg host mode operation + * + * @hsotg: The DWC2 HCD + */ +extern void dwc2_hcd_stop(struct dwc2_hsotg *hsotg); + +extern void dwc2_hcd_start(struct dwc2_hsotg *hsotg); +extern void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_is_b_host() - Returns 1 if core currently is acting as B host, + * and 0 otherwise + * + * @hsotg: The DWC2 HCD + */ +extern int dwc2_hcd_is_b_host(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_get_frame_number() - Returns current frame number + * + * @hsotg: The DWC2 HCD + */ +extern int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_dump_state() - Dumps hsotg state + * + * @hsotg: The DWC2 HCD + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +extern void dwc2_hcd_dump_state(struct dwc2_hsotg *hsotg); + +/** + * dwc2_hcd_dump_frrem() - Dumps the average frame remaining at SOF + * + * @hsotg: The DWC2 HCD + * + * This can be used to determine average interrupt latency. Frame remaining is + * also shown for start transfer and two additional sample points. + * + * NOTE: This function will be removed once the peripheral controller code + * is integrated and the driver is stable + */ +extern void dwc2_hcd_dump_frrem(struct dwc2_hsotg *hsotg); + +/* URB interface */ + +/* Transfer flags */ +#define URB_GIVEBACK_ASAP	0x1 +#define URB_SEND_ZERO_PACKET	0x2 + +/* Host driver callbacks */ + +extern void dwc2_host_start(struct dwc2_hsotg *hsotg); +extern void dwc2_host_disconnect(struct dwc2_hsotg *hsotg); +extern void dwc2_host_hub_info(struct dwc2_hsotg *hsotg, void *context, +			       int *hub_addr, int *hub_port); +extern int dwc2_host_get_speed(struct dwc2_hsotg *hsotg, void *context); +extern void dwc2_host_complete(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, +			       int status); + +#ifdef DEBUG +/* + * Macro to sample the remaining PHY clocks left in the current frame. This + * may be used during debugging to determine the average time it takes to + * execute sections of code. There are two possible sample points, "a" and + * "b", so the _letter_ argument must be one of these values. + * + * To dump the average sample times, read the "hcd_frrem" sysfs attribute. For + * example, "cat /sys/devices/lm0/hcd_frrem". + */ +#define dwc2_sample_frrem(_hcd_, _qh_, _letter_)			\ +do {									\ +	struct hfnum_data _hfnum_;					\ +	struct dwc2_qtd *_qtd_;						\ +									\ +	_qtd_ = list_entry((_qh_)->qtd_list.next, struct dwc2_qtd,	\ +			   qtd_list_entry);				\ +	if (usb_pipeint(_qtd_->urb->pipe) &&				\ +	    (_qh_)->start_split_frame != 0 && !_qtd_->complete_split) {	\ +		_hfnum_.d32 = readl((_hcd_)->regs + HFNUM);		\ +		switch (_hfnum_.b.frnum & 0x7) {			\ +		case 7:							\ +			(_hcd_)->hfnum_7_samples_##_letter_++;		\ +			(_hcd_)->hfnum_7_frrem_accum_##_letter_ +=	\ +				_hfnum_.b.frrem;			\ +			break;						\ +		case 0:							\ +			(_hcd_)->hfnum_0_samples_##_letter_++;		\ +			(_hcd_)->hfnum_0_frrem_accum_##_letter_ +=	\ +				_hfnum_.b.frrem;			\ +			break;						\ +		default:						\ +			(_hcd_)->hfnum_other_samples_##_letter_++;	\ +			(_hcd_)->hfnum_other_frrem_accum_##_letter_ +=	\ +				_hfnum_.b.frrem;			\ +			break;						\ +		}							\ +	}								\ +} while (0) +#else +#define dwc2_sample_frrem(_hcd_, _qh_, _letter_)	do {} while (0) +#endif + +#endif /* __DWC2_HCD_H__ */ diff --git a/drivers/usb/dwc2/hcd_ddma.c b/drivers/usb/dwc2/hcd_ddma.c new file mode 100644 index 00000000000..3376177e4d3 --- /dev/null +++ b/drivers/usb/dwc2/hcd_ddma.c @@ -0,0 +1,1212 @@ +/* + * hcd_ddma.c - DesignWare HS OTG Controller descriptor DMA routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the Descriptor DMA implementation for Host mode + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +static u16 dwc2_frame_list_idx(u16 frame) +{ +	return frame & (FRLISTEN_64_SIZE - 1); +} + +static u16 dwc2_desclist_idx_inc(u16 idx, u16 inc, u8 speed) +{ +	return (idx + inc) & +		((speed == USB_SPEED_HIGH ? MAX_DMA_DESC_NUM_HS_ISOC : +		  MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static u16 dwc2_desclist_idx_dec(u16 idx, u16 inc, u8 speed) +{ +	return (idx - inc) & +		((speed == USB_SPEED_HIGH ? MAX_DMA_DESC_NUM_HS_ISOC : +		  MAX_DMA_DESC_NUM_GENERIC) - 1); +} + +static u16 dwc2_max_desc_num(struct dwc2_qh *qh) +{ +	return (qh->ep_type == USB_ENDPOINT_XFER_ISOC && +		qh->dev_speed == USB_SPEED_HIGH) ? +		MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC; +} + +static u16 dwc2_frame_incr_val(struct dwc2_qh *qh) +{ +	return qh->dev_speed == USB_SPEED_HIGH ? +	       (qh->interval + 8 - 1) / 8 : qh->interval; +} + +static int dwc2_desc_list_alloc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +				gfp_t flags) +{ +	qh->desc_list = dma_alloc_coherent(hsotg->dev, +				sizeof(struct dwc2_hcd_dma_desc) * +				dwc2_max_desc_num(qh), &qh->desc_list_dma, +				flags); + +	if (!qh->desc_list) +		return -ENOMEM; + +	memset(qh->desc_list, 0, +	       sizeof(struct dwc2_hcd_dma_desc) * dwc2_max_desc_num(qh)); + +	qh->n_bytes = kzalloc(sizeof(u32) * dwc2_max_desc_num(qh), flags); +	if (!qh->n_bytes) { +		dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) +				  * dwc2_max_desc_num(qh), qh->desc_list, +				  qh->desc_list_dma); +		qh->desc_list = NULL; +		return -ENOMEM; +	} + +	return 0; +} + +static void dwc2_desc_list_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	if (qh->desc_list) { +		dma_free_coherent(hsotg->dev, sizeof(struct dwc2_hcd_dma_desc) +				  * dwc2_max_desc_num(qh), qh->desc_list, +				  qh->desc_list_dma); +		qh->desc_list = NULL; +	} + +	kfree(qh->n_bytes); +	qh->n_bytes = NULL; +} + +static int dwc2_frame_list_alloc(struct dwc2_hsotg *hsotg, gfp_t mem_flags) +{ +	if (hsotg->frame_list) +		return 0; + +	hsotg->frame_list = dma_alloc_coherent(hsotg->dev, +					       4 * FRLISTEN_64_SIZE, +					       &hsotg->frame_list_dma, +					       mem_flags); +	if (!hsotg->frame_list) +		return -ENOMEM; + +	memset(hsotg->frame_list, 0, 4 * FRLISTEN_64_SIZE); +	return 0; +} + +static void dwc2_frame_list_free(struct dwc2_hsotg *hsotg) +{ +	u32 *frame_list; +	dma_addr_t frame_list_dma; +	unsigned long flags; + +	spin_lock_irqsave(&hsotg->lock, flags); + +	if (!hsotg->frame_list) { +		spin_unlock_irqrestore(&hsotg->lock, flags); +		return; +	} + +	frame_list = hsotg->frame_list; +	frame_list_dma = hsotg->frame_list_dma; +	hsotg->frame_list = NULL; + +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	dma_free_coherent(hsotg->dev, 4 * FRLISTEN_64_SIZE, frame_list, +			  frame_list_dma); +} + +static void dwc2_per_sched_enable(struct dwc2_hsotg *hsotg, u32 fr_list_en) +{ +	u32 hcfg; +	unsigned long flags; + +	spin_lock_irqsave(&hsotg->lock, flags); + +	hcfg = readl(hsotg->regs + HCFG); +	if (hcfg & HCFG_PERSCHEDENA) { +		/* already enabled */ +		spin_unlock_irqrestore(&hsotg->lock, flags); +		return; +	} + +	writel(hsotg->frame_list_dma, hsotg->regs + HFLBADDR); + +	hcfg &= ~HCFG_FRLISTEN_MASK; +	hcfg |= fr_list_en | HCFG_PERSCHEDENA; +	dev_vdbg(hsotg->dev, "Enabling Periodic schedule\n"); +	writel(hcfg, hsotg->regs + HCFG); + +	spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static void dwc2_per_sched_disable(struct dwc2_hsotg *hsotg) +{ +	u32 hcfg; +	unsigned long flags; + +	spin_lock_irqsave(&hsotg->lock, flags); + +	hcfg = readl(hsotg->regs + HCFG); +	if (!(hcfg & HCFG_PERSCHEDENA)) { +		/* already disabled */ +		spin_unlock_irqrestore(&hsotg->lock, flags); +		return; +	} + +	hcfg &= ~HCFG_PERSCHEDENA; +	dev_vdbg(hsotg->dev, "Disabling Periodic schedule\n"); +	writel(hcfg, hsotg->regs + HCFG); + +	spin_unlock_irqrestore(&hsotg->lock, flags); +} + +/* + * Activates/Deactivates FrameList entries for the channel based on endpoint + * servicing period + */ +static void dwc2_update_frame_list(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +				   int enable) +{ +	struct dwc2_host_chan *chan; +	u16 i, j, inc; + +	if (!hsotg) { +		pr_err("hsotg = %p\n", hsotg); +		return; +	} + +	if (!qh->channel) { +		dev_err(hsotg->dev, "qh->channel = %p\n", qh->channel); +		return; +	} + +	if (!hsotg->frame_list) { +		dev_err(hsotg->dev, "hsotg->frame_list = %p\n", +			hsotg->frame_list); +		return; +	} + +	chan = qh->channel; +	inc = dwc2_frame_incr_val(qh); +	if (qh->ep_type == USB_ENDPOINT_XFER_ISOC) +		i = dwc2_frame_list_idx(qh->sched_frame); +	else +		i = 0; + +	j = i; +	do { +		if (enable) +			hsotg->frame_list[j] |= 1 << chan->hc_num; +		else +			hsotg->frame_list[j] &= ~(1 << chan->hc_num); +		j = (j + inc) & (FRLISTEN_64_SIZE - 1); +	} while (j != i); + +	if (!enable) +		return; + +	chan->schinfo = 0; +	if (chan->speed == USB_SPEED_HIGH && qh->interval) { +		j = 1; +		/* TODO - check this */ +		inc = (8 + qh->interval - 1) / qh->interval; +		for (i = 0; i < inc; i++) { +			chan->schinfo |= j; +			j = j << qh->interval; +		} +	} else { +		chan->schinfo = 0xff; +	} +} + +static void dwc2_release_channel_ddma(struct dwc2_hsotg *hsotg, +				      struct dwc2_qh *qh) +{ +	struct dwc2_host_chan *chan = qh->channel; + +	if (dwc2_qh_is_non_per(qh)) { +		if (hsotg->core_params->uframe_sched > 0) +			hsotg->available_host_channels++; +		else +			hsotg->non_periodic_channels--; +	} else { +		dwc2_update_frame_list(hsotg, qh, 0); +	} + +	/* +	 * The condition is added to prevent double cleanup try in case of +	 * device disconnect. See channel cleanup in dwc2_hcd_disconnect(). +	 */ +	if (chan->qh) { +		if (!list_empty(&chan->hc_list_entry)) +			list_del(&chan->hc_list_entry); +		dwc2_hc_cleanup(hsotg, chan); +		list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); +		chan->qh = NULL; +	} + +	qh->channel = NULL; +	qh->ntd = 0; + +	if (qh->desc_list) +		memset(qh->desc_list, 0, sizeof(struct dwc2_hcd_dma_desc) * +		       dwc2_max_desc_num(qh)); +} + +/** + * dwc2_hcd_qh_init_ddma() - Initializes a QH structure's Descriptor DMA + * related members + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    The QH to init + * + * Return: 0 if successful, negative error code otherwise + * + * Allocates memory for the descriptor list. For the first periodic QH, + * allocates memory for the FrameList and enables periodic scheduling. + */ +int dwc2_hcd_qh_init_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +			  gfp_t mem_flags) +{ +	int retval; + +	if (qh->do_split) { +		dev_err(hsotg->dev, +			"SPLIT Transfers are not supported in Descriptor DMA mode.\n"); +		retval = -EINVAL; +		goto err0; +	} + +	retval = dwc2_desc_list_alloc(hsotg, qh, mem_flags); +	if (retval) +		goto err0; + +	if (qh->ep_type == USB_ENDPOINT_XFER_ISOC || +	    qh->ep_type == USB_ENDPOINT_XFER_INT) { +		if (!hsotg->frame_list) { +			retval = dwc2_frame_list_alloc(hsotg, mem_flags); +			if (retval) +				goto err1; +			/* Enable periodic schedule on first periodic QH */ +			dwc2_per_sched_enable(hsotg, HCFG_FRLISTEN_64); +		} +	} + +	qh->ntd = 0; +	return 0; + +err1: +	dwc2_desc_list_free(hsotg, qh); +err0: +	return retval; +} + +/** + * dwc2_hcd_qh_free_ddma() - Frees a QH structure's Descriptor DMA related + * members + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    The QH to free + * + * Frees descriptor list memory associated with the QH. If QH is periodic and + * the last, frees FrameList memory and disables periodic scheduling. + */ +void dwc2_hcd_qh_free_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	dwc2_desc_list_free(hsotg, qh); + +	/* +	 * Channel still assigned due to some reasons. +	 * Seen on Isoc URB dequeue. Channel halted but no subsequent +	 * ChHalted interrupt to release the channel. Afterwards +	 * when it comes here from endpoint disable routine +	 * channel remains assigned. +	 */ +	if (qh->channel) +		dwc2_release_channel_ddma(hsotg, qh); + +	if ((qh->ep_type == USB_ENDPOINT_XFER_ISOC || +	     qh->ep_type == USB_ENDPOINT_XFER_INT) && +	    (hsotg->core_params->uframe_sched > 0 || +	     !hsotg->periodic_channels) && hsotg->frame_list) { +		dwc2_per_sched_disable(hsotg); +		dwc2_frame_list_free(hsotg); +	} +} + +static u8 dwc2_frame_to_desc_idx(struct dwc2_qh *qh, u16 frame_idx) +{ +	if (qh->dev_speed == USB_SPEED_HIGH) +		/* Descriptor set (8 descriptors) index which is 8-aligned */ +		return (frame_idx & ((MAX_DMA_DESC_NUM_HS_ISOC / 8) - 1)) * 8; +	else +		return frame_idx & (MAX_DMA_DESC_NUM_GENERIC - 1); +} + +/* + * Determine starting frame for Isochronous transfer. + * Few frames skipped to prevent race condition with HC. + */ +static u16 dwc2_calc_starting_frame(struct dwc2_hsotg *hsotg, +				    struct dwc2_qh *qh, u16 *skip_frames) +{ +	u16 frame; + +	hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + +	/* sched_frame is always frame number (not uFrame) both in FS and HS! */ + +	/* +	 * skip_frames is used to limit activated descriptors number +	 * to avoid the situation when HC services the last activated +	 * descriptor firstly. +	 * Example for FS: +	 * Current frame is 1, scheduled frame is 3. Since HC always fetches +	 * the descriptor corresponding to curr_frame+1, the descriptor +	 * corresponding to frame 2 will be fetched. If the number of +	 * descriptors is max=64 (or greather) the list will be fully programmed +	 * with Active descriptors and it is possible case (rare) that the +	 * latest descriptor(considering rollback) corresponding to frame 2 will +	 * be serviced first. HS case is more probable because, in fact, up to +	 * 11 uframes (16 in the code) may be skipped. +	 */ +	if (qh->dev_speed == USB_SPEED_HIGH) { +		/* +		 * Consider uframe counter also, to start xfer asap. If half of +		 * the frame elapsed skip 2 frames otherwise just 1 frame. +		 * Starting descriptor index must be 8-aligned, so if the +		 * current frame is near to complete the next one is skipped as +		 * well. +		 */ +		if (dwc2_micro_frame_num(hsotg->frame_number) >= 5) { +			*skip_frames = 2 * 8; +			frame = dwc2_frame_num_inc(hsotg->frame_number, +						   *skip_frames); +		} else { +			*skip_frames = 1 * 8; +			frame = dwc2_frame_num_inc(hsotg->frame_number, +						   *skip_frames); +		} + +		frame = dwc2_full_frame_num(frame); +	} else { +		/* +		 * Two frames are skipped for FS - the current and the next. +		 * But for descriptor programming, 1 frame (descriptor) is +		 * enough, see example above. +		 */ +		*skip_frames = 1; +		frame = dwc2_frame_num_inc(hsotg->frame_number, 2); +	} + +	return frame; +} + +/* + * Calculate initial descriptor index for isochronous transfer based on + * scheduled frame + */ +static u16 dwc2_recalc_initial_desc_idx(struct dwc2_hsotg *hsotg, +					struct dwc2_qh *qh) +{ +	u16 frame, fr_idx, fr_idx_tmp, skip_frames; + +	/* +	 * With current ISOC processing algorithm the channel is being released +	 * when no more QTDs in the list (qh->ntd == 0). Thus this function is +	 * called only when qh->ntd == 0 and qh->channel == 0. +	 * +	 * So qh->channel != NULL branch is not used and just not removed from +	 * the source file. It is required for another possible approach which +	 * is, do not disable and release the channel when ISOC session +	 * completed, just move QH to inactive schedule until new QTD arrives. +	 * On new QTD, the QH moved back to 'ready' schedule, starting frame and +	 * therefore starting desc_index are recalculated. In this case channel +	 * is released only on ep_disable. +	 */ + +	/* +	 * Calculate starting descriptor index. For INTERRUPT endpoint it is +	 * always 0. +	 */ +	if (qh->channel) { +		frame = dwc2_calc_starting_frame(hsotg, qh, &skip_frames); +		/* +		 * Calculate initial descriptor index based on FrameList current +		 * bitmap and servicing period +		 */ +		fr_idx_tmp = dwc2_frame_list_idx(frame); +		fr_idx = (FRLISTEN_64_SIZE + +			  dwc2_frame_list_idx(qh->sched_frame) - fr_idx_tmp) +			 % dwc2_frame_incr_val(qh); +		fr_idx = (fr_idx + fr_idx_tmp) % FRLISTEN_64_SIZE; +	} else { +		qh->sched_frame = dwc2_calc_starting_frame(hsotg, qh, +							   &skip_frames); +		fr_idx = dwc2_frame_list_idx(qh->sched_frame); +	} + +	qh->td_first = qh->td_last = dwc2_frame_to_desc_idx(qh, fr_idx); + +	return skip_frames; +} + +#define ISOC_URB_GIVEBACK_ASAP + +#define MAX_ISOC_XFER_SIZE_FS	1023 +#define MAX_ISOC_XFER_SIZE_HS	3072 +#define DESCNUM_THRESHOLD	4 + +static void dwc2_fill_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, +					 struct dwc2_qtd *qtd, +					 struct dwc2_qh *qh, u32 max_xfer_size, +					 u16 idx) +{ +	struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; +	struct dwc2_hcd_iso_packet_desc *frame_desc; + +	memset(dma_desc, 0, sizeof(*dma_desc)); +	frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; + +	if (frame_desc->length > max_xfer_size) +		qh->n_bytes[idx] = max_xfer_size; +	else +		qh->n_bytes[idx] = frame_desc->length; + +	dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); +	dma_desc->status = qh->n_bytes[idx] << HOST_DMA_ISOC_NBYTES_SHIFT & +			   HOST_DMA_ISOC_NBYTES_MASK; + +#ifdef ISOC_URB_GIVEBACK_ASAP +	/* Set IOC for each descriptor corresponding to last frame of URB */ +	if (qtd->isoc_frame_index_last == qtd->urb->packet_count) +		dma_desc->status |= HOST_DMA_IOC; +#endif + +	qh->ntd++; +	qtd->isoc_frame_index_last++; +} + +static void dwc2_init_isoc_dma_desc(struct dwc2_hsotg *hsotg, +				    struct dwc2_qh *qh, u16 skip_frames) +{ +	struct dwc2_qtd *qtd; +	u32 max_xfer_size; +	u16 idx, inc, n_desc, ntd_max = 0; + +	idx = qh->td_last; +	inc = qh->interval; +	n_desc = 0; + +	if (qh->interval) { +		ntd_max = (dwc2_max_desc_num(qh) + qh->interval - 1) / +				qh->interval; +		if (skip_frames && !qh->channel) +			ntd_max -= skip_frames / qh->interval; +	} + +	max_xfer_size = qh->dev_speed == USB_SPEED_HIGH ? +			MAX_ISOC_XFER_SIZE_HS : MAX_ISOC_XFER_SIZE_FS; + +	list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { +		while (qh->ntd < ntd_max && qtd->isoc_frame_index_last < +						qtd->urb->packet_count) { +			if (n_desc > 1) +				qh->desc_list[n_desc - 1].status |= HOST_DMA_A; +			dwc2_fill_host_isoc_dma_desc(hsotg, qtd, qh, +						     max_xfer_size, idx); +			idx = dwc2_desclist_idx_inc(idx, inc, qh->dev_speed); +			n_desc++; +		} +		qtd->in_process = 1; +	} + +	qh->td_last = idx; + +#ifdef ISOC_URB_GIVEBACK_ASAP +	/* Set IOC for last descriptor if descriptor list is full */ +	if (qh->ntd == ntd_max) { +		idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); +		qh->desc_list[idx].status |= HOST_DMA_IOC; +	} +#else +	/* +	 * Set IOC bit only for one descriptor. Always try to be ahead of HW +	 * processing, i.e. on IOC generation driver activates next descriptor +	 * but core continues to process descriptors following the one with IOC +	 * set. +	 */ + +	if (n_desc > DESCNUM_THRESHOLD) +		/* +		 * Move IOC "up". Required even if there is only one QTD +		 * in the list, because QTDs might continue to be queued, +		 * but during the activation it was only one queued. +		 * Actually more than one QTD might be in the list if this +		 * function called from XferCompletion - QTDs was queued during +		 * HW processing of the previous descriptor chunk. +		 */ +		idx = dwc2_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), +					    qh->dev_speed); +	else +		/* +		 * Set the IOC for the latest descriptor if either number of +		 * descriptors is not greater than threshold or no more new +		 * descriptors activated +		 */ +		idx = dwc2_desclist_idx_dec(qh->td_last, inc, qh->dev_speed); + +	qh->desc_list[idx].status |= HOST_DMA_IOC; +#endif + +	if (n_desc) { +		qh->desc_list[n_desc - 1].status |= HOST_DMA_A; +		if (n_desc > 1) +			qh->desc_list[0].status |= HOST_DMA_A; +	} +} + +static void dwc2_fill_host_dma_desc(struct dwc2_hsotg *hsotg, +				    struct dwc2_host_chan *chan, +				    struct dwc2_qtd *qtd, struct dwc2_qh *qh, +				    int n_desc) +{ +	struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[n_desc]; +	int len = chan->xfer_len; + +	if (len > MAX_DMA_DESC_SIZE - (chan->max_packet - 1)) +		len = MAX_DMA_DESC_SIZE - (chan->max_packet - 1); + +	if (chan->ep_is_in) { +		int num_packets; + +		if (len > 0 && chan->max_packet) +			num_packets = (len + chan->max_packet - 1) +					/ chan->max_packet; +		else +			/* Need 1 packet for transfer length of 0 */ +			num_packets = 1; + +		/* Always program an integral # of packets for IN transfers */ +		len = num_packets * chan->max_packet; +	} + +	dma_desc->status = len << HOST_DMA_NBYTES_SHIFT & HOST_DMA_NBYTES_MASK; +	qh->n_bytes[n_desc] = len; + +	if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL && +	    qtd->control_phase == DWC2_CONTROL_SETUP) +		dma_desc->status |= HOST_DMA_SUP; + +	dma_desc->buf = (u32)chan->xfer_dma; + +	/* +	 * Last (or only) descriptor of IN transfer with actual size less +	 * than MaxPacket +	 */ +	if (len > chan->xfer_len) { +		chan->xfer_len = 0; +	} else { +		chan->xfer_dma += len; +		chan->xfer_len -= len; +	} +} + +static void dwc2_init_non_isoc_dma_desc(struct dwc2_hsotg *hsotg, +					struct dwc2_qh *qh) +{ +	struct dwc2_qtd *qtd; +	struct dwc2_host_chan *chan = qh->channel; +	int n_desc = 0; + +	dev_vdbg(hsotg->dev, "%s(): qh=%p dma=%08lx len=%d\n", __func__, qh, +		 (unsigned long)chan->xfer_dma, chan->xfer_len); + +	/* +	 * Start with chan->xfer_dma initialized in assign_and_init_hc(), then +	 * if SG transfer consists of multiple URBs, this pointer is re-assigned +	 * to the buffer of the currently processed QTD. For non-SG request +	 * there is always one QTD active. +	 */ + +	list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) { +		dev_vdbg(hsotg->dev, "qtd=%p\n", qtd); + +		if (n_desc) { +			/* SG request - more than 1 QTD */ +			chan->xfer_dma = qtd->urb->dma + +					qtd->urb->actual_length; +			chan->xfer_len = qtd->urb->length - +					qtd->urb->actual_length; +			dev_vdbg(hsotg->dev, "buf=%08lx len=%d\n", +				 (unsigned long)chan->xfer_dma, chan->xfer_len); +		} + +		qtd->n_desc = 0; +		do { +			if (n_desc > 1) { +				qh->desc_list[n_desc - 1].status |= HOST_DMA_A; +				dev_vdbg(hsotg->dev, +					 "set A bit in desc %d (%p)\n", +					 n_desc - 1, +					 &qh->desc_list[n_desc - 1]); +			} +			dwc2_fill_host_dma_desc(hsotg, chan, qtd, qh, n_desc); +			dev_vdbg(hsotg->dev, +				 "desc %d (%p) buf=%08x status=%08x\n", +				 n_desc, &qh->desc_list[n_desc], +				 qh->desc_list[n_desc].buf, +				 qh->desc_list[n_desc].status); +			qtd->n_desc++; +			n_desc++; +		} while (chan->xfer_len > 0 && +			 n_desc != MAX_DMA_DESC_NUM_GENERIC); + +		dev_vdbg(hsotg->dev, "n_desc=%d\n", n_desc); +		qtd->in_process = 1; +		if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL) +			break; +		if (n_desc == MAX_DMA_DESC_NUM_GENERIC) +			break; +	} + +	if (n_desc) { +		qh->desc_list[n_desc - 1].status |= +				HOST_DMA_IOC | HOST_DMA_EOL | HOST_DMA_A; +		dev_vdbg(hsotg->dev, "set IOC/EOL/A bits in desc %d (%p)\n", +			 n_desc - 1, &qh->desc_list[n_desc - 1]); +		if (n_desc > 1) { +			qh->desc_list[0].status |= HOST_DMA_A; +			dev_vdbg(hsotg->dev, "set A bit in desc 0 (%p)\n", +				 &qh->desc_list[0]); +		} +		chan->ntd = n_desc; +	} +} + +/** + * dwc2_hcd_start_xfer_ddma() - Starts a transfer in Descriptor DMA mode + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    The QH to init + * + * Return: 0 if successful, negative error code otherwise + * + * For Control and Bulk endpoints, initializes descriptor list and starts the + * transfer. For Interrupt and Isochronous endpoints, initializes descriptor + * list then updates FrameList, marking appropriate entries as active. + * + * For Isochronous endpoints the starting descriptor index is calculated based + * on the scheduled frame, but only on the first transfer descriptor within a + * session. Then the transfer is started via enabling the channel. + * + * For Isochronous endpoints the channel is not halted on XferComplete + * interrupt so remains assigned to the endpoint(QH) until session is done. + */ +void dwc2_hcd_start_xfer_ddma(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	/* Channel is already assigned */ +	struct dwc2_host_chan *chan = qh->channel; +	u16 skip_frames = 0; + +	switch (chan->ep_type) { +	case USB_ENDPOINT_XFER_CONTROL: +	case USB_ENDPOINT_XFER_BULK: +		dwc2_init_non_isoc_dma_desc(hsotg, qh); +		dwc2_hc_start_transfer_ddma(hsotg, chan); +		break; +	case USB_ENDPOINT_XFER_INT: +		dwc2_init_non_isoc_dma_desc(hsotg, qh); +		dwc2_update_frame_list(hsotg, qh, 1); +		dwc2_hc_start_transfer_ddma(hsotg, chan); +		break; +	case USB_ENDPOINT_XFER_ISOC: +		if (!qh->ntd) +			skip_frames = dwc2_recalc_initial_desc_idx(hsotg, qh); +		dwc2_init_isoc_dma_desc(hsotg, qh, skip_frames); + +		if (!chan->xfer_started) { +			dwc2_update_frame_list(hsotg, qh, 1); + +			/* +			 * Always set to max, instead of actual size. Otherwise +			 * ntd will be changed with channel being enabled. Not +			 * recommended. +			 */ +			chan->ntd = dwc2_max_desc_num(qh); + +			/* Enable channel only once for ISOC */ +			dwc2_hc_start_transfer_ddma(hsotg, chan); +		} + +		break; +	default: +		break; +	} +} + +#define DWC2_CMPL_DONE		1 +#define DWC2_CMPL_STOP		2 + +static int dwc2_cmpl_host_isoc_dma_desc(struct dwc2_hsotg *hsotg, +					struct dwc2_host_chan *chan, +					struct dwc2_qtd *qtd, +					struct dwc2_qh *qh, u16 idx) +{ +	struct dwc2_hcd_dma_desc *dma_desc = &qh->desc_list[idx]; +	struct dwc2_hcd_iso_packet_desc *frame_desc; +	u16 remain = 0; +	int rc = 0; + +	if (!qtd->urb) +		return -EINVAL; + +	frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last]; +	dma_desc->buf = (u32)(qtd->urb->dma + frame_desc->offset); +	if (chan->ep_is_in) +		remain = (dma_desc->status & HOST_DMA_ISOC_NBYTES_MASK) >> +			 HOST_DMA_ISOC_NBYTES_SHIFT; + +	if ((dma_desc->status & HOST_DMA_STS_MASK) == HOST_DMA_STS_PKTERR) { +		/* +		 * XactError, or unable to complete all the transactions +		 * in the scheduled micro-frame/frame, both indicated by +		 * HOST_DMA_STS_PKTERR +		 */ +		qtd->urb->error_count++; +		frame_desc->actual_length = qh->n_bytes[idx] - remain; +		frame_desc->status = -EPROTO; +	} else { +		/* Success */ +		frame_desc->actual_length = qh->n_bytes[idx] - remain; +		frame_desc->status = 0; +	} + +	if (++qtd->isoc_frame_index == qtd->urb->packet_count) { +		/* +		 * urb->status is not used for isoc transfers here. The +		 * individual frame_desc status are used instead. +		 */ +		dwc2_host_complete(hsotg, qtd, 0); +		dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); + +		/* +		 * This check is necessary because urb_dequeue can be called +		 * from urb complete callback (sound driver for example). All +		 * pending URBs are dequeued there, so no need for further +		 * processing. +		 */ +		if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) +			return -1; +		rc = DWC2_CMPL_DONE; +	} + +	qh->ntd--; + +	/* Stop if IOC requested descriptor reached */ +	if (dma_desc->status & HOST_DMA_IOC) +		rc = DWC2_CMPL_STOP; + +	return rc; +} + +static void dwc2_complete_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, +					 struct dwc2_host_chan *chan, +					 enum dwc2_halt_status halt_status) +{ +	struct dwc2_hcd_iso_packet_desc *frame_desc; +	struct dwc2_qtd *qtd, *qtd_tmp; +	struct dwc2_qh *qh; +	u16 idx; +	int rc; + +	qh = chan->qh; +	idx = qh->td_first; + +	if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { +		list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) +			qtd->in_process = 0; +		return; +	} + +	if (halt_status == DWC2_HC_XFER_AHB_ERR || +	    halt_status == DWC2_HC_XFER_BABBLE_ERR) { +		/* +		 * Channel is halted in these error cases, considered as serious +		 * issues. +		 * Complete all URBs marking all frames as failed, irrespective +		 * whether some of the descriptors (frames) succeeded or not. +		 * Pass error code to completion routine as well, to update +		 * urb->status, some of class drivers might use it to stop +		 * queing transfer requests. +		 */ +		int err = halt_status == DWC2_HC_XFER_AHB_ERR ? +			  -EIO : -EOVERFLOW; + +		list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, +					 qtd_list_entry) { +			if (qtd->urb) { +				for (idx = 0; idx < qtd->urb->packet_count; +				     idx++) { +					frame_desc = &qtd->urb->iso_descs[idx]; +					frame_desc->status = err; +				} + +				dwc2_host_complete(hsotg, qtd, err); +			} + +			dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); +		} + +		return; +	} + +	list_for_each_entry_safe(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) { +		if (!qtd->in_process) +			break; +		do { +			rc = dwc2_cmpl_host_isoc_dma_desc(hsotg, chan, qtd, qh, +							  idx); +			if (rc < 0) +				return; +			idx = dwc2_desclist_idx_inc(idx, qh->interval, +						    chan->speed); +			if (rc == DWC2_CMPL_STOP) +				goto stop_scan; +			if (rc == DWC2_CMPL_DONE) +				break; +		} while (idx != qh->td_first); +	} + +stop_scan: +	qh->td_first = idx; +} + +static int dwc2_update_non_isoc_urb_state_ddma(struct dwc2_hsotg *hsotg, +					struct dwc2_host_chan *chan, +					struct dwc2_qtd *qtd, +					struct dwc2_hcd_dma_desc *dma_desc, +					enum dwc2_halt_status halt_status, +					u32 n_bytes, int *xfer_done) +{ +	struct dwc2_hcd_urb *urb = qtd->urb; +	u16 remain = 0; + +	if (chan->ep_is_in) +		remain = (dma_desc->status & HOST_DMA_NBYTES_MASK) >> +			 HOST_DMA_NBYTES_SHIFT; + +	dev_vdbg(hsotg->dev, "remain=%d dwc2_urb=%p\n", remain, urb); + +	if (halt_status == DWC2_HC_XFER_AHB_ERR) { +		dev_err(hsotg->dev, "EIO\n"); +		urb->status = -EIO; +		return 1; +	} + +	if ((dma_desc->status & HOST_DMA_STS_MASK) == HOST_DMA_STS_PKTERR) { +		switch (halt_status) { +		case DWC2_HC_XFER_STALL: +			dev_vdbg(hsotg->dev, "Stall\n"); +			urb->status = -EPIPE; +			break; +		case DWC2_HC_XFER_BABBLE_ERR: +			dev_err(hsotg->dev, "Babble\n"); +			urb->status = -EOVERFLOW; +			break; +		case DWC2_HC_XFER_XACT_ERR: +			dev_err(hsotg->dev, "XactErr\n"); +			urb->status = -EPROTO; +			break; +		default: +			dev_err(hsotg->dev, +				"%s: Unhandled descriptor error status (%d)\n", +				__func__, halt_status); +			break; +		} +		return 1; +	} + +	if (dma_desc->status & HOST_DMA_A) { +		dev_vdbg(hsotg->dev, +			 "Active descriptor encountered on channel %d\n", +			 chan->hc_num); +		return 0; +	} + +	if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL) { +		if (qtd->control_phase == DWC2_CONTROL_DATA) { +			urb->actual_length += n_bytes - remain; +			if (remain || urb->actual_length >= urb->length) { +				/* +				 * For Control Data stage do not set urb->status +				 * to 0, to prevent URB callback. Set it when +				 * Status phase is done. See below. +				 */ +				*xfer_done = 1; +			} +		} else if (qtd->control_phase == DWC2_CONTROL_STATUS) { +			urb->status = 0; +			*xfer_done = 1; +		} +		/* No handling for SETUP stage */ +	} else { +		/* BULK and INTR */ +		urb->actual_length += n_bytes - remain; +		dev_vdbg(hsotg->dev, "length=%d actual=%d\n", urb->length, +			 urb->actual_length); +		if (remain || urb->actual_length >= urb->length) { +			urb->status = 0; +			*xfer_done = 1; +		} +	} + +	return 0; +} + +static int dwc2_process_non_isoc_desc(struct dwc2_hsotg *hsotg, +				      struct dwc2_host_chan *chan, +				      int chnum, struct dwc2_qtd *qtd, +				      int desc_num, +				      enum dwc2_halt_status halt_status, +				      int *xfer_done) +{ +	struct dwc2_qh *qh = chan->qh; +	struct dwc2_hcd_urb *urb = qtd->urb; +	struct dwc2_hcd_dma_desc *dma_desc; +	u32 n_bytes; +	int failed; + +	dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	if (!urb) +		return -EINVAL; + +	dma_desc = &qh->desc_list[desc_num]; +	n_bytes = qh->n_bytes[desc_num]; +	dev_vdbg(hsotg->dev, +		 "qtd=%p dwc2_urb=%p desc_num=%d desc=%p n_bytes=%d\n", +		 qtd, urb, desc_num, dma_desc, n_bytes); +	failed = dwc2_update_non_isoc_urb_state_ddma(hsotg, chan, qtd, dma_desc, +						     halt_status, n_bytes, +						     xfer_done); +	if (failed || (*xfer_done && urb->status != -EINPROGRESS)) { +		dwc2_host_complete(hsotg, qtd, urb->status); +		dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); +		dev_vdbg(hsotg->dev, "failed=%1x xfer_done=%1x status=%08x\n", +			 failed, *xfer_done, urb->status); +		return failed; +	} + +	if (qh->ep_type == USB_ENDPOINT_XFER_CONTROL) { +		switch (qtd->control_phase) { +		case DWC2_CONTROL_SETUP: +			if (urb->length > 0) +				qtd->control_phase = DWC2_CONTROL_DATA; +			else +				qtd->control_phase = DWC2_CONTROL_STATUS; +			dev_vdbg(hsotg->dev, +				 "  Control setup transaction done\n"); +			break; +		case DWC2_CONTROL_DATA: +			if (*xfer_done) { +				qtd->control_phase = DWC2_CONTROL_STATUS; +				dev_vdbg(hsotg->dev, +					 "  Control data transfer done\n"); +			} else if (desc_num + 1 == qtd->n_desc) { +				/* +				 * Last descriptor for Control data stage which +				 * is not completed yet +				 */ +				dwc2_hcd_save_data_toggle(hsotg, chan, chnum, +							  qtd); +			} +			break; +		default: +			break; +		} +	} + +	return 0; +} + +static void dwc2_complete_non_isoc_xfer_ddma(struct dwc2_hsotg *hsotg, +					     struct dwc2_host_chan *chan, +					     int chnum, +					     enum dwc2_halt_status halt_status) +{ +	struct list_head *qtd_item, *qtd_tmp; +	struct dwc2_qh *qh = chan->qh; +	struct dwc2_qtd *qtd = NULL; +	int xfer_done; +	int desc_num = 0; + +	if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { +		list_for_each_entry(qtd, &qh->qtd_list, qtd_list_entry) +			qtd->in_process = 0; +		return; +	} + +	list_for_each_safe(qtd_item, qtd_tmp, &qh->qtd_list) { +		int i; + +		qtd = list_entry(qtd_item, struct dwc2_qtd, qtd_list_entry); +		xfer_done = 0; + +		for (i = 0; i < qtd->n_desc; i++) { +			if (dwc2_process_non_isoc_desc(hsotg, chan, chnum, qtd, +						       desc_num, halt_status, +						       &xfer_done)) { +				qtd = NULL; +				break; +			} +			desc_num++; +		} +	} + +	if (qh->ep_type != USB_ENDPOINT_XFER_CONTROL) { +		/* +		 * Resetting the data toggle for bulk and interrupt endpoints +		 * in case of stall. See handle_hc_stall_intr(). +		 */ +		if (halt_status == DWC2_HC_XFER_STALL) +			qh->data_toggle = DWC2_HC_PID_DATA0; +		else if (qtd) +			dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); +	} + +	if (halt_status == DWC2_HC_XFER_COMPLETE) { +		if (chan->hcint & HCINTMSK_NYET) { +			/* +			 * Got a NYET on the last transaction of the transfer. +			 * It means that the endpoint should be in the PING +			 * state at the beginning of the next transfer. +			 */ +			qh->ping_state = 1; +		} +	} +} + +/** + * dwc2_hcd_complete_xfer_ddma() - Scans the descriptor list, updates URB's + * status and calls completion routine for the URB if it's done. Called from + * interrupt handlers. + * + * @hsotg:       The HCD state structure for the DWC OTG controller + * @chan:        Host channel the transfer is completed on + * @chnum:       Index of Host channel registers + * @halt_status: Reason the channel is being halted or just XferComplete + *               for isochronous transfers + * + * Releases the channel to be used by other transfers. + * In case of Isochronous endpoint the channel is not halted until the end of + * the session, i.e. QTD list is empty. + * If periodic channel released the FrameList is updated accordingly. + * Calls transaction selection routines to activate pending transfers. + */ +void dwc2_hcd_complete_xfer_ddma(struct dwc2_hsotg *hsotg, +				 struct dwc2_host_chan *chan, int chnum, +				 enum dwc2_halt_status halt_status) +{ +	struct dwc2_qh *qh = chan->qh; +	int continue_isoc_xfer = 0; +	enum dwc2_transaction_type tr_type; + +	if (chan->ep_type == USB_ENDPOINT_XFER_ISOC) { +		dwc2_complete_isoc_xfer_ddma(hsotg, chan, halt_status); + +		/* Release the channel if halted or session completed */ +		if (halt_status != DWC2_HC_XFER_COMPLETE || +		    list_empty(&qh->qtd_list)) { +			/* Halt the channel if session completed */ +			if (halt_status == DWC2_HC_XFER_COMPLETE) +				dwc2_hc_halt(hsotg, chan, halt_status); +			dwc2_release_channel_ddma(hsotg, qh); +			dwc2_hcd_qh_unlink(hsotg, qh); +		} else { +			/* Keep in assigned schedule to continue transfer */ +			list_move(&qh->qh_list_entry, +				  &hsotg->periodic_sched_assigned); +			continue_isoc_xfer = 1; +		} +		/* +		 * Todo: Consider the case when period exceeds FrameList size. +		 * Frame Rollover interrupt should be used. +		 */ +	} else { +		/* +		 * Scan descriptor list to complete the URB(s), then release +		 * the channel +		 */ +		dwc2_complete_non_isoc_xfer_ddma(hsotg, chan, chnum, +						 halt_status); +		dwc2_release_channel_ddma(hsotg, qh); +		dwc2_hcd_qh_unlink(hsotg, qh); + +		if (!list_empty(&qh->qtd_list)) { +			/* +			 * Add back to inactive non-periodic schedule on normal +			 * completion +			 */ +			dwc2_hcd_qh_add(hsotg, qh); +		} +	} + +	tr_type = dwc2_hcd_select_transactions(hsotg); +	if (tr_type != DWC2_TRANSACTION_NONE || continue_isoc_xfer) { +		if (continue_isoc_xfer) { +			if (tr_type == DWC2_TRANSACTION_NONE) +				tr_type = DWC2_TRANSACTION_PERIODIC; +			else if (tr_type == DWC2_TRANSACTION_NON_PERIODIC) +				tr_type = DWC2_TRANSACTION_ALL; +		} +		dwc2_hcd_queue_transactions(hsotg, tr_type); +	} +} diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c new file mode 100644 index 00000000000..47b9eb5389b --- /dev/null +++ b/drivers/usb/dwc2/hcd_intr.c @@ -0,0 +1,2123 @@ +/* + * hcd_intr.c - DesignWare HS OTG Controller host-mode interrupt handling + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the interrupt handlers for Host mode + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +/* This function is for debug only */ +static void dwc2_track_missed_sofs(struct dwc2_hsotg *hsotg) +{ +#ifdef CONFIG_USB_DWC2_TRACK_MISSED_SOFS +	u16 curr_frame_number = hsotg->frame_number; + +	if (hsotg->frame_num_idx < FRAME_NUM_ARRAY_SIZE) { +		if (((hsotg->last_frame_num + 1) & HFNUM_MAX_FRNUM) != +		    curr_frame_number) { +			hsotg->frame_num_array[hsotg->frame_num_idx] = +					curr_frame_number; +			hsotg->last_frame_num_array[hsotg->frame_num_idx] = +					hsotg->last_frame_num; +			hsotg->frame_num_idx++; +		} +	} else if (!hsotg->dumped_frame_num_array) { +		int i; + +		dev_info(hsotg->dev, "Frame     Last Frame\n"); +		dev_info(hsotg->dev, "-----     ----------\n"); +		for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) { +			dev_info(hsotg->dev, "0x%04x    0x%04x\n", +				 hsotg->frame_num_array[i], +				 hsotg->last_frame_num_array[i]); +		} +		hsotg->dumped_frame_num_array = 1; +	} +	hsotg->last_frame_num = curr_frame_number; +#endif +} + +static void dwc2_hc_handle_tt_clear(struct dwc2_hsotg *hsotg, +				    struct dwc2_host_chan *chan, +				    struct dwc2_qtd *qtd) +{ +	struct urb *usb_urb; + +	if (!chan->qh) +		return; + +	if (chan->qh->dev_speed == USB_SPEED_HIGH) +		return; + +	if (!qtd->urb) +		return; + +	usb_urb = qtd->urb->priv; +	if (!usb_urb || !usb_urb->dev || !usb_urb->dev->tt) +		return; + +	if (qtd->urb->status != -EPIPE && qtd->urb->status != -EREMOTEIO) { +		chan->qh->tt_buffer_dirty = 1; +		if (usb_hub_clear_tt_buffer(usb_urb)) +			/* Clear failed; let's hope things work anyway */ +			chan->qh->tt_buffer_dirty = 0; +	} +} + +/* + * 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 void dwc2_sof_intr(struct dwc2_hsotg *hsotg) +{ +	struct list_head *qh_entry; +	struct dwc2_qh *qh; +	enum dwc2_transaction_type tr_type; + +#ifdef DEBUG_SOF +	dev_vdbg(hsotg->dev, "--Start of Frame Interrupt--\n"); +#endif + +	hsotg->frame_number = dwc2_hcd_get_frame_number(hsotg); + +	dwc2_track_missed_sofs(hsotg); + +	/* Determine whether any periodic QHs should be executed */ +	qh_entry = hsotg->periodic_sched_inactive.next; +	while (qh_entry != &hsotg->periodic_sched_inactive) { +		qh = list_entry(qh_entry, struct dwc2_qh, qh_list_entry); +		qh_entry = qh_entry->next; +		if (dwc2_frame_num_le(qh->sched_frame, hsotg->frame_number)) +			/* +			 * Move QH to the ready list to be executed next +			 * (micro)frame +			 */ +			list_move(&qh->qh_list_entry, +				  &hsotg->periodic_sched_ready); +	} +	tr_type = dwc2_hcd_select_transactions(hsotg); +	if (tr_type != DWC2_TRANSACTION_NONE) +		dwc2_hcd_queue_transactions(hsotg, tr_type); + +	/* Clear interrupt */ +	writel(GINTSTS_SOF, hsotg->regs + GINTSTS); +} + +/* + * Handles the Rx FIFO 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 void dwc2_rx_fifo_level_intr(struct dwc2_hsotg *hsotg) +{ +	u32 grxsts, chnum, bcnt, dpid, pktsts; +	struct dwc2_host_chan *chan; + +	if (dbg_perio()) +		dev_vdbg(hsotg->dev, "--RxFIFO Level Interrupt--\n"); + +	grxsts = readl(hsotg->regs + GRXSTSP); +	chnum = (grxsts & GRXSTS_HCHNUM_MASK) >> GRXSTS_HCHNUM_SHIFT; +	chan = hsotg->hc_ptr_array[chnum]; +	if (!chan) { +		dev_err(hsotg->dev, "Unable to get corresponding channel\n"); +		return; +	} + +	bcnt = (grxsts & GRXSTS_BYTECNT_MASK) >> GRXSTS_BYTECNT_SHIFT; +	dpid = (grxsts & GRXSTS_DPID_MASK) >> GRXSTS_DPID_SHIFT; +	pktsts = (grxsts & GRXSTS_PKTSTS_MASK) >> GRXSTS_PKTSTS_SHIFT; + +	/* Packet Status */ +	if (dbg_perio()) { +		dev_vdbg(hsotg->dev, "    Ch num = %d\n", chnum); +		dev_vdbg(hsotg->dev, "    Count = %d\n", bcnt); +		dev_vdbg(hsotg->dev, "    DPID = %d, chan.dpid = %d\n", dpid, +			 chan->data_pid_start); +		dev_vdbg(hsotg->dev, "    PStatus = %d\n", pktsts); +	} + +	switch (pktsts) { +	case GRXSTS_PKTSTS_HCHIN: +		/* Read the data into the host buffer */ +		if (bcnt > 0) { +			dwc2_read_packet(hsotg, chan->xfer_buf, bcnt); + +			/* Update the HC fields for the next packet received */ +			chan->xfer_count += bcnt; +			chan->xfer_buf += bcnt; +		} +		break; +	case GRXSTS_PKTSTS_HCHIN_XFER_COMP: +	case GRXSTS_PKTSTS_DATATOGGLEERR: +	case GRXSTS_PKTSTS_HCHHALTED: +		/* Handled in interrupt, just ignore data */ +		break; +	default: +		dev_err(hsotg->dev, +			"RxFIFO Level Interrupt: Unknown status %d\n", pktsts); +		break; +	} +} + +/* + * 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 void dwc2_np_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg) +{ +	dev_vdbg(hsotg->dev, "--Non-Periodic TxFIFO Empty Interrupt--\n"); +	dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_NON_PERIODIC); +} + +/* + * 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 void dwc2_perio_tx_fifo_empty_intr(struct dwc2_hsotg *hsotg) +{ +	if (dbg_perio()) +		dev_vdbg(hsotg->dev, "--Periodic TxFIFO Empty Interrupt--\n"); +	dwc2_hcd_queue_transactions(hsotg, DWC2_TRANSACTION_PERIODIC); +} + +static void dwc2_hprt0_enable(struct dwc2_hsotg *hsotg, u32 hprt0, +			      u32 *hprt0_modify) +{ +	struct dwc2_core_params *params = hsotg->core_params; +	int do_reset = 0; +	u32 usbcfg; +	u32 prtspd; +	u32 hcfg; +	u32 fslspclksel; +	u32 hfir; + +	dev_vdbg(hsotg->dev, "%s(%p)\n", __func__, hsotg); + +	/* Every time when port enables calculate HFIR.FrInterval */ +	hfir = readl(hsotg->regs + HFIR); +	hfir &= ~HFIR_FRINT_MASK; +	hfir |= dwc2_calc_frame_interval(hsotg) << HFIR_FRINT_SHIFT & +		HFIR_FRINT_MASK; +	writel(hfir, hsotg->regs + HFIR); + +	/* Check if we need to adjust the PHY clock speed for low power */ +	if (!params->host_support_fs_ls_low_power) { +		/* Port has been enabled, set the reset change flag */ +		hsotg->flags.b.port_reset_change = 1; +		return; +	} + +	usbcfg = readl(hsotg->regs + GUSBCFG); +	prtspd = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + +	if (prtspd == HPRT0_SPD_LOW_SPEED || prtspd == HPRT0_SPD_FULL_SPEED) { +		/* Low power */ +		if (!(usbcfg & GUSBCFG_PHY_LP_CLK_SEL)) { +			/* Set PHY low power clock select for FS/LS devices */ +			usbcfg |= GUSBCFG_PHY_LP_CLK_SEL; +			writel(usbcfg, hsotg->regs + GUSBCFG); +			do_reset = 1; +		} + +		hcfg = readl(hsotg->regs + HCFG); +		fslspclksel = (hcfg & HCFG_FSLSPCLKSEL_MASK) >> +			      HCFG_FSLSPCLKSEL_SHIFT; + +		if (prtspd == HPRT0_SPD_LOW_SPEED && +		    params->host_ls_low_power_phy_clk == +		    DWC2_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) { +			/* 6 MHZ */ +			dev_vdbg(hsotg->dev, +				 "FS_PHY programming HCFG to 6 MHz\n"); +			if (fslspclksel != HCFG_FSLSPCLKSEL_6_MHZ) { +				fslspclksel = HCFG_FSLSPCLKSEL_6_MHZ; +				hcfg &= ~HCFG_FSLSPCLKSEL_MASK; +				hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT; +				writel(hcfg, hsotg->regs + HCFG); +				do_reset = 1; +			} +		} else { +			/* 48 MHZ */ +			dev_vdbg(hsotg->dev, +				 "FS_PHY programming HCFG to 48 MHz\n"); +			if (fslspclksel != HCFG_FSLSPCLKSEL_48_MHZ) { +				fslspclksel = HCFG_FSLSPCLKSEL_48_MHZ; +				hcfg &= ~HCFG_FSLSPCLKSEL_MASK; +				hcfg |= fslspclksel << HCFG_FSLSPCLKSEL_SHIFT; +				writel(hcfg, hsotg->regs + HCFG); +				do_reset = 1; +			} +		} +	} else { +		/* Not low power */ +		if (usbcfg & GUSBCFG_PHY_LP_CLK_SEL) { +			usbcfg &= ~GUSBCFG_PHY_LP_CLK_SEL; +			writel(usbcfg, hsotg->regs + GUSBCFG); +			do_reset = 1; +		} +	} + +	if (do_reset) { +		*hprt0_modify |= HPRT0_RST; +		queue_delayed_work(hsotg->wq_otg, &hsotg->reset_work, +				   msecs_to_jiffies(60)); +	} else { +		/* Port has been enabled, set the reset change flag */ +		hsotg->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 void dwc2_port_intr(struct dwc2_hsotg *hsotg) +{ +	u32 hprt0; +	u32 hprt0_modify; + +	dev_vdbg(hsotg->dev, "--Port Interrupt--\n"); + +	hprt0 = readl(hsotg->regs + HPRT0); +	hprt0_modify = hprt0; + +	/* +	 * Clear appropriate bits in HPRT0 to clear the interrupt bit in +	 * GINTSTS +	 */ +	hprt0_modify &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | +			  HPRT0_OVRCURRCHG); + +	/* +	 * Port Connect Detected +	 * Set flag and clear if detected +	 */ +	if (hprt0 & HPRT0_CONNDET) { +		dev_vdbg(hsotg->dev, +			 "--Port Interrupt HPRT0=0x%08x Port Connect Detected--\n", +			 hprt0); +		hsotg->flags.b.port_connect_status_change = 1; +		hsotg->flags.b.port_connect_status = 1; +		hprt0_modify |= HPRT0_CONNDET; + +		/* +		 * The Hub driver asserts a reset when it sees port connect +		 * status change flag +		 */ +	} + +	/* +	 * Port Enable Changed +	 * Clear if detected - Set internal flag if disabled +	 */ +	if (hprt0 & HPRT0_ENACHG) { +		dev_vdbg(hsotg->dev, +			 "  --Port Interrupt HPRT0=0x%08x Port Enable Changed (now %d)--\n", +			 hprt0, !!(hprt0 & HPRT0_ENA)); +		hprt0_modify |= HPRT0_ENACHG; +		if (hprt0 & HPRT0_ENA) +			dwc2_hprt0_enable(hsotg, hprt0, &hprt0_modify); +		else +			hsotg->flags.b.port_enable_change = 1; +	} + +	/* Overcurrent Change Interrupt */ +	if (hprt0 & HPRT0_OVRCURRCHG) { +		dev_vdbg(hsotg->dev, +			 "  --Port Interrupt HPRT0=0x%08x Port Overcurrent Changed--\n", +			 hprt0); +		hsotg->flags.b.port_over_current_change = 1; +		hprt0_modify |= HPRT0_OVRCURRCHG; +	} + +	/* Clear Port Interrupts */ +	writel(hprt0_modify, hsotg->regs + HPRT0); +} + +/* + * 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 DWC2_HC_XFER_COMPLETE, *short_read + * is set to 1 upon return if less than the requested number of bytes were + * transferred. short_read may also be NULL on entry, in which case it remains + * unchanged. + */ +static u32 dwc2_get_actual_xfer_length(struct dwc2_hsotg *hsotg, +				       struct dwc2_host_chan *chan, int chnum, +				       struct dwc2_qtd *qtd, +				       enum dwc2_halt_status halt_status, +				       int *short_read) +{ +	u32 hctsiz, count, length; + +	hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); + +	if (halt_status == DWC2_HC_XFER_COMPLETE) { +		if (chan->ep_is_in) { +			count = (hctsiz & TSIZ_XFERSIZE_MASK) >> +				TSIZ_XFERSIZE_SHIFT; +			length = chan->xfer_len - count; +			if (short_read != NULL) +				*short_read = (count != 0); +		} else if (chan->qh->do_split) { +			length = qtd->ssplit_out_xfer_count; +		} else { +			length = chan->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). +		 */ +		count = (hctsiz & TSIZ_PKTCNT_MASK) >> TSIZ_PKTCNT_SHIFT; +		length = (chan->start_pkt_count - count) * chan->max_packet; +	} + +	return length; +} + +/** + * dwc2_update_urb_state() - 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. + * + * Return: 1 if the data transfer specified by the URB is completely finished, + * 0 otherwise + */ +static int dwc2_update_urb_state(struct dwc2_hsotg *hsotg, +				 struct dwc2_host_chan *chan, int chnum, +				 struct dwc2_hcd_urb *urb, +				 struct dwc2_qtd *qtd) +{ +	u32 hctsiz; +	int xfer_done = 0; +	int short_read = 0; +	int xfer_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, +						      DWC2_HC_XFER_COMPLETE, +						      &short_read); + +	if (urb->actual_length + xfer_length > urb->length) { +		dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__); +		xfer_length = urb->length - urb->actual_length; +	} + +	/* Non DWORD-aligned buffer case handling */ +	if (chan->align_buf && xfer_length && chan->ep_is_in) { +		dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); +		dma_sync_single_for_cpu(hsotg->dev, urb->dma, urb->length, +					DMA_FROM_DEVICE); +		memcpy(urb->buf + urb->actual_length, chan->qh->dw_align_buf, +		       xfer_length); +		dma_sync_single_for_device(hsotg->dev, urb->dma, urb->length, +					   DMA_FROM_DEVICE); +	} + +	dev_vdbg(hsotg->dev, "urb->actual_length=%d xfer_length=%d\n", +		 urb->actual_length, xfer_length); +	urb->actual_length += xfer_length; + +	if (xfer_length && chan->ep_type == USB_ENDPOINT_XFER_BULK && +	    (urb->flags & URB_SEND_ZERO_PACKET) && +	    urb->actual_length >= urb->length && +	    !(urb->length % chan->max_packet)) { +		xfer_done = 0; +	} else if (short_read || urb->actual_length >= urb->length) { +		xfer_done = 1; +		urb->status = 0; +	} + +	hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); +	dev_vdbg(hsotg->dev, "DWC_otg: %s: %s, channel %d\n", +		 __func__, (chan->ep_is_in ? "IN" : "OUT"), chnum); +	dev_vdbg(hsotg->dev, "  chan->xfer_len %d\n", chan->xfer_len); +	dev_vdbg(hsotg->dev, "  hctsiz.xfersize %d\n", +		 (hctsiz & TSIZ_XFERSIZE_MASK) >> TSIZ_XFERSIZE_SHIFT); +	dev_vdbg(hsotg->dev, "  urb->transfer_buffer_length %d\n", urb->length); +	dev_vdbg(hsotg->dev, "  urb->actual_length %d\n", urb->actual_length); +	dev_vdbg(hsotg->dev, "  short_read %d, xfer_done %d\n", short_read, +		 xfer_done); + +	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. + */ +void dwc2_hcd_save_data_toggle(struct dwc2_hsotg *hsotg, +			       struct dwc2_host_chan *chan, int chnum, +			       struct dwc2_qtd *qtd) +{ +	u32 hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); +	u32 pid = (hctsiz & TSIZ_SC_MC_PID_MASK) >> TSIZ_SC_MC_PID_SHIFT; + +	if (chan->ep_type != USB_ENDPOINT_XFER_CONTROL) { +		if (pid == TSIZ_SC_MC_PID_DATA0) +			chan->qh->data_toggle = DWC2_HC_PID_DATA0; +		else +			chan->qh->data_toggle = DWC2_HC_PID_DATA1; +	} else { +		if (pid == TSIZ_SC_MC_PID_DATA0) +			qtd->data_toggle = DWC2_HC_PID_DATA0; +		else +			qtd->data_toggle = DWC2_HC_PID_DATA1; +	} +} + +/** + * dwc2_update_isoc_urb_state() - 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 + * halt_status. Completes the Isochronous URB if all the URB frames have been + * completed. + * + * Return: DWC2_HC_XFER_COMPLETE if there are more frames remaining to be + * transferred in the URB. Otherwise return DWC2_HC_XFER_URB_COMPLETE. + */ +static enum dwc2_halt_status dwc2_update_isoc_urb_state( +		struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan, +		int chnum, struct dwc2_qtd *qtd, +		enum dwc2_halt_status halt_status) +{ +	struct dwc2_hcd_iso_packet_desc *frame_desc; +	struct dwc2_hcd_urb *urb = qtd->urb; + +	if (!urb) +		return DWC2_HC_XFER_NO_HALT_STATUS; + +	frame_desc = &urb->iso_descs[qtd->isoc_frame_index]; + +	switch (halt_status) { +	case DWC2_HC_XFER_COMPLETE: +		frame_desc->status = 0; +		frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg, +					chan, chnum, qtd, halt_status, NULL); + +		/* Non DWORD-aligned buffer case handling */ +		if (chan->align_buf && frame_desc->actual_length && +		    chan->ep_is_in) { +			dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", +				 __func__); +			dma_sync_single_for_cpu(hsotg->dev, urb->dma, +						urb->length, DMA_FROM_DEVICE); +			memcpy(urb->buf + frame_desc->offset + +			       qtd->isoc_split_offset, chan->qh->dw_align_buf, +			       frame_desc->actual_length); +			dma_sync_single_for_device(hsotg->dev, urb->dma, +						   urb->length, +						   DMA_FROM_DEVICE); +		} +		break; +	case DWC2_HC_XFER_FRAME_OVERRUN: +		urb->error_count++; +		if (chan->ep_is_in) +			frame_desc->status = -ENOSR; +		else +			frame_desc->status = -ECOMM; +		frame_desc->actual_length = 0; +		break; +	case DWC2_HC_XFER_BABBLE_ERR: +		urb->error_count++; +		frame_desc->status = -EOVERFLOW; +		/* Don't need to update actual_length in this case */ +		break; +	case DWC2_HC_XFER_XACT_ERR: +		urb->error_count++; +		frame_desc->status = -EPROTO; +		frame_desc->actual_length = dwc2_get_actual_xfer_length(hsotg, +					chan, chnum, qtd, halt_status, NULL); + +		/* Non DWORD-aligned buffer case handling */ +		if (chan->align_buf && frame_desc->actual_length && +		    chan->ep_is_in) { +			dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", +				 __func__); +			dma_sync_single_for_cpu(hsotg->dev, urb->dma, +						urb->length, DMA_FROM_DEVICE); +			memcpy(urb->buf + frame_desc->offset + +			       qtd->isoc_split_offset, chan->qh->dw_align_buf, +			       frame_desc->actual_length); +			dma_sync_single_for_device(hsotg->dev, urb->dma, +						   urb->length, +						   DMA_FROM_DEVICE); +		} + +		/* Skip whole frame */ +		if (chan->qh->do_split && +		    chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in && +		    hsotg->core_params->dma_enable > 0) { +			qtd->complete_split = 0; +			qtd->isoc_split_offset = 0; +		} + +		break; +	default: +		dev_err(hsotg->dev, "Unhandled halt_status (%d)\n", +			halt_status); +		break; +	} + +	if (++qtd->isoc_frame_index == urb->packet_count) { +		/* +		 * urb->status is not used for isoc transfers. The individual +		 * frame_desc statuses are used instead. +		 */ +		dwc2_host_complete(hsotg, qtd, 0); +		halt_status = DWC2_HC_XFER_URB_COMPLETE; +	} else { +		halt_status = DWC2_HC_XFER_COMPLETE; +	} + +	return halt_status; +} + +/* + * 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 dwc2_deactivate_qh(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +			       int free_qtd) +{ +	int continue_split = 0; +	struct dwc2_qtd *qtd; + +	if (dbg_qh(qh)) +		dev_vdbg(hsotg->dev, "  %s(%p,%p,%d)\n", __func__, +			 hsotg, qh, free_qtd); + +	if (list_empty(&qh->qtd_list)) { +		dev_dbg(hsotg->dev, "## QTD list empty ##\n"); +		goto no_qtd; +	} + +	qtd = list_first_entry(&qh->qtd_list, struct dwc2_qtd, qtd_list_entry); + +	if (qtd->complete_split) +		continue_split = 1; +	else if (qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_MID || +		 qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_END) +		continue_split = 1; + +	if (free_qtd) { +		dwc2_hcd_qtd_unlink_and_free(hsotg, qtd, qh); +		continue_split = 0; +	} + +no_qtd: +	if (qh->channel) +		qh->channel->align_buf = 0; +	qh->channel = NULL; +	dwc2_hcd_qh_deactivate(hsotg, qh, continue_split); +} + +/** + * dwc2_release_channel() - Releases a host channel for use by other transfers + * + * @hsotg:       The HCD state structure + * @chan:        The host channel to release + * @qtd:         The QTD associated with the host channel. This QTD may be + *               freed if the transfer is complete or an error has occurred. + * @halt_status: Reason the channel is being released. This status + *               determines the actions taken by this function. + * + * Also attempts to select and queue more transactions since at least one host + * channel is available. + */ +static void dwc2_release_channel(struct dwc2_hsotg *hsotg, +				 struct dwc2_host_chan *chan, +				 struct dwc2_qtd *qtd, +				 enum dwc2_halt_status halt_status) +{ +	enum dwc2_transaction_type tr_type; +	u32 haintmsk; +	int free_qtd = 0; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "  %s: channel %d, halt_status %d\n", +			 __func__, chan->hc_num, halt_status); + +	switch (halt_status) { +	case DWC2_HC_XFER_URB_COMPLETE: +		free_qtd = 1; +		break; +	case DWC2_HC_XFER_AHB_ERR: +	case DWC2_HC_XFER_STALL: +	case DWC2_HC_XFER_BABBLE_ERR: +		free_qtd = 1; +		break; +	case DWC2_HC_XFER_XACT_ERR: +		if (qtd && qtd->error_count >= 3) { +			dev_vdbg(hsotg->dev, +				 "  Complete URB with transaction error\n"); +			free_qtd = 1; +			dwc2_host_complete(hsotg, qtd, -EPROTO); +		} +		break; +	case DWC2_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 DWC2_HC_XFER_PERIODIC_INCOMPLETE: +		dev_vdbg(hsotg->dev, "  Complete URB with I/O error\n"); +		free_qtd = 1; +		dwc2_host_complete(hsotg, qtd, -EIO); +		break; +	case DWC2_HC_XFER_NO_HALT_STATUS: +	default: +		break; +	} + +	dwc2_deactivate_qh(hsotg, chan->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. +	 */ +	if (!list_empty(&chan->hc_list_entry)) +		list_del(&chan->hc_list_entry); +	dwc2_hc_cleanup(hsotg, chan); +	list_add_tail(&chan->hc_list_entry, &hsotg->free_hc_list); + +	if (hsotg->core_params->uframe_sched > 0) { +		hsotg->available_host_channels++; +	} else { +		switch (chan->ep_type) { +		case USB_ENDPOINT_XFER_CONTROL: +		case USB_ENDPOINT_XFER_BULK: +			hsotg->non_periodic_channels--; +			break; +		default: +			/* +			 * Don't release reservations for periodic channels +			 * here. That's done when a periodic transfer is +			 * descheduled (i.e. when the QH is removed from the +			 * periodic schedule). +			 */ +			break; +		} +	} + +	haintmsk = readl(hsotg->regs + HAINTMSK); +	haintmsk &= ~(1 << chan->hc_num); +	writel(haintmsk, hsotg->regs + HAINTMSK); + +	/* Try to queue more transfers now that there's a free channel */ +	tr_type = dwc2_hcd_select_transactions(hsotg); +	if (tr_type != DWC2_TRANSACTION_NONE) +		dwc2_hcd_queue_transactions(hsotg, 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 dwc2_halt_channel(struct dwc2_hsotg *hsotg, +			      struct dwc2_host_chan *chan, struct dwc2_qtd *qtd, +			      enum dwc2_halt_status halt_status) +{ +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	if (hsotg->core_params->dma_enable > 0) { +		if (dbg_hc(chan)) +			dev_vdbg(hsotg->dev, "DMA enabled\n"); +		dwc2_release_channel(hsotg, chan, qtd, halt_status); +		return; +	} + +	/* Slave mode processing */ +	dwc2_hc_halt(hsotg, chan, halt_status); + +	if (chan->halt_on_queue) { +		u32 gintmsk; + +		dev_vdbg(hsotg->dev, "Halt on queue\n"); +		if (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || +		    chan->ep_type == USB_ENDPOINT_XFER_BULK) { +			dev_vdbg(hsotg->dev, "control/bulk\n"); +			/* +			 * Make sure the Non-periodic Tx FIFO empty interrupt +			 * is enabled so that the non-periodic schedule will +			 * be processed +			 */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk |= GINTSTS_NPTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} else { +			dev_vdbg(hsotg->dev, "isoc/intr\n"); +			/* +			 * 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(&chan->qh->qh_list_entry, +				  &hsotg->periodic_sched_assigned); + +			/* +			 * Make sure the Periodic Tx FIFO Empty interrupt is +			 * enabled so that the periodic schedule will be +			 * processed +			 */ +			gintmsk = readl(hsotg->regs + GINTMSK); +			gintmsk |= GINTSTS_PTXFEMP; +			writel(gintmsk, hsotg->regs + GINTMSK); +		} +	} +} + +/* + * Performs common cleanup for non-periodic transfers after a Transfer + * Complete interrupt. This function should be called after any endpoint type + * specific handling is finished to release the host channel. + */ +static void dwc2_complete_non_periodic_xfer(struct dwc2_hsotg *hsotg, +					    struct dwc2_host_chan *chan, +					    int chnum, struct dwc2_qtd *qtd, +					    enum dwc2_halt_status halt_status) +{ +	dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	qtd->error_count = 0; + +	if (chan->hcint & HCINTMSK_NYET) { +		/* +		 * 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. +		 */ +		dev_vdbg(hsotg->dev, "got NYET\n"); +		chan->qh->ping_state = 1; +	} + +	/* +	 * 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 dwc2_hcd_queue_transactions, which also enables the +	 * Tx FIFO Empty interrupt if necessary. +	 */ +	if (chan->ep_is_in) { +		/* +		 * IN transfers in Slave mode require an explicit disable to +		 * halt the channel. (In DMA mode, this call simply releases +		 * the channel.) +		 */ +		dwc2_halt_channel(hsotg, chan, qtd, halt_status); +	} else { +		/* +		 * The channel is automatically disabled by the core for OUT +		 * transfers in Slave mode +		 */ +		dwc2_release_channel(hsotg, chan, qtd, halt_status); +	} +} + +/* + * 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 dwc2_complete_periodic_xfer(struct dwc2_hsotg *hsotg, +					struct dwc2_host_chan *chan, int chnum, +					struct dwc2_qtd *qtd, +					enum dwc2_halt_status halt_status) +{ +	u32 hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); + +	qtd->error_count = 0; + +	if (!chan->ep_is_in || (hctsiz & TSIZ_PKTCNT_MASK) == 0) +		/* Core halts channel in these cases */ +		dwc2_release_channel(hsotg, chan, qtd, halt_status); +	else +		/* Flush any outstanding requests from the Tx queue */ +		dwc2_halt_channel(hsotg, chan, qtd, halt_status); +} + +static int dwc2_xfercomp_isoc_split_in(struct dwc2_hsotg *hsotg, +				       struct dwc2_host_chan *chan, int chnum, +				       struct dwc2_qtd *qtd) +{ +	struct dwc2_hcd_iso_packet_desc *frame_desc; +	u32 len; + +	if (!qtd->urb) +		return 0; + +	frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index]; +	len = dwc2_get_actual_xfer_length(hsotg, chan, chnum, qtd, +					  DWC2_HC_XFER_COMPLETE, NULL); +	if (!len) { +		qtd->complete_split = 0; +		qtd->isoc_split_offset = 0; +		return 0; +	} + +	frame_desc->actual_length += len; + +	if (chan->align_buf) { +		dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); +		dma_sync_single_for_cpu(hsotg->dev, qtd->urb->dma, +					qtd->urb->length, DMA_FROM_DEVICE); +		memcpy(qtd->urb->buf + frame_desc->offset + +		       qtd->isoc_split_offset, chan->qh->dw_align_buf, len); +		dma_sync_single_for_device(hsotg->dev, qtd->urb->dma, +					   qtd->urb->length, DMA_FROM_DEVICE); +	} + +	qtd->isoc_split_offset += len; + +	if (frame_desc->actual_length >= frame_desc->length) { +		frame_desc->status = 0; +		qtd->isoc_frame_index++; +		qtd->complete_split = 0; +		qtd->isoc_split_offset = 0; +	} + +	if (qtd->isoc_frame_index == qtd->urb->packet_count) { +		dwc2_host_complete(hsotg, qtd, 0); +		dwc2_release_channel(hsotg, chan, qtd, +				     DWC2_HC_XFER_URB_COMPLETE); +	} else { +		dwc2_release_channel(hsotg, chan, qtd, +				     DWC2_HC_XFER_NO_HALT_STATUS); +	} + +	return 1;	/* Indicates that channel released */ +} + +/* + * Handles a host channel Transfer Complete interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static void dwc2_hc_xfercomp_intr(struct dwc2_hsotg *hsotg, +				  struct dwc2_host_chan *chan, int chnum, +				  struct dwc2_qtd *qtd) +{ +	struct dwc2_hcd_urb *urb = qtd->urb; +	enum dwc2_halt_status halt_status = DWC2_HC_XFER_COMPLETE; +	int pipe_type; +	int urb_xfer_done; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, +			 "--Host Channel %d Interrupt: Transfer Complete--\n", +			 chnum); + +	if (!urb) +		goto handle_xfercomp_done; + +	pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + +	if (hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, halt_status); +		if (pipe_type == USB_ENDPOINT_XFER_ISOC) +			/* Do not disable the interrupt, just clear it */ +			return; +		goto handle_xfercomp_done; +	} + +	/* Handle xfer complete on CSPLIT */ +	if (chan->qh->do_split) { +		if (chan->ep_type == USB_ENDPOINT_XFER_ISOC && chan->ep_is_in && +		    hsotg->core_params->dma_enable > 0) { +			if (qtd->complete_split && +			    dwc2_xfercomp_isoc_split_in(hsotg, chan, chnum, +							qtd)) +				goto handle_xfercomp_done; +		} else { +			qtd->complete_split = 0; +		} +	} + +	/* Update the QTD and URB states */ +	switch (pipe_type) { +	case USB_ENDPOINT_XFER_CONTROL: +		switch (qtd->control_phase) { +		case DWC2_CONTROL_SETUP: +			if (urb->length > 0) +				qtd->control_phase = DWC2_CONTROL_DATA; +			else +				qtd->control_phase = DWC2_CONTROL_STATUS; +			dev_vdbg(hsotg->dev, +				 "  Control setup transaction done\n"); +			halt_status = DWC2_HC_XFER_COMPLETE; +			break; +		case DWC2_CONTROL_DATA: +			urb_xfer_done = dwc2_update_urb_state(hsotg, chan, +							      chnum, urb, qtd); +			if (urb_xfer_done) { +				qtd->control_phase = DWC2_CONTROL_STATUS; +				dev_vdbg(hsotg->dev, +					 "  Control data transfer done\n"); +			} else { +				dwc2_hcd_save_data_toggle(hsotg, chan, chnum, +							  qtd); +			} +			halt_status = DWC2_HC_XFER_COMPLETE; +			break; +		case DWC2_CONTROL_STATUS: +			dev_vdbg(hsotg->dev, "  Control transfer complete\n"); +			if (urb->status == -EINPROGRESS) +				urb->status = 0; +			dwc2_host_complete(hsotg, qtd, urb->status); +			halt_status = DWC2_HC_XFER_URB_COMPLETE; +			break; +		} + +		dwc2_complete_non_periodic_xfer(hsotg, chan, chnum, qtd, +						halt_status); +		break; +	case USB_ENDPOINT_XFER_BULK: +		dev_vdbg(hsotg->dev, "  Bulk transfer complete\n"); +		urb_xfer_done = dwc2_update_urb_state(hsotg, chan, chnum, urb, +						      qtd); +		if (urb_xfer_done) { +			dwc2_host_complete(hsotg, qtd, urb->status); +			halt_status = DWC2_HC_XFER_URB_COMPLETE; +		} else { +			halt_status = DWC2_HC_XFER_COMPLETE; +		} + +		dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); +		dwc2_complete_non_periodic_xfer(hsotg, chan, chnum, qtd, +						halt_status); +		break; +	case USB_ENDPOINT_XFER_INT: +		dev_vdbg(hsotg->dev, "  Interrupt transfer complete\n"); +		urb_xfer_done = dwc2_update_urb_state(hsotg, chan, chnum, urb, +						      qtd); + +		/* +		 * Interrupt URB is done on the first transfer complete +		 * interrupt +		 */ +		if (urb_xfer_done) { +			dwc2_host_complete(hsotg, qtd, urb->status); +			halt_status = DWC2_HC_XFER_URB_COMPLETE; +		} else { +			halt_status = DWC2_HC_XFER_COMPLETE; +		} + +		dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); +		dwc2_complete_periodic_xfer(hsotg, chan, chnum, qtd, +					    halt_status); +		break; +	case USB_ENDPOINT_XFER_ISOC: +		if (dbg_perio()) +			dev_vdbg(hsotg->dev, "  Isochronous transfer complete\n"); +		if (qtd->isoc_split_pos == DWC2_HCSPLT_XACTPOS_ALL) +			halt_status = dwc2_update_isoc_urb_state(hsotg, chan, +					chnum, qtd, DWC2_HC_XFER_COMPLETE); +		dwc2_complete_periodic_xfer(hsotg, chan, chnum, qtd, +					    halt_status); +		break; +	} + +handle_xfercomp_done: +	disable_hc_int(hsotg, chnum, HCINTMSK_XFERCOMPL); +} + +/* + * Handles a host channel STALL interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static void dwc2_hc_stall_intr(struct dwc2_hsotg *hsotg, +			       struct dwc2_host_chan *chan, int chnum, +			       struct dwc2_qtd *qtd) +{ +	struct dwc2_hcd_urb *urb = qtd->urb; +	int pipe_type; + +	dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: STALL Received--\n", +		chnum); + +	if (hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, +					    DWC2_HC_XFER_STALL); +		goto handle_stall_done; +	} + +	if (!urb) +		goto handle_stall_halt; + +	pipe_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); + +	if (pipe_type == USB_ENDPOINT_XFER_CONTROL) +		dwc2_host_complete(hsotg, qtd, -EPIPE); + +	if (pipe_type == USB_ENDPOINT_XFER_BULK || +	    pipe_type == USB_ENDPOINT_XFER_INT) { +		dwc2_host_complete(hsotg, qtd, -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. +		 */ +		chan->qh->data_toggle = 0; +	} + +handle_stall_halt: +	dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_STALL); + +handle_stall_done: +	disable_hc_int(hsotg, chnum, HCINTMSK_STALL); +} + +/* + * 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 dwc2_update_urb_state_abn(struct dwc2_hsotg *hsotg, +				      struct dwc2_host_chan *chan, int chnum, +				      struct dwc2_hcd_urb *urb, +				      struct dwc2_qtd *qtd, +				      enum dwc2_halt_status halt_status) +{ +	u32 xfer_length = dwc2_get_actual_xfer_length(hsotg, chan, chnum, +						      qtd, halt_status, NULL); +	u32 hctsiz; + +	if (urb->actual_length + xfer_length > urb->length) { +		dev_warn(hsotg->dev, "%s(): trimming xfer length\n", __func__); +		xfer_length = urb->length - urb->actual_length; +	} + +	/* Non DWORD-aligned buffer case handling */ +	if (chan->align_buf && xfer_length && chan->ep_is_in) { +		dev_vdbg(hsotg->dev, "%s(): non-aligned buffer\n", __func__); +		dma_sync_single_for_cpu(hsotg->dev, urb->dma, urb->length, +					DMA_FROM_DEVICE); +		memcpy(urb->buf + urb->actual_length, chan->qh->dw_align_buf, +		       xfer_length); +		dma_sync_single_for_device(hsotg->dev, urb->dma, urb->length, +					   DMA_FROM_DEVICE); +	} + +	urb->actual_length += xfer_length; + +	hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); +	dev_vdbg(hsotg->dev, "DWC_otg: %s: %s, channel %d\n", +		 __func__, (chan->ep_is_in ? "IN" : "OUT"), chnum); +	dev_vdbg(hsotg->dev, "  chan->start_pkt_count %d\n", +		 chan->start_pkt_count); +	dev_vdbg(hsotg->dev, "  hctsiz.pktcnt %d\n", +		 (hctsiz & TSIZ_PKTCNT_MASK) >> TSIZ_PKTCNT_SHIFT); +	dev_vdbg(hsotg->dev, "  chan->max_packet %d\n", chan->max_packet); +	dev_vdbg(hsotg->dev, "  bytes_transferred %d\n", +		 xfer_length); +	dev_vdbg(hsotg->dev, "  urb->actual_length %d\n", +		 urb->actual_length); +	dev_vdbg(hsotg->dev, "  urb->transfer_buffer_length %d\n", +		 urb->length); +} + +/* + * Handles a host channel NAK interrupt. This handler may be called in either + * DMA mode or Slave mode. + */ +static void dwc2_hc_nak_intr(struct dwc2_hsotg *hsotg, +			     struct dwc2_host_chan *chan, int chnum, +			     struct dwc2_qtd *qtd) +{ +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: NAK Received--\n", +			 chnum); + +	/* +	 * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and +	 * interrupt. Re-start the SSPLIT transfer. +	 */ +	if (chan->do_split) { +		if (chan->complete_split) +			qtd->error_count = 0; +		qtd->complete_split = 0; +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK); +		goto handle_nak_done; +	} + +	switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { +	case USB_ENDPOINT_XFER_CONTROL: +	case USB_ENDPOINT_XFER_BULK: +		if (hsotg->core_params->dma_enable > 0 && chan->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; +			break; +		} + +		/* +		 * 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 (!chan->qh->ping_state) { +			dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, +						  qtd, DWC2_HC_XFER_NAK); +			dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + +			if (chan->speed == USB_SPEED_HIGH) +				chan->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 +		 */ +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK); +		break; +	case USB_ENDPOINT_XFER_INT: +		qtd->error_count = 0; +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NAK); +		break; +	case USB_ENDPOINT_XFER_ISOC: +		/* Should never get called for isochronous transfers */ +		dev_err(hsotg->dev, "NACK interrupt for ISOC transfer\n"); +		break; +	} + +handle_nak_done: +	disable_hc_int(hsotg, chnum, HCINTMSK_NAK); +} + +/* + * 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 void dwc2_hc_ack_intr(struct dwc2_hsotg *hsotg, +			     struct dwc2_host_chan *chan, int chnum, +			     struct dwc2_qtd *qtd) +{ +	struct dwc2_hcd_iso_packet_desc *frame_desc; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: ACK Received--\n", +			 chnum); + +	if (chan->do_split) { +		/* Handle ACK on SSPLIT. ACK should not occur in CSPLIT. */ +		if (!chan->ep_is_in && +		    chan->data_pid_start != DWC2_HC_PID_SETUP) +			qtd->ssplit_out_xfer_count = chan->xfer_len; + +		if (chan->ep_type != USB_ENDPOINT_XFER_ISOC || chan->ep_is_in) { +			qtd->complete_split = 1; +			dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_ACK); +		} else { +			/* ISOC OUT */ +			switch (chan->xact_pos) { +			case DWC2_HCSPLT_XACTPOS_ALL: +				break; +			case DWC2_HCSPLT_XACTPOS_END: +				qtd->isoc_split_pos = DWC2_HCSPLT_XACTPOS_ALL; +				qtd->isoc_split_offset = 0; +				break; +			case DWC2_HCSPLT_XACTPOS_BEGIN: +			case DWC2_HCSPLT_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_descs[ +						qtd->isoc_frame_index]; +				qtd->isoc_split_offset += 188; + +				if (frame_desc->length - qtd->isoc_split_offset +							<= 188) +					qtd->isoc_split_pos = +							DWC2_HCSPLT_XACTPOS_END; +				else +					qtd->isoc_split_pos = +							DWC2_HCSPLT_XACTPOS_MID; +				break; +			} +		} +	} else { +		qtd->error_count = 0; + +		if (chan->qh->ping_state) { +			chan->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. +			 */ +			dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_ACK); +		} +	} + +	/* +	 * If the ACK occurred when _not_ in the PING state, let the channel +	 * continue transferring data after clearing the error count +	 */ +	disable_hc_int(hsotg, chnum, HCINTMSK_ACK); +} + +/* + * 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 void dwc2_hc_nyet_intr(struct dwc2_hsotg *hsotg, +			      struct dwc2_host_chan *chan, int chnum, +			      struct dwc2_qtd *qtd) +{ +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: NYET Received--\n", +			 chnum); + +	/* +	 * NYET on CSPLIT +	 * re-do the CSPLIT immediately on non-periodic +	 */ +	if (chan->do_split && chan->complete_split) { +		if (chan->ep_is_in && chan->ep_type == USB_ENDPOINT_XFER_ISOC && +		    hsotg->core_params->dma_enable > 0) { +			qtd->complete_split = 0; +			qtd->isoc_split_offset = 0; +			qtd->isoc_frame_index++; +			if (qtd->urb && +			    qtd->isoc_frame_index == qtd->urb->packet_count) { +				dwc2_host_complete(hsotg, qtd, 0); +				dwc2_release_channel(hsotg, chan, qtd, +						     DWC2_HC_XFER_URB_COMPLETE); +			} else { +				dwc2_release_channel(hsotg, chan, qtd, +						DWC2_HC_XFER_NO_HALT_STATUS); +			} +			goto handle_nyet_done; +		} + +		if (chan->ep_type == USB_ENDPOINT_XFER_INT || +		    chan->ep_type == USB_ENDPOINT_XFER_ISOC) { +			int frnum = dwc2_hcd_get_frame_number(hsotg); + +			if (dwc2_full_frame_num(frnum) != +			    dwc2_full_frame_num(chan->qh->sched_frame)) { +				/* +				 * No longer in the same full speed frame. +				 * Treat this as a transaction error. +				 */ +#if 0 +				/* +				 * Todo: Fix system performance so this can +				 * be treated as an error. Right now complete +				 * splits cannot be scheduled precisely enough +				 * due to other system activity, so this error +				 * occurs regularly in Slave mode. +				 */ +				qtd->error_count++; +#endif +				qtd->complete_split = 0; +				dwc2_halt_channel(hsotg, chan, qtd, +						  DWC2_HC_XFER_XACT_ERR); +				/* Todo: add support for isoc release */ +				goto handle_nyet_done; +			} +		} + +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NYET); +		goto handle_nyet_done; +	} + +	chan->qh->ping_state = 1; +	qtd->error_count = 0; + +	dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, qtd, +				  DWC2_HC_XFER_NYET); +	dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); + +	/* +	 * Halt the channel and re-start the transfer so the PING protocol +	 * will start +	 */ +	dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_NYET); + +handle_nyet_done: +	disable_hc_int(hsotg, chnum, HCINTMSK_NYET); +} + +/* + * Handles a host channel babble interrupt. This handler may be called in + * either DMA mode or Slave mode. + */ +static void dwc2_hc_babble_intr(struct dwc2_hsotg *hsotg, +				struct dwc2_host_chan *chan, int chnum, +				struct dwc2_qtd *qtd) +{ +	dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: Babble Error--\n", +		chnum); + +	dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + +	if (hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, +					    DWC2_HC_XFER_BABBLE_ERR); +		goto disable_int; +	} + +	if (chan->ep_type != USB_ENDPOINT_XFER_ISOC) { +		dwc2_host_complete(hsotg, qtd, -EOVERFLOW); +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_BABBLE_ERR); +	} else { +		enum dwc2_halt_status halt_status; + +		halt_status = dwc2_update_isoc_urb_state(hsotg, chan, chnum, +						qtd, DWC2_HC_XFER_BABBLE_ERR); +		dwc2_halt_channel(hsotg, chan, qtd, halt_status); +	} + +disable_int: +	disable_hc_int(hsotg, chnum, HCINTMSK_BBLERR); +} + +/* + * Handles a host channel AHB error interrupt. This handler is only called in + * DMA mode. + */ +static void dwc2_hc_ahberr_intr(struct dwc2_hsotg *hsotg, +				struct dwc2_host_chan *chan, int chnum, +				struct dwc2_qtd *qtd) +{ +	struct dwc2_hcd_urb *urb = qtd->urb; +	char *pipetype, *speed; +	u32 hcchar; +	u32 hcsplt; +	u32 hctsiz; +	u32 hc_dma; + +	dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: AHB Error--\n", +		chnum); + +	if (!urb) +		goto handle_ahberr_halt; + +	dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + +	hcchar = readl(hsotg->regs + HCCHAR(chnum)); +	hcsplt = readl(hsotg->regs + HCSPLT(chnum)); +	hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); +	hc_dma = readl(hsotg->regs + HCDMA(chnum)); + +	dev_err(hsotg->dev, "AHB ERROR, Channel %d\n", chnum); +	dev_err(hsotg->dev, "  hcchar 0x%08x, hcsplt 0x%08x\n", hcchar, hcsplt); +	dev_err(hsotg->dev, "  hctsiz 0x%08x, hc_dma 0x%08x\n", hctsiz, hc_dma); +	dev_err(hsotg->dev, "  Device address: %d\n", +		dwc2_hcd_get_dev_addr(&urb->pipe_info)); +	dev_err(hsotg->dev, "  Endpoint: %d, %s\n", +		dwc2_hcd_get_ep_num(&urb->pipe_info), +		dwc2_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"); + +	switch (dwc2_hcd_get_pipe_type(&urb->pipe_info)) { +	case USB_ENDPOINT_XFER_CONTROL: +		pipetype = "CONTROL"; +		break; +	case USB_ENDPOINT_XFER_BULK: +		pipetype = "BULK"; +		break; +	case USB_ENDPOINT_XFER_INT: +		pipetype = "INTERRUPT"; +		break; +	case USB_ENDPOINT_XFER_ISOC: +		pipetype = "ISOCHRONOUS"; +		break; +	default: +		pipetype = "UNKNOWN"; +		break; +	} + +	dev_err(hsotg->dev, "  Endpoint type: %s\n", pipetype); + +	switch (chan->speed) { +	case USB_SPEED_HIGH: +		speed = "HIGH"; +		break; +	case USB_SPEED_FULL: +		speed = "FULL"; +		break; +	case USB_SPEED_LOW: +		speed = "LOW"; +		break; +	default: +		speed = "UNKNOWN"; +		break; +	} + +	dev_err(hsotg->dev, "  Speed: %s\n", speed); + +	dev_err(hsotg->dev, "  Max packet size: %d\n", +		dwc2_hcd_get_mps(&urb->pipe_info)); +	dev_err(hsotg->dev, "  Data buffer length: %d\n", urb->length); +	dev_err(hsotg->dev, "  Transfer buffer: %p, Transfer DMA: %08lx\n", +		urb->buf, (unsigned long)urb->dma); +	dev_err(hsotg->dev, "  Setup buffer: %p, Setup DMA: %08lx\n", +		urb->setup_packet, (unsigned long)urb->setup_dma); +	dev_err(hsotg->dev, "  Interval: %d\n", urb->interval); + +	/* Core halts the channel for Descriptor DMA mode */ +	if (hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, +					    DWC2_HC_XFER_AHB_ERR); +		goto handle_ahberr_done; +	} + +	dwc2_host_complete(hsotg, qtd, -EIO); + +handle_ahberr_halt: +	/* +	 * Force a channel halt. Don't call dwc2_halt_channel because that won't +	 * write to the HCCHARn register in DMA mode to force the halt. +	 */ +	dwc2_hc_halt(hsotg, chan, DWC2_HC_XFER_AHB_ERR); + +handle_ahberr_done: +	disable_hc_int(hsotg, chnum, HCINTMSK_AHBERR); +} + +/* + * Handles a host channel transaction error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static void dwc2_hc_xacterr_intr(struct dwc2_hsotg *hsotg, +				 struct dwc2_host_chan *chan, int chnum, +				 struct dwc2_qtd *qtd) +{ +	dev_dbg(hsotg->dev, +		"--Host Channel %d Interrupt: Transaction Error--\n", chnum); + +	dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + +	if (hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, +					    DWC2_HC_XFER_XACT_ERR); +		goto handle_xacterr_done; +	} + +	switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { +	case USB_ENDPOINT_XFER_CONTROL: +	case USB_ENDPOINT_XFER_BULK: +		qtd->error_count++; +		if (!chan->qh->ping_state) { + +			dwc2_update_urb_state_abn(hsotg, chan, chnum, qtd->urb, +						  qtd, DWC2_HC_XFER_XACT_ERR); +			dwc2_hcd_save_data_toggle(hsotg, chan, chnum, qtd); +			if (!chan->ep_is_in && chan->speed == USB_SPEED_HIGH) +				chan->qh->ping_state = 1; +		} + +		/* +		 * Halt the channel so the transfer can be re-started from +		 * the appropriate point or the PING protocol will start +		 */ +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR); +		break; +	case USB_ENDPOINT_XFER_INT: +		qtd->error_count++; +		if (chan->do_split && chan->complete_split) +			qtd->complete_split = 0; +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_XACT_ERR); +		break; +	case USB_ENDPOINT_XFER_ISOC: +		{ +			enum dwc2_halt_status halt_status; + +			halt_status = dwc2_update_isoc_urb_state(hsotg, chan, +					chnum, qtd, DWC2_HC_XFER_XACT_ERR); +			dwc2_halt_channel(hsotg, chan, qtd, halt_status); +		} +		break; +	} + +handle_xacterr_done: +	disable_hc_int(hsotg, chnum, HCINTMSK_XACTERR); +} + +/* + * Handles a host channel frame overrun interrupt. This handler may be called + * in either DMA mode or Slave mode. + */ +static void dwc2_hc_frmovrun_intr(struct dwc2_hsotg *hsotg, +				  struct dwc2_host_chan *chan, int chnum, +				  struct dwc2_qtd *qtd) +{ +	enum dwc2_halt_status halt_status; + +	if (dbg_hc(chan)) +		dev_dbg(hsotg->dev, "--Host Channel %d Interrupt: Frame Overrun--\n", +			chnum); + +	dwc2_hc_handle_tt_clear(hsotg, chan, qtd); + +	switch (dwc2_hcd_get_pipe_type(&qtd->urb->pipe_info)) { +	case USB_ENDPOINT_XFER_CONTROL: +	case USB_ENDPOINT_XFER_BULK: +		break; +	case USB_ENDPOINT_XFER_INT: +		dwc2_halt_channel(hsotg, chan, qtd, DWC2_HC_XFER_FRAME_OVERRUN); +		break; +	case USB_ENDPOINT_XFER_ISOC: +		halt_status = dwc2_update_isoc_urb_state(hsotg, chan, chnum, +					qtd, DWC2_HC_XFER_FRAME_OVERRUN); +		dwc2_halt_channel(hsotg, chan, qtd, halt_status); +		break; +	} + +	disable_hc_int(hsotg, chnum, HCINTMSK_FRMOVRUN); +} + +/* + * Handles a host channel data toggle error interrupt. This handler may be + * called in either DMA mode or Slave mode. + */ +static void dwc2_hc_datatglerr_intr(struct dwc2_hsotg *hsotg, +				    struct dwc2_host_chan *chan, int chnum, +				    struct dwc2_qtd *qtd) +{ +	dev_dbg(hsotg->dev, +		"--Host Channel %d Interrupt: Data Toggle Error--\n", chnum); + +	if (chan->ep_is_in) +		qtd->error_count = 0; +	else +		dev_err(hsotg->dev, +			"Data Toggle Error on OUT transfer, channel %d\n", +			chnum); + +	dwc2_hc_handle_tt_clear(hsotg, chan, qtd); +	disable_hc_int(hsotg, chnum, HCINTMSK_DATATGLERR); +} + +/* + * For debug only. It checks that a valid halt status is set and that + * HCCHARn.chdis is clear. If there's a problem, corrective action is + * taken and a warning is issued. + * + * Return: true if halt status is ok, false otherwise + */ +static bool dwc2_halt_status_ok(struct dwc2_hsotg *hsotg, +				struct dwc2_host_chan *chan, int chnum, +				struct dwc2_qtd *qtd) +{ +#ifdef DEBUG +	u32 hcchar; +	u32 hctsiz; +	u32 hcintmsk; +	u32 hcsplt; + +	if (chan->halt_status == DWC2_HC_XFER_NO_HALT_STATUS) { +		/* +		 * This code is here only as a check. This condition should +		 * never happen. Ignore the halt if it does occur. +		 */ +		hcchar = readl(hsotg->regs + HCCHAR(chnum)); +		hctsiz = readl(hsotg->regs + HCTSIZ(chnum)); +		hcintmsk = readl(hsotg->regs + HCINTMSK(chnum)); +		hcsplt = readl(hsotg->regs + HCSPLT(chnum)); +		dev_dbg(hsotg->dev, +			"%s: chan->halt_status DWC2_HC_XFER_NO_HALT_STATUS,\n", +			 __func__); +		dev_dbg(hsotg->dev, +			"channel %d, hcchar 0x%08x, hctsiz 0x%08x,\n", +			chnum, hcchar, hctsiz); +		dev_dbg(hsotg->dev, +			"hcint 0x%08x, hcintmsk 0x%08x, hcsplt 0x%08x,\n", +			chan->hcint, hcintmsk, hcsplt); +		if (qtd) +			dev_dbg(hsotg->dev, "qtd->complete_split %d\n", +				qtd->complete_split); +		dev_warn(hsotg->dev, +			 "%s: no halt status, channel %d, ignoring interrupt\n", +			 __func__, chnum); +		return false; +	} + +	/* +	 * This code is here only as a check. hcchar.chdis should never be set +	 * when the halt interrupt occurs. Halt the channel again if it does +	 * occur. +	 */ +	hcchar = readl(hsotg->regs + HCCHAR(chnum)); +	if (hcchar & HCCHAR_CHDIS) { +		dev_warn(hsotg->dev, +			 "%s: hcchar.chdis set unexpectedly, hcchar 0x%08x, trying to halt again\n", +			 __func__, hcchar); +		chan->halt_pending = 0; +		dwc2_halt_channel(hsotg, chan, qtd, chan->halt_status); +		return false; +	} +#endif + +	return true; +} + +/* + * Handles a host Channel Halted interrupt in DMA mode. This handler + * determines the reason the channel halted and proceeds accordingly. + */ +static void dwc2_hc_chhltd_intr_dma(struct dwc2_hsotg *hsotg, +				    struct dwc2_host_chan *chan, int chnum, +				    struct dwc2_qtd *qtd) +{ +	u32 hcintmsk; +	int out_nak_enh = 0; + +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, +			 "--Host Channel %d Interrupt: DMA Channel Halted--\n", +			 chnum); + +	/* +	 * For core with OUT NAK enhancement, the flow for high-speed +	 * CONTROL/BULK OUT is handled a little differently +	 */ +	if (hsotg->hw_params.snpsid >= DWC2_CORE_REV_2_71a) { +		if (chan->speed == USB_SPEED_HIGH && !chan->ep_is_in && +		    (chan->ep_type == USB_ENDPOINT_XFER_CONTROL || +		     chan->ep_type == USB_ENDPOINT_XFER_BULK)) { +			out_nak_enh = 1; +		} +	} + +	if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE || +	    (chan->halt_status == DWC2_HC_XFER_AHB_ERR && +	     hsotg->core_params->dma_desc_enable <= 0)) { +		if (hsotg->core_params->dma_desc_enable > 0) +			dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, +						    chan->halt_status); +		else +			/* +			 * 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. +			 */ +			dwc2_release_channel(hsotg, chan, qtd, +					     chan->halt_status); +		return; +	} + +	hcintmsk = readl(hsotg->regs + HCINTMSK(chnum)); + +	if (chan->hcint & HCINTMSK_XFERCOMPL) { +		/* +		 * Todo: 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 (chan->ep_type == USB_ENDPOINT_XFER_ISOC && !chan->ep_is_in) +			dwc2_hc_ack_intr(hsotg, chan, chnum, qtd); +		dwc2_hc_xfercomp_intr(hsotg, chan, chnum, qtd); +	} else if (chan->hcint & HCINTMSK_STALL) { +		dwc2_hc_stall_intr(hsotg, chan, chnum, qtd); +	} else if ((chan->hcint & HCINTMSK_XACTERR) && +		   hsotg->core_params->dma_desc_enable <= 0) { +		if (out_nak_enh) { +			if (chan->hcint & +			    (HCINTMSK_NYET | HCINTMSK_NAK | HCINTMSK_ACK)) { +				dev_vdbg(hsotg->dev, +					 "XactErr with NYET/NAK/ACK\n"); +				qtd->error_count = 0; +			} else { +				dev_vdbg(hsotg->dev, +					 "XactErr without NYET/NAK/ACK\n"); +			} +		} + +		/* +		 * 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. +		 */ +		dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); +	} else if ((chan->hcint & HCINTMSK_XCS_XACT) && +		   hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); +	} else if ((chan->hcint & HCINTMSK_AHBERR) && +		   hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hc_ahberr_intr(hsotg, chan, chnum, qtd); +	} else if (chan->hcint & HCINTMSK_BBLERR) { +		dwc2_hc_babble_intr(hsotg, chan, chnum, qtd); +	} else if (chan->hcint & HCINTMSK_FRMOVRUN) { +		dwc2_hc_frmovrun_intr(hsotg, chan, chnum, qtd); +	} else if (!out_nak_enh) { +		if (chan->hcint & HCINTMSK_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. +			 */ +			dwc2_hc_nyet_intr(hsotg, chan, chnum, qtd); +		} else if ((chan->hcint & HCINTMSK_NAK) && +			   !(hcintmsk & HCINTMSK_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. +			 */ +			dwc2_hc_nak_intr(hsotg, chan, chnum, qtd); +		} else if ((chan->hcint & HCINTMSK_ACK) && +			   !(hcintmsk & HCINTMSK_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. +			 */ +			dwc2_hc_ack_intr(hsotg, chan, chnum, qtd); +		} else { +			if (chan->ep_type == USB_ENDPOINT_XFER_INT || +			    chan->ep_type == USB_ENDPOINT_XFER_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. +				 */ +				dev_dbg(hsotg->dev, +					"%s: Halt channel %d (assume incomplete periodic transfer)\n", +					__func__, chnum); +				dwc2_halt_channel(hsotg, chan, qtd, +					DWC2_HC_XFER_PERIODIC_INCOMPLETE); +			} else { +				dev_err(hsotg->dev, +					"%s: Channel %d - ChHltd set, but reason is unknown\n", +					__func__, chnum); +				dev_err(hsotg->dev, +					"hcint 0x%08x, intsts 0x%08x\n", +					chan->hcint, +					readl(hsotg->regs + GINTSTS)); +			} +		} +	} else { +		dev_info(hsotg->dev, +			 "NYET/NAK/ACK/other in non-error case, 0x%08x\n", +			 chan->hcint); +	} +} + +/* + * 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 void dwc2_hc_chhltd_intr(struct dwc2_hsotg *hsotg, +				struct dwc2_host_chan *chan, int chnum, +				struct dwc2_qtd *qtd) +{ +	if (dbg_hc(chan)) +		dev_vdbg(hsotg->dev, "--Host Channel %d Interrupt: Channel Halted--\n", +			 chnum); + +	if (hsotg->core_params->dma_enable > 0) { +		dwc2_hc_chhltd_intr_dma(hsotg, chan, chnum, qtd); +	} else { +		if (!dwc2_halt_status_ok(hsotg, chan, chnum, qtd)) +			return; +		dwc2_release_channel(hsotg, chan, qtd, chan->halt_status); +	} +} + +/* Handles interrupt for a specific Host Channel */ +static void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum) +{ +	struct dwc2_qtd *qtd; +	struct dwc2_host_chan *chan; +	u32 hcint, hcintmsk; + +	chan = hsotg->hc_ptr_array[chnum]; + +	hcint = readl(hsotg->regs + HCINT(chnum)); +	hcintmsk = readl(hsotg->regs + HCINTMSK(chnum)); +	if (!chan) { +		dev_err(hsotg->dev, "## hc_ptr_array for channel is NULL ##\n"); +		writel(hcint, hsotg->regs + HCINT(chnum)); +		return; +	} + +	if (dbg_hc(chan)) { +		dev_vdbg(hsotg->dev, "--Host Channel Interrupt--, Channel %d\n", +			 chnum); +		dev_vdbg(hsotg->dev, +			 "  hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", +			 hcint, hcintmsk, hcint & hcintmsk); +	} + +	writel(hcint, hsotg->regs + HCINT(chnum)); +	chan->hcint = hcint; +	hcint &= hcintmsk; + +	/* +	 * If the channel was halted due to a dequeue, the qtd list might +	 * be empty or at least the first entry will not be the active qtd. +	 * In this case, take a shortcut and just release the channel. +	 */ +	if (chan->halt_status == DWC2_HC_XFER_URB_DEQUEUE) { +		/* +		 * If the channel was halted, this should be the only +		 * interrupt unmasked +		 */ +		WARN_ON(hcint != HCINTMSK_CHHLTD); +		if (hsotg->core_params->dma_desc_enable > 0) +			dwc2_hcd_complete_xfer_ddma(hsotg, chan, chnum, +						    chan->halt_status); +		else +			dwc2_release_channel(hsotg, chan, NULL, +					     chan->halt_status); +		return; +	} + +	if (list_empty(&chan->qh->qtd_list)) { +		/* +		 * TODO: Will this ever happen with the +		 * DWC2_HC_XFER_URB_DEQUEUE handling above? +		 */ +		dev_dbg(hsotg->dev, "## no QTD queued for channel %d ##\n", +			chnum); +		dev_dbg(hsotg->dev, +			"  hcint 0x%08x, hcintmsk 0x%08x, hcint&hcintmsk 0x%08x\n", +			chan->hcint, hcintmsk, hcint); +		chan->halt_status = DWC2_HC_XFER_NO_HALT_STATUS; +		disable_hc_int(hsotg, chnum, HCINTMSK_CHHLTD); +		chan->hcint = 0; +		return; +	} + +	qtd = list_first_entry(&chan->qh->qtd_list, struct dwc2_qtd, +			       qtd_list_entry); + +	if (hsotg->core_params->dma_enable <= 0) { +		if ((hcint & HCINTMSK_CHHLTD) && hcint != HCINTMSK_CHHLTD) +			hcint &= ~HCINTMSK_CHHLTD; +	} + +	if (hcint & HCINTMSK_XFERCOMPL) { +		dwc2_hc_xfercomp_intr(hsotg, chan, chnum, qtd); +		/* +		 * 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 &= ~HCINTMSK_NYET; +	} +	if (hcint & HCINTMSK_CHHLTD) +		dwc2_hc_chhltd_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_AHBERR) +		dwc2_hc_ahberr_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_STALL) +		dwc2_hc_stall_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_NAK) +		dwc2_hc_nak_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_ACK) +		dwc2_hc_ack_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_NYET) +		dwc2_hc_nyet_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_XACTERR) +		dwc2_hc_xacterr_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_BBLERR) +		dwc2_hc_babble_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_FRMOVRUN) +		dwc2_hc_frmovrun_intr(hsotg, chan, chnum, qtd); +	if (hcint & HCINTMSK_DATATGLERR) +		dwc2_hc_datatglerr_intr(hsotg, chan, chnum, qtd); + +	chan->hcint = 0; +} + +/* + * 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 void dwc2_hc_intr(struct dwc2_hsotg *hsotg) +{ +	u32 haint; +	int i; + +	haint = readl(hsotg->regs + HAINT); +	if (dbg_perio()) { +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +		dev_vdbg(hsotg->dev, "HAINT=%08x\n", haint); +	} + +	for (i = 0; i < hsotg->core_params->host_channels; i++) { +		if (haint & (1 << i)) +			dwc2_hc_n_intr(hsotg, i); +	} +} + +/* This function handles interrupts for the HCD */ +irqreturn_t dwc2_handle_hcd_intr(struct dwc2_hsotg *hsotg) +{ +	u32 gintsts, dbg_gintsts; +	irqreturn_t retval = IRQ_NONE; + +	if (!dwc2_is_controller_alive(hsotg)) { +		dev_warn(hsotg->dev, "Controller is dead\n"); +		return retval; +	} + +	spin_lock(&hsotg->lock); + +	/* Check if HOST Mode */ +	if (dwc2_is_host_mode(hsotg)) { +		gintsts = dwc2_read_core_intr(hsotg); +		if (!gintsts) { +			spin_unlock(&hsotg->lock); +			return retval; +		} + +		retval = IRQ_HANDLED; + +		dbg_gintsts = gintsts; +#ifndef DEBUG_SOF +		dbg_gintsts &= ~GINTSTS_SOF; +#endif +		if (!dbg_perio()) +			dbg_gintsts &= ~(GINTSTS_HCHINT | GINTSTS_RXFLVL | +					 GINTSTS_PTXFEMP); + +		/* Only print if there are any non-suppressed interrupts left */ +		if (dbg_gintsts) +			dev_vdbg(hsotg->dev, +				 "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x\n", +				 gintsts); + +		if (gintsts & GINTSTS_SOF) +			dwc2_sof_intr(hsotg); +		if (gintsts & GINTSTS_RXFLVL) +			dwc2_rx_fifo_level_intr(hsotg); +		if (gintsts & GINTSTS_NPTXFEMP) +			dwc2_np_tx_fifo_empty_intr(hsotg); +		if (gintsts & GINTSTS_PRTINT) +			dwc2_port_intr(hsotg); +		if (gintsts & GINTSTS_HCHINT) +			dwc2_hc_intr(hsotg); +		if (gintsts & GINTSTS_PTXFEMP) +			dwc2_perio_tx_fifo_empty_intr(hsotg); + +		if (dbg_gintsts) { +			dev_vdbg(hsotg->dev, +				 "DWC OTG HCD Finished Servicing Interrupts\n"); +			dev_vdbg(hsotg->dev, +				 "DWC OTG HCD gintsts=0x%08x gintmsk=0x%08x\n", +				 readl(hsotg->regs + GINTSTS), +				 readl(hsotg->regs + GINTMSK)); +		} +	} + +	spin_unlock(&hsotg->lock); + +	return retval; +} diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c new file mode 100644 index 00000000000..9540f7e1e20 --- /dev/null +++ b/drivers/usb/dwc2/hcd_queue.c @@ -0,0 +1,835 @@ +/* + * hcd_queue.c - DesignWare HS OTG Controller host queuing routines + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This file contains the functions to manage Queue Heads and Queue + * Transfer Descriptors for Host mode + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +/** + * dwc2_qh_init() - Initializes a QH structure + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    The QH to init + * @urb:   Holds the information about the device/endpoint needed to initialize + *         the QH + */ +#define SCHEDULE_SLOP 10 +static void dwc2_qh_init(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +			 struct dwc2_hcd_urb *urb) +{ +	int dev_speed, hub_addr, hub_port; +	char *speed, *type; + +	dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	/* Initialize QH */ +	qh->ep_type = dwc2_hcd_get_pipe_type(&urb->pipe_info); +	qh->ep_is_in = dwc2_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0; + +	qh->data_toggle = DWC2_HC_PID_DATA0; +	qh->maxp = dwc2_hcd_get_mps(&urb->pipe_info); +	INIT_LIST_HEAD(&qh->qtd_list); +	INIT_LIST_HEAD(&qh->qh_list_entry); + +	/* FS/LS Endpoint on HS Hub, NOT virtual root hub */ +	dev_speed = dwc2_host_get_speed(hsotg, urb->priv); + +	dwc2_host_hub_info(hsotg, urb->priv, &hub_addr, &hub_port); + +	if ((dev_speed == USB_SPEED_LOW || dev_speed == USB_SPEED_FULL) && +	    hub_addr != 0 && hub_addr != 1) { +		dev_vdbg(hsotg->dev, +			 "QH init: EP %d: TT found at hub addr %d, for port %d\n", +			 dwc2_hcd_get_ep_num(&urb->pipe_info), hub_addr, +			 hub_port); +		qh->do_split = 1; +	} + +	if (qh->ep_type == USB_ENDPOINT_XFER_INT || +	    qh->ep_type == USB_ENDPOINT_XFER_ISOC) { +		/* Compute scheduling parameters once and save them */ +		u32 hprt, prtspd; + +		/* Todo: Account for split transfers in the bus time */ +		int bytecount = +			dwc2_hb_mult(qh->maxp) * dwc2_max_packet(qh->maxp); + +		qh->usecs = NS_TO_US(usb_calc_bus_time(qh->do_split ? +				USB_SPEED_HIGH : dev_speed, qh->ep_is_in, +				qh->ep_type == USB_ENDPOINT_XFER_ISOC, +				bytecount)); +		/* Start in a slightly future (micro)frame */ +		qh->sched_frame = dwc2_frame_num_inc(hsotg->frame_number, +						     SCHEDULE_SLOP); +		qh->interval = urb->interval; +#if 0 +		/* Increase interrupt polling rate for debugging */ +		if (qh->ep_type == USB_ENDPOINT_XFER_INT) +			qh->interval = 8; +#endif +		hprt = readl(hsotg->regs + HPRT0); +		prtspd = (hprt & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; +		if (prtspd == HPRT0_SPD_HIGH_SPEED && +		    (dev_speed == USB_SPEED_LOW || +		     dev_speed == USB_SPEED_FULL)) { +			qh->interval *= 8; +			qh->sched_frame |= 0x7; +			qh->start_split_frame = qh->sched_frame; +		} +		dev_dbg(hsotg->dev, "interval=%d\n", qh->interval); +	} + +	dev_vdbg(hsotg->dev, "DWC OTG HCD QH Initialized\n"); +	dev_vdbg(hsotg->dev, "DWC OTG HCD QH - qh = %p\n", qh); +	dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Device Address = %d\n", +		 dwc2_hcd_get_dev_addr(&urb->pipe_info)); +	dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Endpoint %d, %s\n", +		 dwc2_hcd_get_ep_num(&urb->pipe_info), +		 dwc2_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"); + +	qh->dev_speed = dev_speed; + +	switch (dev_speed) { +	case USB_SPEED_LOW: +		speed = "low"; +		break; +	case USB_SPEED_FULL: +		speed = "full"; +		break; +	case USB_SPEED_HIGH: +		speed = "high"; +		break; +	default: +		speed = "?"; +		break; +	} +	dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Speed = %s\n", speed); + +	switch (qh->ep_type) { +	case USB_ENDPOINT_XFER_ISOC: +		type = "isochronous"; +		break; +	case USB_ENDPOINT_XFER_INT: +		type = "interrupt"; +		break; +	case USB_ENDPOINT_XFER_CONTROL: +		type = "control"; +		break; +	case USB_ENDPOINT_XFER_BULK: +		type = "bulk"; +		break; +	default: +		type = "?"; +		break; +	} + +	dev_vdbg(hsotg->dev, "DWC OTG HCD QH - Type = %s\n", type); + +	if (qh->ep_type == USB_ENDPOINT_XFER_INT) { +		dev_vdbg(hsotg->dev, "DWC OTG HCD QH - usecs = %d\n", +			 qh->usecs); +		dev_vdbg(hsotg->dev, "DWC OTG HCD QH - interval = %d\n", +			 qh->interval); +	} +} + +/** + * dwc2_hcd_qh_create() - Allocates and initializes a QH + * + * @hsotg:        The HCD state structure for the DWC OTG controller + * @urb:          Holds the information about the device/endpoint needed + *                to initialize the QH + * @atomic_alloc: Flag to do atomic allocation if needed + * + * Return: Pointer to the newly allocated QH, or NULL on error + */ +static struct dwc2_qh *dwc2_hcd_qh_create(struct dwc2_hsotg *hsotg, +					  struct dwc2_hcd_urb *urb, +					  gfp_t mem_flags) +{ +	struct dwc2_qh *qh; + +	if (!urb->priv) +		return NULL; + +	/* Allocate memory */ +	qh = kzalloc(sizeof(*qh), mem_flags); +	if (!qh) +		return NULL; + +	dwc2_qh_init(hsotg, qh, urb); + +	if (hsotg->core_params->dma_desc_enable > 0 && +	    dwc2_hcd_qh_init_ddma(hsotg, qh, mem_flags) < 0) { +		dwc2_hcd_qh_free(hsotg, qh); +		return NULL; +	} + +	return qh; +} + +/** + * dwc2_hcd_qh_free() - Frees the QH + * + * @hsotg: HCD instance + * @qh:    The QH to free + * + * QH should already be removed from the list. QTD list should already be empty + * if called from URB Dequeue. + * + * Must NOT be called with interrupt disabled or spinlock held + */ +void dwc2_hcd_qh_free(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	u32 buf_size; + +	if (hsotg->core_params->dma_desc_enable > 0) { +		dwc2_hcd_qh_free_ddma(hsotg, qh); +	} else if (qh->dw_align_buf) { +		if (qh->ep_type == USB_ENDPOINT_XFER_ISOC) +			buf_size = 4096; +		else +			buf_size = hsotg->core_params->max_transfer_size; +		dma_free_coherent(hsotg->dev, buf_size, qh->dw_align_buf, +				  qh->dw_align_buf_dma); +	} + +	kfree(qh); +} + +/** + * dwc2_periodic_channel_available() - Checks that a channel is available for a + * periodic transfer + * + * @hsotg: The HCD state structure for the DWC OTG controller + * + * Return: 0 if successful, negative error code otherwise + */ +static int dwc2_periodic_channel_available(struct dwc2_hsotg *hsotg) +{ +	/* +	 * Currently assuming that there is a dedicated host channel for +	 * each periodic transaction plus at least one host channel for +	 * non-periodic transactions +	 */ +	int status; +	int num_channels; + +	num_channels = hsotg->core_params->host_channels; +	if (hsotg->periodic_channels + hsotg->non_periodic_channels < +								num_channels +	    && hsotg->periodic_channels < num_channels - 1) { +		status = 0; +	} else { +		dev_dbg(hsotg->dev, +			"%s: Total channels: %d, Periodic: %d, " +			"Non-periodic: %d\n", __func__, num_channels, +			hsotg->periodic_channels, hsotg->non_periodic_channels); +		status = -ENOSPC; +	} + +	return status; +} + +/** + * dwc2_check_periodic_bandwidth() - Checks that there is sufficient bandwidth + * for the specified QH in the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    QH containing periodic bandwidth required + * + * Return: 0 if successful, negative error code otherwise + * + * For simplicity, this calculation assumes that all the transfers in the + * periodic schedule may occur in the same (micro)frame + */ +static int dwc2_check_periodic_bandwidth(struct dwc2_hsotg *hsotg, +					 struct dwc2_qh *qh) +{ +	int status; +	s16 max_claimed_usecs; + +	status = 0; + +	if (qh->dev_speed == USB_SPEED_HIGH || qh->do_split) { +		/* +		 * High speed mode +		 * Max periodic usecs is 80% x 125 usec = 100 usec +		 */ +		max_claimed_usecs = 100 - qh->usecs; +	} else { +		/* +		 * Full speed mode +		 * Max periodic usecs is 90% x 1000 usec = 900 usec +		 */ +		max_claimed_usecs = 900 - qh->usecs; +	} + +	if (hsotg->periodic_usecs > max_claimed_usecs) { +		dev_err(hsotg->dev, +			"%s: already claimed usecs %d, required usecs %d\n", +			__func__, hsotg->periodic_usecs, qh->usecs); +		status = -ENOSPC; +	} + +	return status; +} + +/** + * Microframe scheduler + * track the total use in hsotg->frame_usecs + * keep each qh use in qh->frame_usecs + * when surrendering the qh then donate the time back + */ +static const unsigned short max_uframe_usecs[] = { +	100, 100, 100, 100, 100, 100, 30, 0 +}; + +void dwc2_hcd_init_usecs(struct dwc2_hsotg *hsotg) +{ +	int i; + +	for (i = 0; i < 8; i++) +		hsotg->frame_usecs[i] = max_uframe_usecs[i]; +} + +static int dwc2_find_single_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	unsigned short utime = qh->usecs; +	int i; + +	for (i = 0; i < 8; i++) { +		/* At the start hsotg->frame_usecs[i] = max_uframe_usecs[i] */ +		if (utime <= hsotg->frame_usecs[i]) { +			hsotg->frame_usecs[i] -= utime; +			qh->frame_usecs[i] += utime; +			return i; +		} +	} +	return -ENOSPC; +} + +/* + * use this for FS apps that can span multiple uframes + */ +static int dwc2_find_multi_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	unsigned short utime = qh->usecs; +	unsigned short xtime; +	int t_left; +	int i; +	int j; +	int k; + +	for (i = 0; i < 8; i++) { +		if (hsotg->frame_usecs[i] <= 0) +			continue; + +		/* +		 * we need n consecutive slots so use j as a start slot +		 * j plus j+1 must be enough time (for now) +		 */ +		xtime = hsotg->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 + hsotg->frame_usecs[j] < utime) { +				if (hsotg->frame_usecs[j] < +							max_uframe_usecs[j]) +					continue; +			} +			if (xtime >= utime) { +				t_left = utime; +				for (k = i; k < 8; k++) { +					t_left -= hsotg->frame_usecs[k]; +					if (t_left <= 0) { +						qh->frame_usecs[k] += +							hsotg->frame_usecs[k] +								+ t_left; +						hsotg->frame_usecs[k] = -t_left; +						return i; +					} else { +						qh->frame_usecs[k] += +							hsotg->frame_usecs[k]; +						hsotg->frame_usecs[k] = 0; +					} +				} +			} +			/* add the frame time to x time */ +			xtime += hsotg->frame_usecs[j]; +			/* we must have a fully available next frame or break */ +			if (xtime < utime && +			   hsotg->frame_usecs[j] == max_uframe_usecs[j]) +				continue; +		} +	} +	return -ENOSPC; +} + +static int dwc2_find_uframe(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	int ret; + +	if (qh->dev_speed == USB_SPEED_HIGH) { +		/* if this is a hs transaction we need a full frame */ +		ret = dwc2_find_single_uframe(hsotg, qh); +	} else { +		/* +		 * if this is a fs transaction we may need a sequence +		 * of frames +		 */ +		ret = dwc2_find_multi_uframe(hsotg, qh); +	} +	return ret; +} + +/** + * dwc2_check_max_xfer_size() - 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 + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    QH for a periodic endpoint + * + * Return: 0 if successful, negative error code otherwise + */ +static int dwc2_check_max_xfer_size(struct dwc2_hsotg *hsotg, +				    struct dwc2_qh *qh) +{ +	u32 max_xfer_size; +	u32 max_channel_xfer_size; +	int status = 0; + +	max_xfer_size = dwc2_max_packet(qh->maxp) * dwc2_hb_mult(qh->maxp); +	max_channel_xfer_size = hsotg->core_params->max_transfer_size; + +	if (max_xfer_size > max_channel_xfer_size) { +		dev_err(hsotg->dev, +			"%s: Periodic xfer length %d > max xfer length for channel %d\n", +			__func__, max_xfer_size, max_channel_xfer_size); +		status = -ENOSPC; +	} + +	return status; +} + +/** + * dwc2_schedule_periodic() - Schedules an interrupt or isochronous transfer in + * the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    QH for the periodic transfer. The QH should already contain the + *         scheduling information. + * + * Return: 0 if successful, negative error code otherwise + */ +static int dwc2_schedule_periodic(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	int status; + +	if (hsotg->core_params->uframe_sched > 0) { +		int frame = -1; + +		status = dwc2_find_uframe(hsotg, qh); +		if (status == 0) +			frame = 7; +		else if (status > 0) +			frame = status - 1; + +		/* Set the new frame up */ +		if (frame >= 0) { +			qh->sched_frame &= ~0x7; +			qh->sched_frame |= (frame & 7); +		} + +		if (status > 0) +			status = 0; +	} else { +		status = dwc2_periodic_channel_available(hsotg); +		if (status) { +			dev_info(hsotg->dev, +				 "%s: No host channel available for periodic transfer\n", +				 __func__); +			return status; +		} + +		status = dwc2_check_periodic_bandwidth(hsotg, qh); +	} + +	if (status) { +		dev_dbg(hsotg->dev, +			"%s: Insufficient periodic bandwidth for periodic transfer\n", +			__func__); +		return status; +	} + +	status = dwc2_check_max_xfer_size(hsotg, qh); +	if (status) { +		dev_dbg(hsotg->dev, +			"%s: Channel max transfer size too small for periodic transfer\n", +			__func__); +		return status; +	} + +	if (hsotg->core_params->dma_desc_enable > 0) +		/* Don't rely on SOF and start in ready schedule */ +		list_add_tail(&qh->qh_list_entry, &hsotg->periodic_sched_ready); +	else +		/* Always start in inactive schedule */ +		list_add_tail(&qh->qh_list_entry, +			      &hsotg->periodic_sched_inactive); + +	if (hsotg->core_params->uframe_sched <= 0) +		/* Reserve periodic channel */ +		hsotg->periodic_channels++; + +	/* Update claimed usecs per (micro)frame */ +	hsotg->periodic_usecs += qh->usecs; + +	return status; +} + +/** + * dwc2_deschedule_periodic() - Removes an interrupt or isochronous transfer + * from the periodic schedule + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:	   QH for the periodic transfer + */ +static void dwc2_deschedule_periodic(struct dwc2_hsotg *hsotg, +				     struct dwc2_qh *qh) +{ +	int i; + +	list_del_init(&qh->qh_list_entry); + +	/* Update claimed usecs per (micro)frame */ +	hsotg->periodic_usecs -= qh->usecs; + +	if (hsotg->core_params->uframe_sched > 0) { +		for (i = 0; i < 8; i++) { +			hsotg->frame_usecs[i] += qh->frame_usecs[i]; +			qh->frame_usecs[i] = 0; +		} +	} else { +		/* Release periodic channel reservation */ +		hsotg->periodic_channels--; +	} +} + +/** + * dwc2_hcd_qh_add() - 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. + * + * @hsotg: The HCD state structure for the DWC OTG controller + * @qh:    The QH to add + * + * Return: 0 if successful, negative error code otherwise + */ +int dwc2_hcd_qh_add(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	int status; +	u32 intr_mask; + +	if (dbg_qh(qh)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	if (!list_empty(&qh->qh_list_entry)) +		/* QH already in a schedule */ +		return 0; + +	/* Add the new QH to the appropriate schedule */ +	if (dwc2_qh_is_non_per(qh)) { +		/* Always start in inactive schedule */ +		list_add_tail(&qh->qh_list_entry, +			      &hsotg->non_periodic_sched_inactive); +		return 0; +	} + +	status = dwc2_schedule_periodic(hsotg, qh); +	if (status) +		return status; +	if (!hsotg->periodic_qh_count) { +		intr_mask = readl(hsotg->regs + GINTMSK); +		intr_mask |= GINTSTS_SOF; +		writel(intr_mask, hsotg->regs + GINTMSK); +	} +	hsotg->periodic_qh_count++; + +	return 0; +} + +/** + * dwc2_hcd_qh_unlink() - Removes a QH from either the non-periodic or periodic + * schedule. Memory is not freed. + * + * @hsotg: The HCD state structure + * @qh:    QH to remove from schedule + */ +void dwc2_hcd_qh_unlink(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh) +{ +	u32 intr_mask; + +	dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	if (list_empty(&qh->qh_list_entry)) +		/* QH is not in a schedule */ +		return; + +	if (dwc2_qh_is_non_per(qh)) { +		if (hsotg->non_periodic_qh_ptr == &qh->qh_list_entry) +			hsotg->non_periodic_qh_ptr = +					hsotg->non_periodic_qh_ptr->next; +		list_del_init(&qh->qh_list_entry); +		return; +	} + +	dwc2_deschedule_periodic(hsotg, qh); +	hsotg->periodic_qh_count--; +	if (!hsotg->periodic_qh_count) { +		intr_mask = readl(hsotg->regs + GINTMSK); +		intr_mask &= ~GINTSTS_SOF; +		writel(intr_mask, hsotg->regs + GINTMSK); +	} +} + +/* + * Schedule the next continuing periodic split transfer + */ +static void dwc2_sched_periodic_split(struct dwc2_hsotg *hsotg, +				      struct dwc2_qh *qh, u16 frame_number, +				      int sched_next_periodic_split) +{ +	u16 incr; + +	if (sched_next_periodic_split) { +		qh->sched_frame = frame_number; +		incr = dwc2_frame_num_inc(qh->start_split_frame, 1); +		if (dwc2_frame_num_le(frame_number, incr)) { +			/* +			 * Allow one frame to elapse after start split +			 * microframe before scheduling complete split, but +			 * DON'T 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 != 0) { +				qh->sched_frame = +					dwc2_frame_num_inc(qh->sched_frame, 1); +			} +		} +	} else { +		qh->sched_frame = dwc2_frame_num_inc(qh->start_split_frame, +						     qh->interval); +		if (dwc2_frame_num_le(qh->sched_frame, frame_number)) +			qh->sched_frame = frame_number; +		qh->sched_frame |= 0x7; +		qh->start_split_frame = qh->sched_frame; +	} +} + +/* + * Deactivates a QH. For non-periodic QHs, 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. + * + * For periodic QHs, 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. + */ +void dwc2_hcd_qh_deactivate(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh, +			    int sched_next_periodic_split) +{ +	u16 frame_number; + +	if (dbg_qh(qh)) +		dev_vdbg(hsotg->dev, "%s()\n", __func__); + +	if (dwc2_qh_is_non_per(qh)) { +		dwc2_hcd_qh_unlink(hsotg, qh); +		if (!list_empty(&qh->qtd_list)) +			/* Add back to inactive non-periodic schedule */ +			dwc2_hcd_qh_add(hsotg, qh); +		return; +	} + +	frame_number = dwc2_hcd_get_frame_number(hsotg); + +	if (qh->do_split) { +		dwc2_sched_periodic_split(hsotg, qh, frame_number, +					  sched_next_periodic_split); +	} else { +		qh->sched_frame = dwc2_frame_num_inc(qh->sched_frame, +						     qh->interval); +		if (dwc2_frame_num_le(qh->sched_frame, frame_number)) +			qh->sched_frame = frame_number; +	} + +	if (list_empty(&qh->qtd_list)) { +		dwc2_hcd_qh_unlink(hsotg, qh); +		return; +	} +	/* +	 * Remove from periodic_sched_queued and move to +	 * appropriate queue +	 */ +	if ((hsotg->core_params->uframe_sched > 0 && +	     dwc2_frame_num_le(qh->sched_frame, frame_number)) || +	    (hsotg->core_params->uframe_sched <= 0 && +	     qh->sched_frame == frame_number)) +		list_move(&qh->qh_list_entry, &hsotg->periodic_sched_ready); +	else +		list_move(&qh->qh_list_entry, &hsotg->periodic_sched_inactive); +} + +/** + * dwc2_hcd_qtd_init() - Initializes a QTD structure + * + * @qtd: The QTD to initialize + * @urb: The associated URB + */ +void dwc2_hcd_qtd_init(struct dwc2_qtd *qtd, struct dwc2_hcd_urb *urb) +{ +	qtd->urb = urb; +	if (dwc2_hcd_get_pipe_type(&urb->pipe_info) == +			USB_ENDPOINT_XFER_CONTROL) { +		/* +		 * 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 = DWC2_HC_PID_DATA1; +		qtd->control_phase = DWC2_CONTROL_SETUP; +	} + +	/* Start split */ +	qtd->complete_split = 0; +	qtd->isoc_split_pos = DWC2_HCSPLT_XACTPOS_ALL; +	qtd->isoc_split_offset = 0; +	qtd->in_process = 0; + +	/* Store the qtd ptr in the urb to reference the QTD */ +	urb->qtd = qtd; +} + +/** + * dwc2_hcd_qtd_add() - Adds a QTD to the QTD-list of a QH + * + * @hsotg:        The DWC HCD structure + * @qtd:          The QTD to add + * @qh:           Out parameter to return queue head + * @atomic_alloc: Flag to do atomic alloc if needed + * + * Return: 0 if successful, negative error code otherwise + * + * Finds the correct QH to place the QTD into. If it does not find a QH, 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 dwc2_hcd_qtd_add(struct dwc2_hsotg *hsotg, struct dwc2_qtd *qtd, +		     struct dwc2_qh **qh, gfp_t mem_flags) +{ +	struct dwc2_hcd_urb *urb = qtd->urb; +	unsigned long flags; +	int allocated = 0; +	int retval; + +	/* +	 * Get the QH which holds the QTD-list to insert to. Create QH if it +	 * doesn't exist. +	 */ +	if (*qh == NULL) { +		*qh = dwc2_hcd_qh_create(hsotg, urb, mem_flags); +		if (*qh == NULL) +			return -ENOMEM; +		allocated = 1; +	} + +	spin_lock_irqsave(&hsotg->lock, flags); + +	retval = dwc2_hcd_qh_add(hsotg, *qh); +	if (retval) +		goto fail; + +	qtd->qh = *qh; +	list_add_tail(&qtd->qtd_list_entry, &(*qh)->qtd_list); +	spin_unlock_irqrestore(&hsotg->lock, flags); + +	return 0; + +fail: +	if (allocated) { +		struct dwc2_qtd *qtd2, *qtd2_tmp; +		struct dwc2_qh *qh_tmp = *qh; + +		*qh = NULL; +		dwc2_hcd_qh_unlink(hsotg, qh_tmp); + +		/* Free each QTD in the QH's QTD list */ +		list_for_each_entry_safe(qtd2, qtd2_tmp, &qh_tmp->qtd_list, +					 qtd_list_entry) +			dwc2_hcd_qtd_unlink_and_free(hsotg, qtd2, qh_tmp); + +		spin_unlock_irqrestore(&hsotg->lock, flags); +		dwc2_hcd_qh_free(hsotg, qh_tmp); +	} else { +		spin_unlock_irqrestore(&hsotg->lock, flags); +	} + +	return retval; +} diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h new file mode 100644 index 00000000000..51248b93549 --- /dev/null +++ b/drivers/usb/dwc2/hw.h @@ -0,0 +1,811 @@ +/* + * hw.h - DesignWare HS OTG Controller hardware definitions + * + * Copyright 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __DWC2_HW_H__ +#define __DWC2_HW_H__ + +#define HSOTG_REG(x)	(x) + +#define GOTGCTL				HSOTG_REG(0x000) +#define GOTGCTL_CHIRPEN			(1 << 27) +#define GOTGCTL_MULT_VALID_BC_MASK	(0x1f << 22) +#define GOTGCTL_MULT_VALID_BC_SHIFT	22 +#define GOTGCTL_OTGVER			(1 << 20) +#define GOTGCTL_BSESVLD			(1 << 19) +#define GOTGCTL_ASESVLD			(1 << 18) +#define GOTGCTL_DBNC_SHORT		(1 << 17) +#define GOTGCTL_CONID_B			(1 << 16) +#define GOTGCTL_DEVHNPEN		(1 << 11) +#define GOTGCTL_HSTSETHNPEN		(1 << 10) +#define GOTGCTL_HNPREQ			(1 << 9) +#define GOTGCTL_HSTNEGSCS		(1 << 8) +#define GOTGCTL_SESREQ			(1 << 1) +#define GOTGCTL_SESREQSCS		(1 << 0) + +#define GOTGINT				HSOTG_REG(0x004) +#define GOTGINT_DBNCE_DONE		(1 << 19) +#define GOTGINT_A_DEV_TOUT_CHG		(1 << 18) +#define GOTGINT_HST_NEG_DET		(1 << 17) +#define GOTGINT_HST_NEG_SUC_STS_CHNG	(1 << 9) +#define GOTGINT_SES_REQ_SUC_STS_CHNG	(1 << 8) +#define GOTGINT_SES_END_DET		(1 << 2) + +#define GAHBCFG				HSOTG_REG(0x008) +#define GAHBCFG_AHB_SINGLE		(1 << 23) +#define GAHBCFG_NOTI_ALL_DMA_WRIT	(1 << 22) +#define GAHBCFG_REM_MEM_SUPP		(1 << 21) +#define GAHBCFG_P_TXF_EMP_LVL		(1 << 8) +#define GAHBCFG_NP_TXF_EMP_LVL		(1 << 7) +#define GAHBCFG_DMA_EN			(1 << 5) +#define GAHBCFG_HBSTLEN_MASK		(0xf << 1) +#define GAHBCFG_HBSTLEN_SHIFT		1 +#define GAHBCFG_HBSTLEN_SINGLE		0 +#define GAHBCFG_HBSTLEN_INCR		1 +#define GAHBCFG_HBSTLEN_INCR4		3 +#define GAHBCFG_HBSTLEN_INCR8		5 +#define GAHBCFG_HBSTLEN_INCR16		7 +#define GAHBCFG_GLBL_INTR_EN		(1 << 0) +#define GAHBCFG_CTRL_MASK		(GAHBCFG_P_TXF_EMP_LVL | \ +					 GAHBCFG_NP_TXF_EMP_LVL | \ +					 GAHBCFG_DMA_EN | \ +					 GAHBCFG_GLBL_INTR_EN) + +#define GUSBCFG				HSOTG_REG(0x00C) +#define GUSBCFG_FORCEDEVMODE		(1 << 30) +#define GUSBCFG_FORCEHOSTMODE		(1 << 29) +#define GUSBCFG_TXENDDELAY		(1 << 28) +#define GUSBCFG_ICTRAFFICPULLREMOVE	(1 << 27) +#define GUSBCFG_ICUSBCAP		(1 << 26) +#define GUSBCFG_ULPI_INT_PROT_DIS	(1 << 25) +#define GUSBCFG_INDICATORPASSTHROUGH	(1 << 24) +#define GUSBCFG_INDICATORCOMPLEMENT	(1 << 23) +#define GUSBCFG_TERMSELDLPULSE		(1 << 22) +#define GUSBCFG_ULPI_INT_VBUS_IND	(1 << 21) +#define GUSBCFG_ULPI_EXT_VBUS_DRV	(1 << 20) +#define GUSBCFG_ULPI_CLK_SUSP_M		(1 << 19) +#define GUSBCFG_ULPI_AUTO_RES		(1 << 18) +#define GUSBCFG_ULPI_FS_LS		(1 << 17) +#define GUSBCFG_OTG_UTMI_FS_SEL		(1 << 16) +#define GUSBCFG_PHY_LP_CLK_SEL		(1 << 15) +#define GUSBCFG_USBTRDTIM_MASK		(0xf << 10) +#define GUSBCFG_USBTRDTIM_SHIFT		10 +#define GUSBCFG_HNPCAP			(1 << 9) +#define GUSBCFG_SRPCAP			(1 << 8) +#define GUSBCFG_DDRSEL			(1 << 7) +#define GUSBCFG_PHYSEL			(1 << 6) +#define GUSBCFG_FSINTF			(1 << 5) +#define GUSBCFG_ULPI_UTMI_SEL		(1 << 4) +#define GUSBCFG_PHYIF16			(1 << 3) +#define GUSBCFG_PHYIF8			(0 << 3) +#define GUSBCFG_TOUTCAL_MASK		(0x7 << 0) +#define GUSBCFG_TOUTCAL_SHIFT		0 +#define GUSBCFG_TOUTCAL_LIMIT		0x7 +#define GUSBCFG_TOUTCAL(_x)		((_x) << 0) + +#define GRSTCTL				HSOTG_REG(0x010) +#define GRSTCTL_AHBIDLE			(1 << 31) +#define GRSTCTL_DMAREQ			(1 << 30) +#define GRSTCTL_TXFNUM_MASK		(0x1f << 6) +#define GRSTCTL_TXFNUM_SHIFT		6 +#define GRSTCTL_TXFNUM_LIMIT		0x1f +#define GRSTCTL_TXFNUM(_x)		((_x) << 6) +#define GRSTCTL_TXFFLSH			(1 << 5) +#define GRSTCTL_RXFFLSH			(1 << 4) +#define GRSTCTL_IN_TKNQ_FLSH		(1 << 3) +#define GRSTCTL_FRMCNTRRST		(1 << 2) +#define GRSTCTL_HSFTRST			(1 << 1) +#define GRSTCTL_CSFTRST			(1 << 0) + +#define GINTSTS				HSOTG_REG(0x014) +#define GINTMSK				HSOTG_REG(0x018) +#define GINTSTS_WKUPINT			(1 << 31) +#define GINTSTS_SESSREQINT		(1 << 30) +#define GINTSTS_DISCONNINT		(1 << 29) +#define GINTSTS_CONIDSTSCHNG		(1 << 28) +#define GINTSTS_LPMTRANRCVD		(1 << 27) +#define GINTSTS_PTXFEMP			(1 << 26) +#define GINTSTS_HCHINT			(1 << 25) +#define GINTSTS_PRTINT			(1 << 24) +#define GINTSTS_RESETDET		(1 << 23) +#define GINTSTS_FET_SUSP		(1 << 22) +#define GINTSTS_INCOMPL_IP		(1 << 21) +#define GINTSTS_INCOMPL_SOIN		(1 << 20) +#define GINTSTS_OEPINT			(1 << 19) +#define GINTSTS_IEPINT			(1 << 18) +#define GINTSTS_EPMIS			(1 << 17) +#define GINTSTS_RESTOREDONE		(1 << 16) +#define GINTSTS_EOPF			(1 << 15) +#define GINTSTS_ISOUTDROP		(1 << 14) +#define GINTSTS_ENUMDONE		(1 << 13) +#define GINTSTS_USBRST			(1 << 12) +#define GINTSTS_USBSUSP			(1 << 11) +#define GINTSTS_ERLYSUSP		(1 << 10) +#define GINTSTS_I2CINT			(1 << 9) +#define GINTSTS_ULPI_CK_INT		(1 << 8) +#define GINTSTS_GOUTNAKEFF		(1 << 7) +#define GINTSTS_GINNAKEFF		(1 << 6) +#define GINTSTS_NPTXFEMP		(1 << 5) +#define GINTSTS_RXFLVL			(1 << 4) +#define GINTSTS_SOF			(1 << 3) +#define GINTSTS_OTGINT			(1 << 2) +#define GINTSTS_MODEMIS			(1 << 1) +#define GINTSTS_CURMODE_HOST		(1 << 0) + +#define GRXSTSR				HSOTG_REG(0x01C) +#define GRXSTSP				HSOTG_REG(0x020) +#define GRXSTS_FN_MASK			(0x7f << 25) +#define GRXSTS_FN_SHIFT			25 +#define GRXSTS_PKTSTS_MASK		(0xf << 17) +#define GRXSTS_PKTSTS_SHIFT		17 +#define GRXSTS_PKTSTS_GLOBALOUTNAK	1 +#define GRXSTS_PKTSTS_OUTRX		2 +#define GRXSTS_PKTSTS_HCHIN		2 +#define GRXSTS_PKTSTS_OUTDONE		3 +#define GRXSTS_PKTSTS_HCHIN_XFER_COMP	3 +#define GRXSTS_PKTSTS_SETUPDONE		4 +#define GRXSTS_PKTSTS_DATATOGGLEERR	5 +#define GRXSTS_PKTSTS_SETUPRX		6 +#define GRXSTS_PKTSTS_HCHHALTED		7 +#define GRXSTS_HCHNUM_MASK		(0xf << 0) +#define GRXSTS_HCHNUM_SHIFT		0 +#define GRXSTS_DPID_MASK		(0x3 << 15) +#define GRXSTS_DPID_SHIFT		15 +#define GRXSTS_BYTECNT_MASK		(0x7ff << 4) +#define GRXSTS_BYTECNT_SHIFT		4 +#define GRXSTS_EPNUM_MASK		(0xf << 0) +#define GRXSTS_EPNUM_SHIFT		0 + +#define GRXFSIZ				HSOTG_REG(0x024) +#define GRXFSIZ_DEPTH_MASK		(0xffff << 0) +#define GRXFSIZ_DEPTH_SHIFT		0 + +#define GNPTXFSIZ			HSOTG_REG(0x028) +/* Use FIFOSIZE_* constants to access this register */ + +#define GNPTXSTS			HSOTG_REG(0x02C) +#define GNPTXSTS_NP_TXQ_TOP_MASK		(0x7f << 24) +#define GNPTXSTS_NP_TXQ_TOP_SHIFT		24 +#define GNPTXSTS_NP_TXQ_SPC_AVAIL_MASK		(0xff << 16) +#define GNPTXSTS_NP_TXQ_SPC_AVAIL_SHIFT		16 +#define GNPTXSTS_NP_TXQ_SPC_AVAIL_GET(_v)	(((_v) >> 16) & 0xff) +#define GNPTXSTS_NP_TXF_SPC_AVAIL_MASK		(0xffff << 0) +#define GNPTXSTS_NP_TXF_SPC_AVAIL_SHIFT		0 +#define GNPTXSTS_NP_TXF_SPC_AVAIL_GET(_v)	(((_v) >> 0) & 0xffff) + +#define GI2CCTL				HSOTG_REG(0x0030) +#define GI2CCTL_BSYDNE			(1 << 31) +#define GI2CCTL_RW			(1 << 30) +#define GI2CCTL_I2CDATSE0		(1 << 28) +#define GI2CCTL_I2CDEVADDR_MASK		(0x3 << 26) +#define GI2CCTL_I2CDEVADDR_SHIFT	26 +#define GI2CCTL_I2CSUSPCTL		(1 << 25) +#define GI2CCTL_ACK			(1 << 24) +#define GI2CCTL_I2CEN			(1 << 23) +#define GI2CCTL_ADDR_MASK		(0x7f << 16) +#define GI2CCTL_ADDR_SHIFT		16 +#define GI2CCTL_REGADDR_MASK		(0xff << 8) +#define GI2CCTL_REGADDR_SHIFT		8 +#define GI2CCTL_RWDATA_MASK		(0xff << 0) +#define GI2CCTL_RWDATA_SHIFT		0 + +#define GPVNDCTL			HSOTG_REG(0x0034) +#define GGPIO				HSOTG_REG(0x0038) +#define GUID				HSOTG_REG(0x003c) +#define GSNPSID				HSOTG_REG(0x0040) +#define GHWCFG1				HSOTG_REG(0x0044) + +#define GHWCFG2				HSOTG_REG(0x0048) +#define GHWCFG2_OTG_ENABLE_IC_USB		(1 << 31) +#define GHWCFG2_DEV_TOKEN_Q_DEPTH_MASK		(0x1f << 26) +#define GHWCFG2_DEV_TOKEN_Q_DEPTH_SHIFT		26 +#define GHWCFG2_HOST_PERIO_TX_Q_DEPTH_MASK	(0x3 << 24) +#define GHWCFG2_HOST_PERIO_TX_Q_DEPTH_SHIFT	24 +#define GHWCFG2_NONPERIO_TX_Q_DEPTH_MASK	(0x3 << 22) +#define GHWCFG2_NONPERIO_TX_Q_DEPTH_SHIFT	22 +#define GHWCFG2_MULTI_PROC_INT			(1 << 20) +#define GHWCFG2_DYNAMIC_FIFO			(1 << 19) +#define GHWCFG2_PERIO_EP_SUPPORTED		(1 << 18) +#define GHWCFG2_NUM_HOST_CHAN_MASK		(0xf << 14) +#define GHWCFG2_NUM_HOST_CHAN_SHIFT		14 +#define GHWCFG2_NUM_DEV_EP_MASK			(0xf << 10) +#define GHWCFG2_NUM_DEV_EP_SHIFT		10 +#define GHWCFG2_FS_PHY_TYPE_MASK		(0x3 << 8) +#define GHWCFG2_FS_PHY_TYPE_SHIFT		8 +#define GHWCFG2_FS_PHY_TYPE_NOT_SUPPORTED	0 +#define GHWCFG2_FS_PHY_TYPE_DEDICATED		1 +#define GHWCFG2_FS_PHY_TYPE_SHARED_UTMI		2 +#define GHWCFG2_FS_PHY_TYPE_SHARED_ULPI		3 +#define GHWCFG2_HS_PHY_TYPE_MASK		(0x3 << 6) +#define GHWCFG2_HS_PHY_TYPE_SHIFT		6 +#define GHWCFG2_HS_PHY_TYPE_NOT_SUPPORTED	0 +#define GHWCFG2_HS_PHY_TYPE_UTMI		1 +#define GHWCFG2_HS_PHY_TYPE_ULPI		2 +#define GHWCFG2_HS_PHY_TYPE_UTMI_ULPI		3 +#define GHWCFG2_POINT2POINT			(1 << 5) +#define GHWCFG2_ARCHITECTURE_MASK		(0x3 << 3) +#define GHWCFG2_ARCHITECTURE_SHIFT		3 +#define GHWCFG2_SLAVE_ONLY_ARCH			0 +#define GHWCFG2_EXT_DMA_ARCH			1 +#define GHWCFG2_INT_DMA_ARCH			2 +#define GHWCFG2_OP_MODE_MASK			(0x7 << 0) +#define GHWCFG2_OP_MODE_SHIFT			0 +#define GHWCFG2_OP_MODE_HNP_SRP_CAPABLE		0 +#define GHWCFG2_OP_MODE_SRP_ONLY_CAPABLE	1 +#define GHWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE	2 +#define GHWCFG2_OP_MODE_SRP_CAPABLE_DEVICE	3 +#define GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE	4 +#define GHWCFG2_OP_MODE_SRP_CAPABLE_HOST	5 +#define GHWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST	6 +#define GHWCFG2_OP_MODE_UNDEFINED		7 + +#define GHWCFG3				HSOTG_REG(0x004c) +#define GHWCFG3_DFIFO_DEPTH_MASK		(0xffff << 16) +#define GHWCFG3_DFIFO_DEPTH_SHIFT		16 +#define GHWCFG3_OTG_LPM_EN			(1 << 15) +#define GHWCFG3_BC_SUPPORT			(1 << 14) +#define GHWCFG3_OTG_ENABLE_HSIC			(1 << 13) +#define GHWCFG3_ADP_SUPP			(1 << 12) +#define GHWCFG3_SYNCH_RESET_TYPE		(1 << 11) +#define GHWCFG3_OPTIONAL_FEATURES		(1 << 10) +#define GHWCFG3_VENDOR_CTRL_IF			(1 << 9) +#define GHWCFG3_I2C				(1 << 8) +#define GHWCFG3_OTG_FUNC			(1 << 7) +#define GHWCFG3_PACKET_SIZE_CNTR_WIDTH_MASK	(0x7 << 4) +#define GHWCFG3_PACKET_SIZE_CNTR_WIDTH_SHIFT	4 +#define GHWCFG3_XFER_SIZE_CNTR_WIDTH_MASK	(0xf << 0) +#define GHWCFG3_XFER_SIZE_CNTR_WIDTH_SHIFT	0 + +#define GHWCFG4				HSOTG_REG(0x0050) +#define GHWCFG4_DESC_DMA_DYN			(1 << 31) +#define GHWCFG4_DESC_DMA			(1 << 30) +#define GHWCFG4_NUM_IN_EPS_MASK			(0xf << 26) +#define GHWCFG4_NUM_IN_EPS_SHIFT		26 +#define GHWCFG4_DED_FIFO_EN			(1 << 25) +#define GHWCFG4_SESSION_END_FILT_EN		(1 << 24) +#define GHWCFG4_B_VALID_FILT_EN			(1 << 23) +#define GHWCFG4_A_VALID_FILT_EN			(1 << 22) +#define GHWCFG4_VBUS_VALID_FILT_EN		(1 << 21) +#define GHWCFG4_IDDIG_FILT_EN			(1 << 20) +#define GHWCFG4_NUM_DEV_MODE_CTRL_EP_MASK	(0xf << 16) +#define GHWCFG4_NUM_DEV_MODE_CTRL_EP_SHIFT	16 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_MASK	(0x3 << 14) +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_SHIFT	14 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8		0 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_16		1 +#define GHWCFG4_UTMI_PHY_DATA_WIDTH_8_OR_16	2 +#define GHWCFG4_XHIBER				(1 << 7) +#define GHWCFG4_HIBER				(1 << 6) +#define GHWCFG4_MIN_AHB_FREQ			(1 << 5) +#define GHWCFG4_POWER_OPTIMIZ			(1 << 4) +#define GHWCFG4_NUM_DEV_PERIO_IN_EP_MASK	(0xf << 0) +#define GHWCFG4_NUM_DEV_PERIO_IN_EP_SHIFT	0 + +#define GLPMCFG				HSOTG_REG(0x0054) +#define GLPMCFG_INV_SEL_HSIC		(1 << 31) +#define GLPMCFG_HSIC_CONNECT		(1 << 30) +#define GLPMCFG_RETRY_COUNT_STS_MASK	(0x7 << 25) +#define GLPMCFG_RETRY_COUNT_STS_SHIFT	25 +#define GLPMCFG_SEND_LPM		(1 << 24) +#define GLPMCFG_RETRY_COUNT_MASK	(0x7 << 21) +#define GLPMCFG_RETRY_COUNT_SHIFT	21 +#define GLPMCFG_LPM_CHAN_INDEX_MASK	(0xf << 17) +#define GLPMCFG_LPM_CHAN_INDEX_SHIFT	17 +#define GLPMCFG_SLEEP_STATE_RESUMEOK	(1 << 16) +#define GLPMCFG_PRT_SLEEP_STS		(1 << 15) +#define GLPMCFG_LPM_RESP_MASK		(0x3 << 13) +#define GLPMCFG_LPM_RESP_SHIFT		13 +#define GLPMCFG_HIRD_THRES_MASK		(0x1f << 8) +#define GLPMCFG_HIRD_THRES_SHIFT	8 +#define GLPMCFG_HIRD_THRES_EN			(0x10 << 8) +#define GLPMCFG_EN_UTMI_SLEEP		(1 << 7) +#define GLPMCFG_REM_WKUP_EN		(1 << 6) +#define GLPMCFG_HIRD_MASK		(0xf << 2) +#define GLPMCFG_HIRD_SHIFT		2 +#define GLPMCFG_APPL_RESP		(1 << 1) +#define GLPMCFG_LPM_CAP_EN		(1 << 0) + +#define GPWRDN				HSOTG_REG(0x0058) +#define GPWRDN_MULT_VAL_ID_BC_MASK	(0x1f << 24) +#define GPWRDN_MULT_VAL_ID_BC_SHIFT	24 +#define GPWRDN_ADP_INT			(1 << 23) +#define GPWRDN_BSESSVLD			(1 << 22) +#define GPWRDN_IDSTS			(1 << 21) +#define GPWRDN_LINESTATE_MASK		(0x3 << 19) +#define GPWRDN_LINESTATE_SHIFT		19 +#define GPWRDN_STS_CHGINT_MSK		(1 << 18) +#define GPWRDN_STS_CHGINT		(1 << 17) +#define GPWRDN_SRP_DET_MSK		(1 << 16) +#define GPWRDN_SRP_DET			(1 << 15) +#define GPWRDN_CONNECT_DET_MSK		(1 << 14) +#define GPWRDN_CONNECT_DET		(1 << 13) +#define GPWRDN_DISCONN_DET_MSK		(1 << 12) +#define GPWRDN_DISCONN_DET		(1 << 11) +#define GPWRDN_RST_DET_MSK		(1 << 10) +#define GPWRDN_RST_DET			(1 << 9) +#define GPWRDN_LNSTSCHG_MSK		(1 << 8) +#define GPWRDN_LNSTSCHG			(1 << 7) +#define GPWRDN_DIS_VBUS			(1 << 6) +#define GPWRDN_PWRDNSWTCH		(1 << 5) +#define GPWRDN_PWRDNRSTN		(1 << 4) +#define GPWRDN_PWRDNCLMP		(1 << 3) +#define GPWRDN_RESTORE			(1 << 2) +#define GPWRDN_PMUACTV			(1 << 1) +#define GPWRDN_PMUINTSEL		(1 << 0) + +#define GDFIFOCFG			HSOTG_REG(0x005c) +#define GDFIFOCFG_EPINFOBASE_MASK	(0xffff << 16) +#define GDFIFOCFG_EPINFOBASE_SHIFT	16 +#define GDFIFOCFG_GDFIFOCFG_MASK	(0xffff << 0) +#define GDFIFOCFG_GDFIFOCFG_SHIFT	0 + +#define ADPCTL				HSOTG_REG(0x0060) +#define ADPCTL_AR_MASK			(0x3 << 27) +#define ADPCTL_AR_SHIFT			27 +#define ADPCTL_ADP_TMOUT_INT_MSK	(1 << 26) +#define ADPCTL_ADP_SNS_INT_MSK		(1 << 25) +#define ADPCTL_ADP_PRB_INT_MSK		(1 << 24) +#define ADPCTL_ADP_TMOUT_INT		(1 << 23) +#define ADPCTL_ADP_SNS_INT		(1 << 22) +#define ADPCTL_ADP_PRB_INT		(1 << 21) +#define ADPCTL_ADPENA			(1 << 20) +#define ADPCTL_ADPRES			(1 << 19) +#define ADPCTL_ENASNS			(1 << 18) +#define ADPCTL_ENAPRB			(1 << 17) +#define ADPCTL_RTIM_MASK		(0x7ff << 6) +#define ADPCTL_RTIM_SHIFT		6 +#define ADPCTL_PRB_PER_MASK		(0x3 << 4) +#define ADPCTL_PRB_PER_SHIFT		4 +#define ADPCTL_PRB_DELTA_MASK		(0x3 << 2) +#define ADPCTL_PRB_DELTA_SHIFT		2 +#define ADPCTL_PRB_DSCHRG_MASK		(0x3 << 0) +#define ADPCTL_PRB_DSCHRG_SHIFT		0 + +#define HPTXFSIZ			HSOTG_REG(0x100) +/* Use FIFOSIZE_* constants to access this register */ + +#define DPTXFSIZN(_a)			HSOTG_REG(0x104 + (((_a) - 1) * 4)) +/* Use FIFOSIZE_* constants to access this register */ + +/* These apply to the GNPTXFSIZ, HPTXFSIZ and DPTXFSIZN registers */ +#define FIFOSIZE_DEPTH_MASK		(0xffff << 16) +#define FIFOSIZE_DEPTH_SHIFT		16 +#define FIFOSIZE_STARTADDR_MASK		(0xffff << 0) +#define FIFOSIZE_STARTADDR_SHIFT	0 +#define FIFOSIZE_DEPTH_GET(_x)		(((_x) >> 16) & 0xffff) + +/* Device mode registers */ + +#define DCFG				HSOTG_REG(0x800) +#define DCFG_EPMISCNT_MASK		(0x1f << 18) +#define DCFG_EPMISCNT_SHIFT		18 +#define DCFG_EPMISCNT_LIMIT		0x1f +#define DCFG_EPMISCNT(_x)		((_x) << 18) +#define DCFG_PERFRINT_MASK		(0x3 << 11) +#define DCFG_PERFRINT_SHIFT		11 +#define DCFG_PERFRINT_LIMIT		0x3 +#define DCFG_PERFRINT(_x)		((_x) << 11) +#define DCFG_DEVADDR_MASK		(0x7f << 4) +#define DCFG_DEVADDR_SHIFT		4 +#define DCFG_DEVADDR_LIMIT		0x7f +#define DCFG_DEVADDR(_x)		((_x) << 4) +#define DCFG_NZ_STS_OUT_HSHK		(1 << 2) +#define DCFG_DEVSPD_MASK		(0x3 << 0) +#define DCFG_DEVSPD_SHIFT		0 +#define DCFG_DEVSPD_HS			0 +#define DCFG_DEVSPD_FS			1 +#define DCFG_DEVSPD_LS			2 +#define DCFG_DEVSPD_FS48		3 + +#define DCTL				HSOTG_REG(0x804) +#define DCTL_PWRONPRGDONE		(1 << 11) +#define DCTL_CGOUTNAK			(1 << 10) +#define DCTL_SGOUTNAK			(1 << 9) +#define DCTL_CGNPINNAK			(1 << 8) +#define DCTL_SGNPINNAK			(1 << 7) +#define DCTL_TSTCTL_MASK		(0x7 << 4) +#define DCTL_TSTCTL_SHIFT		4 +#define DCTL_GOUTNAKSTS			(1 << 3) +#define DCTL_GNPINNAKSTS		(1 << 2) +#define DCTL_SFTDISCON			(1 << 1) +#define DCTL_RMTWKUPSIG			(1 << 0) + +#define DSTS				HSOTG_REG(0x808) +#define DSTS_SOFFN_MASK			(0x3fff << 8) +#define DSTS_SOFFN_SHIFT		8 +#define DSTS_SOFFN_LIMIT		0x3fff +#define DSTS_SOFFN(_x)			((_x) << 8) +#define DSTS_ERRATICERR			(1 << 3) +#define DSTS_ENUMSPD_MASK		(0x3 << 1) +#define DSTS_ENUMSPD_SHIFT		1 +#define DSTS_ENUMSPD_HS			0 +#define DSTS_ENUMSPD_FS			1 +#define DSTS_ENUMSPD_LS			2 +#define DSTS_ENUMSPD_FS48		3 +#define DSTS_SUSPSTS			(1 << 0) + +#define DIEPMSK				HSOTG_REG(0x810) +#define DIEPMSK_TXFIFOEMPTY		(1 << 7) +#define DIEPMSK_INEPNAKEFFMSK		(1 << 6) +#define DIEPMSK_INTKNEPMISMSK		(1 << 5) +#define DIEPMSK_INTKNTXFEMPMSK		(1 << 4) +#define DIEPMSK_TIMEOUTMSK		(1 << 3) +#define DIEPMSK_AHBERRMSK		(1 << 2) +#define DIEPMSK_EPDISBLDMSK		(1 << 1) +#define DIEPMSK_XFERCOMPLMSK		(1 << 0) + +#define DOEPMSK				HSOTG_REG(0x814) +#define DOEPMSK_BACK2BACKSETUP		(1 << 6) +#define DOEPMSK_OUTTKNEPDISMSK		(1 << 4) +#define DOEPMSK_SETUPMSK		(1 << 3) +#define DOEPMSK_AHBERRMSK		(1 << 2) +#define DOEPMSK_EPDISBLDMSK		(1 << 1) +#define DOEPMSK_XFERCOMPLMSK		(1 << 0) + +#define DAINT				HSOTG_REG(0x818) +#define DAINTMSK			HSOTG_REG(0x81C) +#define DAINT_OUTEP_SHIFT		16 +#define DAINT_OUTEP(_x)			(1 << ((_x) + 16)) +#define DAINT_INEP(_x)			(1 << (_x)) + +#define DTKNQR1				HSOTG_REG(0x820) +#define DTKNQR2				HSOTG_REG(0x824) +#define DTKNQR3				HSOTG_REG(0x830) +#define DTKNQR4				HSOTG_REG(0x834) + +#define DVBUSDIS			HSOTG_REG(0x828) +#define DVBUSPULSE			HSOTG_REG(0x82C) + +#define DIEPCTL0			HSOTG_REG(0x900) +#define DIEPCTL(_a)			HSOTG_REG(0x900 + ((_a) * 0x20)) + +#define DOEPCTL0			HSOTG_REG(0xB00) +#define DOEPCTL(_a)			HSOTG_REG(0xB00 + ((_a) * 0x20)) + +/* EP0 specialness: + * bits[29..28] - reserved (no SetD0PID, SetD1PID) + * bits[25..22] - should always be zero, this isn't a periodic endpoint + * bits[10..0]  - MPS setting different for EP0 + */ +#define D0EPCTL_MPS_MASK		(0x3 << 0) +#define D0EPCTL_MPS_SHIFT		0 +#define D0EPCTL_MPS_64			0 +#define D0EPCTL_MPS_32			1 +#define D0EPCTL_MPS_16			2 +#define D0EPCTL_MPS_8			3 + +#define DXEPCTL_EPENA			(1 << 31) +#define DXEPCTL_EPDIS			(1 << 30) +#define DXEPCTL_SETD1PID		(1 << 29) +#define DXEPCTL_SETODDFR		(1 << 29) +#define DXEPCTL_SETD0PID		(1 << 28) +#define DXEPCTL_SETEVENFR		(1 << 28) +#define DXEPCTL_SNAK			(1 << 27) +#define DXEPCTL_CNAK			(1 << 26) +#define DXEPCTL_TXFNUM_MASK		(0xf << 22) +#define DXEPCTL_TXFNUM_SHIFT		22 +#define DXEPCTL_TXFNUM_LIMIT		0xf +#define DXEPCTL_TXFNUM(_x)		((_x) << 22) +#define DXEPCTL_STALL			(1 << 21) +#define DXEPCTL_SNP			(1 << 20) +#define DXEPCTL_EPTYPE_MASK		(0x3 << 18) +#define DXEPCTL_EPTYPE_CONTROL		(0x0 << 18) +#define DXEPCTL_EPTYPE_ISO		(0x1 << 18) +#define DXEPCTL_EPTYPE_BULK		(0x2 << 18) +#define DXEPCTL_EPTYPE_INTERRUPT	(0x3 << 18) + +#define DXEPCTL_NAKSTS			(1 << 17) +#define DXEPCTL_DPID			(1 << 16) +#define DXEPCTL_EOFRNUM			(1 << 16) +#define DXEPCTL_USBACTEP		(1 << 15) +#define DXEPCTL_NEXTEP_MASK		(0xf << 11) +#define DXEPCTL_NEXTEP_SHIFT		11 +#define DXEPCTL_NEXTEP_LIMIT		0xf +#define DXEPCTL_NEXTEP(_x)		((_x) << 11) +#define DXEPCTL_MPS_MASK		(0x7ff << 0) +#define DXEPCTL_MPS_SHIFT		0 +#define DXEPCTL_MPS_LIMIT		0x7ff +#define DXEPCTL_MPS(_x)			((_x) << 0) + +#define DIEPINT(_a)			HSOTG_REG(0x908 + ((_a) * 0x20)) +#define DOEPINT(_a)			HSOTG_REG(0xB08 + ((_a) * 0x20)) +#define DXEPINT_INEPNAKEFF		(1 << 6) +#define DXEPINT_BACK2BACKSETUP		(1 << 6) +#define DXEPINT_INTKNEPMIS		(1 << 5) +#define DXEPINT_INTKNTXFEMP		(1 << 4) +#define DXEPINT_OUTTKNEPDIS		(1 << 4) +#define DXEPINT_TIMEOUT			(1 << 3) +#define DXEPINT_SETUP			(1 << 3) +#define DXEPINT_AHBERR			(1 << 2) +#define DXEPINT_EPDISBLD		(1 << 1) +#define DXEPINT_XFERCOMPL		(1 << 0) + +#define DIEPTSIZ0			HSOTG_REG(0x910) +#define DIEPTSIZ0_PKTCNT_MASK		(0x3 << 19) +#define DIEPTSIZ0_PKTCNT_SHIFT		19 +#define DIEPTSIZ0_PKTCNT_LIMIT		0x3 +#define DIEPTSIZ0_PKTCNT(_x)		((_x) << 19) +#define DIEPTSIZ0_XFERSIZE_MASK		(0x7f << 0) +#define DIEPTSIZ0_XFERSIZE_SHIFT	0 +#define DIEPTSIZ0_XFERSIZE_LIMIT	0x7f +#define DIEPTSIZ0_XFERSIZE(_x)		((_x) << 0) + +#define DOEPTSIZ0			HSOTG_REG(0xB10) +#define DOEPTSIZ0_SUPCNT_MASK		(0x3 << 29) +#define DOEPTSIZ0_SUPCNT_SHIFT		29 +#define DOEPTSIZ0_SUPCNT_LIMIT		0x3 +#define DOEPTSIZ0_SUPCNT(_x)		((_x) << 29) +#define DOEPTSIZ0_PKTCNT		(1 << 19) +#define DOEPTSIZ0_XFERSIZE_MASK		(0x7f << 0) +#define DOEPTSIZ0_XFERSIZE_SHIFT	0 + +#define DIEPTSIZ(_a)			HSOTG_REG(0x910 + ((_a) * 0x20)) +#define DOEPTSIZ(_a)			HSOTG_REG(0xB10 + ((_a) * 0x20)) +#define DXEPTSIZ_MC_MASK		(0x3 << 29) +#define DXEPTSIZ_MC_SHIFT		29 +#define DXEPTSIZ_MC_LIMIT		0x3 +#define DXEPTSIZ_MC(_x)			((_x) << 29) +#define DXEPTSIZ_PKTCNT_MASK		(0x3ff << 19) +#define DXEPTSIZ_PKTCNT_SHIFT		19 +#define DXEPTSIZ_PKTCNT_LIMIT		0x3ff +#define DXEPTSIZ_PKTCNT_GET(_v)		(((_v) >> 19) & 0x3ff) +#define DXEPTSIZ_PKTCNT(_x)		((_x) << 19) +#define DXEPTSIZ_XFERSIZE_MASK		(0x7ffff << 0) +#define DXEPTSIZ_XFERSIZE_SHIFT		0 +#define DXEPTSIZ_XFERSIZE_LIMIT		0x7ffff +#define DXEPTSIZ_XFERSIZE_GET(_v)	(((_v) >> 0) & 0x7ffff) +#define DXEPTSIZ_XFERSIZE(_x)		((_x) << 0) + +#define DIEPDMA(_a)			HSOTG_REG(0x914 + ((_a) * 0x20)) +#define DOEPDMA(_a)			HSOTG_REG(0xB14 + ((_a) * 0x20)) + +#define DTXFSTS(_a)			HSOTG_REG(0x918 + ((_a) * 0x20)) + +#define PCGCTL				HSOTG_REG(0x0e00) +#define PCGCTL_IF_DEV_MODE		(1 << 31) +#define PCGCTL_P2HD_PRT_SPD_MASK	(0x3 << 29) +#define PCGCTL_P2HD_PRT_SPD_SHIFT	29 +#define PCGCTL_P2HD_DEV_ENUM_SPD_MASK	(0x3 << 27) +#define PCGCTL_P2HD_DEV_ENUM_SPD_SHIFT	27 +#define PCGCTL_MAC_DEV_ADDR_MASK	(0x7f << 20) +#define PCGCTL_MAC_DEV_ADDR_SHIFT	20 +#define PCGCTL_MAX_TERMSEL		(1 << 19) +#define PCGCTL_MAX_XCVRSELECT_MASK	(0x3 << 17) +#define PCGCTL_MAX_XCVRSELECT_SHIFT	17 +#define PCGCTL_PORT_POWER		(1 << 16) +#define PCGCTL_PRT_CLK_SEL_MASK		(0x3 << 14) +#define PCGCTL_PRT_CLK_SEL_SHIFT	14 +#define PCGCTL_ESS_REG_RESTORED		(1 << 13) +#define PCGCTL_EXTND_HIBER_SWITCH	(1 << 12) +#define PCGCTL_EXTND_HIBER_PWRCLMP	(1 << 11) +#define PCGCTL_ENBL_EXTND_HIBER		(1 << 10) +#define PCGCTL_RESTOREMODE		(1 << 9) +#define PCGCTL_RESETAFTSUSP		(1 << 8) +#define PCGCTL_DEEP_SLEEP		(1 << 7) +#define PCGCTL_PHY_IN_SLEEP		(1 << 6) +#define PCGCTL_ENBL_SLEEP_GATING	(1 << 5) +#define PCGCTL_RSTPDWNMODULE		(1 << 3) +#define PCGCTL_PWRCLMP			(1 << 2) +#define PCGCTL_GATEHCLK			(1 << 1) +#define PCGCTL_STOPPCLK			(1 << 0) + +#define EPFIFO(_a)			HSOTG_REG(0x1000 + ((_a) * 0x1000)) + +/* Host Mode Registers */ + +#define HCFG				HSOTG_REG(0x0400) +#define HCFG_MODECHTIMEN		(1 << 31) +#define HCFG_PERSCHEDENA		(1 << 26) +#define HCFG_FRLISTEN_MASK		(0x3 << 24) +#define HCFG_FRLISTEN_SHIFT		24 +#define HCFG_FRLISTEN_8				(0 << 24) +#define FRLISTEN_8_SIZE				8 +#define HCFG_FRLISTEN_16			(1 << 24) +#define FRLISTEN_16_SIZE			16 +#define HCFG_FRLISTEN_32			(2 << 24) +#define FRLISTEN_32_SIZE			32 +#define HCFG_FRLISTEN_64			(3 << 24) +#define FRLISTEN_64_SIZE			64 +#define HCFG_DESCDMA			(1 << 23) +#define HCFG_RESVALID_MASK		(0xff << 8) +#define HCFG_RESVALID_SHIFT		8 +#define HCFG_ENA32KHZ			(1 << 7) +#define HCFG_FSLSSUPP			(1 << 2) +#define HCFG_FSLSPCLKSEL_MASK		(0x3 << 0) +#define HCFG_FSLSPCLKSEL_SHIFT		0 +#define HCFG_FSLSPCLKSEL_30_60_MHZ	0 +#define HCFG_FSLSPCLKSEL_48_MHZ		1 +#define HCFG_FSLSPCLKSEL_6_MHZ		2 + +#define HFIR				HSOTG_REG(0x0404) +#define HFIR_FRINT_MASK			(0xffff << 0) +#define HFIR_FRINT_SHIFT		0 +#define HFIR_RLDCTRL			(1 << 16) + +#define HFNUM				HSOTG_REG(0x0408) +#define HFNUM_FRREM_MASK		(0xffff << 16) +#define HFNUM_FRREM_SHIFT		16 +#define HFNUM_FRNUM_MASK		(0xffff << 0) +#define HFNUM_FRNUM_SHIFT		0 +#define HFNUM_MAX_FRNUM			0x3fff + +#define HPTXSTS				HSOTG_REG(0x0410) +#define TXSTS_QTOP_ODD			(1 << 31) +#define TXSTS_QTOP_CHNEP_MASK		(0xf << 27) +#define TXSTS_QTOP_CHNEP_SHIFT		27 +#define TXSTS_QTOP_TOKEN_MASK		(0x3 << 25) +#define TXSTS_QTOP_TOKEN_SHIFT		25 +#define TXSTS_QTOP_TERMINATE		(1 << 24) +#define TXSTS_QSPCAVAIL_MASK		(0xff << 16) +#define TXSTS_QSPCAVAIL_SHIFT		16 +#define TXSTS_FSPCAVAIL_MASK		(0xffff << 0) +#define TXSTS_FSPCAVAIL_SHIFT		0 + +#define HAINT				HSOTG_REG(0x0414) +#define HAINTMSK			HSOTG_REG(0x0418) +#define HFLBADDR			HSOTG_REG(0x041c) + +#define HPRT0				HSOTG_REG(0x0440) +#define HPRT0_SPD_MASK			(0x3 << 17) +#define HPRT0_SPD_SHIFT			17 +#define HPRT0_SPD_HIGH_SPEED		0 +#define HPRT0_SPD_FULL_SPEED		1 +#define HPRT0_SPD_LOW_SPEED		2 +#define HPRT0_TSTCTL_MASK		(0xf << 13) +#define HPRT0_TSTCTL_SHIFT		13 +#define HPRT0_PWR			(1 << 12) +#define HPRT0_LNSTS_MASK		(0x3 << 10) +#define HPRT0_LNSTS_SHIFT		10 +#define HPRT0_RST			(1 << 8) +#define HPRT0_SUSP			(1 << 7) +#define HPRT0_RES			(1 << 6) +#define HPRT0_OVRCURRCHG		(1 << 5) +#define HPRT0_OVRCURRACT		(1 << 4) +#define HPRT0_ENACHG			(1 << 3) +#define HPRT0_ENA			(1 << 2) +#define HPRT0_CONNDET			(1 << 1) +#define HPRT0_CONNSTS			(1 << 0) + +#define HCCHAR(_ch)			HSOTG_REG(0x0500 + 0x20 * (_ch)) +#define HCCHAR_CHENA			(1 << 31) +#define HCCHAR_CHDIS			(1 << 30) +#define HCCHAR_ODDFRM			(1 << 29) +#define HCCHAR_DEVADDR_MASK		(0x7f << 22) +#define HCCHAR_DEVADDR_SHIFT		22 +#define HCCHAR_MULTICNT_MASK		(0x3 << 20) +#define HCCHAR_MULTICNT_SHIFT		20 +#define HCCHAR_EPTYPE_MASK		(0x3 << 18) +#define HCCHAR_EPTYPE_SHIFT		18 +#define HCCHAR_LSPDDEV			(1 << 17) +#define HCCHAR_EPDIR			(1 << 15) +#define HCCHAR_EPNUM_MASK		(0xf << 11) +#define HCCHAR_EPNUM_SHIFT		11 +#define HCCHAR_MPS_MASK			(0x7ff << 0) +#define HCCHAR_MPS_SHIFT		0 + +#define HCSPLT(_ch)			HSOTG_REG(0x0504 + 0x20 * (_ch)) +#define HCSPLT_SPLTENA			(1 << 31) +#define HCSPLT_COMPSPLT			(1 << 16) +#define HCSPLT_XACTPOS_MASK		(0x3 << 14) +#define HCSPLT_XACTPOS_SHIFT		14 +#define HCSPLT_XACTPOS_MID		0 +#define HCSPLT_XACTPOS_END		1 +#define HCSPLT_XACTPOS_BEGIN		2 +#define HCSPLT_XACTPOS_ALL		3 +#define HCSPLT_HUBADDR_MASK		(0x7f << 7) +#define HCSPLT_HUBADDR_SHIFT		7 +#define HCSPLT_PRTADDR_MASK		(0x7f << 0) +#define HCSPLT_PRTADDR_SHIFT		0 + +#define HCINT(_ch)			HSOTG_REG(0x0508 + 0x20 * (_ch)) +#define HCINTMSK(_ch)			HSOTG_REG(0x050c + 0x20 * (_ch)) +#define HCINTMSK_RESERVED14_31		(0x3ffff << 14) +#define HCINTMSK_FRM_LIST_ROLL		(1 << 13) +#define HCINTMSK_XCS_XACT		(1 << 12) +#define HCINTMSK_BNA			(1 << 11) +#define HCINTMSK_DATATGLERR		(1 << 10) +#define HCINTMSK_FRMOVRUN		(1 << 9) +#define HCINTMSK_BBLERR			(1 << 8) +#define HCINTMSK_XACTERR		(1 << 7) +#define HCINTMSK_NYET			(1 << 6) +#define HCINTMSK_ACK			(1 << 5) +#define HCINTMSK_NAK			(1 << 4) +#define HCINTMSK_STALL			(1 << 3) +#define HCINTMSK_AHBERR			(1 << 2) +#define HCINTMSK_CHHLTD			(1 << 1) +#define HCINTMSK_XFERCOMPL		(1 << 0) + +#define HCTSIZ(_ch)			HSOTG_REG(0x0510 + 0x20 * (_ch)) +#define TSIZ_DOPNG			(1 << 31) +#define TSIZ_SC_MC_PID_MASK		(0x3 << 29) +#define TSIZ_SC_MC_PID_SHIFT		29 +#define TSIZ_SC_MC_PID_DATA0		0 +#define TSIZ_SC_MC_PID_DATA2		1 +#define TSIZ_SC_MC_PID_DATA1		2 +#define TSIZ_SC_MC_PID_MDATA		3 +#define TSIZ_SC_MC_PID_SETUP		3 +#define TSIZ_PKTCNT_MASK		(0x3ff << 19) +#define TSIZ_PKTCNT_SHIFT		19 +#define TSIZ_NTD_MASK			(0xff << 8) +#define TSIZ_NTD_SHIFT			8 +#define TSIZ_SCHINFO_MASK		(0xff << 0) +#define TSIZ_SCHINFO_SHIFT		0 +#define TSIZ_XFERSIZE_MASK		(0x7ffff << 0) +#define TSIZ_XFERSIZE_SHIFT		0 + +#define HCDMA(_ch)			HSOTG_REG(0x0514 + 0x20 * (_ch)) +#define HCDMA_DMA_ADDR_MASK		(0x1fffff << 11) +#define HCDMA_DMA_ADDR_SHIFT		11 +#define HCDMA_CTD_MASK			(0xff << 3) +#define HCDMA_CTD_SHIFT			3 + +#define HCDMAB(_ch)			HSOTG_REG(0x051c + 0x20 * (_ch)) + +#define HCFIFO(_ch)			HSOTG_REG(0x1000 + 0x1000 * (_ch)) + +/** + * struct dwc2_hcd_dma_desc - Host-mode DMA descriptor structure + * + * @status: DMA descriptor status quadlet + * @buf:    DMA descriptor data buffer pointer + * + * DMA Descriptor structure contains two quadlets: + * Status quadlet and Data buffer pointer. + */ +struct dwc2_hcd_dma_desc { +	u32 status; +	u32 buf; +}; + +#define HOST_DMA_A			(1 << 31) +#define HOST_DMA_STS_MASK		(0x3 << 28) +#define HOST_DMA_STS_SHIFT		28 +#define HOST_DMA_STS_PKTERR		(1 << 28) +#define HOST_DMA_EOL			(1 << 26) +#define HOST_DMA_IOC			(1 << 25) +#define HOST_DMA_SUP			(1 << 24) +#define HOST_DMA_ALT_QTD		(1 << 23) +#define HOST_DMA_QTD_OFFSET_MASK	(0x3f << 17) +#define HOST_DMA_QTD_OFFSET_SHIFT	17 +#define HOST_DMA_ISOC_NBYTES_MASK	(0xfff << 0) +#define HOST_DMA_ISOC_NBYTES_SHIFT	0 +#define HOST_DMA_NBYTES_MASK		(0x1ffff << 0) +#define HOST_DMA_NBYTES_SHIFT		0 + +#define MAX_DMA_DESC_SIZE		131071 +#define MAX_DMA_DESC_NUM_GENERIC	64 +#define MAX_DMA_DESC_NUM_HS_ISOC	256 + +#endif /* __DWC2_HW_H__ */ diff --git a/drivers/usb/dwc2/pci.c b/drivers/usb/dwc2/pci.c new file mode 100644 index 00000000000..c291fca5d21 --- /dev/null +++ b/drivers/usb/dwc2/pci.c @@ -0,0 +1,178 @@ +/* + * pci.c - DesignWare HS OTG Controller PCI driver + * + * Copyright (C) 2004-2013 Synopsys, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Provides the initialization and cleanup entry points for the DWC_otg PCI + * driver + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/usb.h> + +#include <linux/usb/hcd.h> +#include <linux/usb/ch11.h> + +#include "core.h" +#include "hcd.h" + +#define PCI_VENDOR_ID_SYNOPSYS		0x16c3 +#define PCI_PRODUCT_ID_HAPS_HSOTG	0xabc0 + +static const char dwc2_driver_name[] = "dwc2"; + +static const struct dwc2_core_params dwc2_module_params = { +	.otg_cap			= -1, +	.otg_ver			= -1, +	.dma_enable			= -1, +	.dma_desc_enable		= 0, +	.speed				= -1, +	.enable_dynamic_fifo		= -1, +	.en_multiple_tx_fifo		= -1, +	.host_rx_fifo_size		= 1024, +	.host_nperio_tx_fifo_size	= 256, +	.host_perio_tx_fifo_size	= 1024, +	.max_transfer_size		= 65535, +	.max_packet_count		= 511, +	.host_channels			= -1, +	.phy_type			= -1, +	.phy_utmi_width			= -1, +	.phy_ulpi_ddr			= -1, +	.phy_ulpi_ext_vbus		= -1, +	.i2c_enable			= -1, +	.ulpi_fs_ls			= -1, +	.host_support_fs_ls_low_power	= -1, +	.host_ls_low_power_phy_clk	= -1, +	.ts_dline			= -1, +	.reload_ctl			= -1, +	.ahbcfg				= -1, +	.uframe_sched			= -1, +}; + +/** + * dwc2_driver_remove() - Called when the DWC_otg core is unregistered with the + * DWC_otg driver + * + * @dev: Bus device + * + * This routine is called, 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 void dwc2_driver_remove(struct pci_dev *dev) +{ +	struct dwc2_hsotg *hsotg = pci_get_drvdata(dev); + +	dwc2_hcd_remove(hsotg); +	pci_disable_device(dev); +} + +/** + * dwc2_driver_probe() - Called when the DWC_otg core is bound to the DWC_otg + * driver + * + * @dev: Bus device + * + * This routine creates the driver components required to control the device + * (core, HCD, and PCD) and initializes the device. The driver components are + * stored in a dwc2_hsotg structure. A reference to the dwc2_hsotg is saved + * in the device private data. This allows the driver to access the dwc2_hsotg + * structure on subsequent calls to driver methods for this device. + */ +static int dwc2_driver_probe(struct pci_dev *dev, +			     const struct pci_device_id *id) +{ +	struct dwc2_hsotg *hsotg; +	int retval; + +	hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL); +	if (!hsotg) +		return -ENOMEM; + +	hsotg->dev = &dev->dev; +	hsotg->regs = devm_ioremap_resource(&dev->dev, &dev->resource[0]); +	if (IS_ERR(hsotg->regs)) +		return PTR_ERR(hsotg->regs); + +	dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", +		(unsigned long)pci_resource_start(dev, 0), hsotg->regs); + +	if (pci_enable_device(dev) < 0) +		return -ENODEV; + +	pci_set_master(dev); + +	retval = dwc2_hcd_init(hsotg, dev->irq, &dwc2_module_params); +	if (retval) { +		pci_disable_device(dev); +		return retval; +	} + +	pci_set_drvdata(dev, hsotg); + +	return retval; +} + +static const struct pci_device_id dwc2_pci_ids[] = { +	{ +		PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, PCI_PRODUCT_ID_HAPS_HSOTG), +	}, +	{ +		PCI_DEVICE(PCI_VENDOR_ID_STMICRO, +			   PCI_DEVICE_ID_STMICRO_USB_OTG), +	}, +	{ /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, dwc2_pci_ids); + +static struct pci_driver dwc2_pci_driver = { +	.name = dwc2_driver_name, +	.id_table = dwc2_pci_ids, +	.probe = dwc2_driver_probe, +	.remove = dwc2_driver_remove, +}; + +module_pci_driver(dwc2_pci_driver); + +MODULE_DESCRIPTION("DESIGNWARE HS OTG PCI Bus Glue"); +MODULE_AUTHOR("Synopsys, Inc."); +MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c new file mode 100644 index 00000000000..a10e7a35357 --- /dev/null +++ b/drivers/usb/dwc2/platform.c @@ -0,0 +1,196 @@ +/* + * platform.c - DesignWare HS OTG Controller platform driver + * + * Copyright (C) Matthijs Kooijman <matthijs@stdin.nl> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + *    notice, this list of conditions, and the following disclaimer, + *    without modification. + * 2. Redistributions in binary form must reproduce the above copyright + *    notice, this list of conditions and the following disclaimer in the + *    documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + *    to endorse or promote products derived from this software without + *    specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +#include "core.h" +#include "hcd.h" + +static const char dwc2_driver_name[] = "dwc2"; + +static const struct dwc2_core_params params_bcm2835 = { +	.otg_cap			= 0,	/* HNP/SRP capable */ +	.otg_ver			= 0,	/* 1.3 */ +	.dma_enable			= 1, +	.dma_desc_enable		= 0, +	.speed				= 0,	/* High Speed */ +	.enable_dynamic_fifo		= 1, +	.en_multiple_tx_fifo		= 1, +	.host_rx_fifo_size		= 774,	/* 774 DWORDs */ +	.host_nperio_tx_fifo_size	= 256,	/* 256 DWORDs */ +	.host_perio_tx_fifo_size	= 512,	/* 512 DWORDs */ +	.max_transfer_size		= 65535, +	.max_packet_count		= 511, +	.host_channels			= 8, +	.phy_type			= 1,	/* UTMI */ +	.phy_utmi_width			= 8,	/* 8 bits */ +	.phy_ulpi_ddr			= 0,	/* Single */ +	.phy_ulpi_ext_vbus		= 0, +	.i2c_enable			= 0, +	.ulpi_fs_ls			= 0, +	.host_support_fs_ls_low_power	= 0, +	.host_ls_low_power_phy_clk	= 0,	/* 48 MHz */ +	.ts_dline			= 0, +	.reload_ctl			= 0, +	.ahbcfg				= 0x10, +	.uframe_sched			= 0, +}; + +/** + * dwc2_driver_remove() - Called when the DWC_otg core is unregistered with the + * DWC_otg driver + * + * @dev: Platform device + * + * This routine is called, 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 dwc2_driver_remove(struct platform_device *dev) +{ +	struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); + +	dwc2_hcd_remove(hsotg); + +	return 0; +} + +static const struct of_device_id dwc2_of_match_table[] = { +	{ .compatible = "brcm,bcm2835-usb", .data = ¶ms_bcm2835 }, +	{ .compatible = "snps,dwc2", .data = NULL }, +	{}, +}; +MODULE_DEVICE_TABLE(of, dwc2_of_match_table); + +/** + * dwc2_driver_probe() - Called when the DWC_otg core is bound to the DWC_otg + * driver + * + * @dev: Platform device + * + * This routine creates the driver components required to control the device + * (core, HCD, and PCD) and initializes the device. The driver components are + * stored in a dwc2_hsotg structure. A reference to the dwc2_hsotg is saved + * in the device private data. This allows the driver to access the dwc2_hsotg + * structure on subsequent calls to driver methods for this device. + */ +static int dwc2_driver_probe(struct platform_device *dev) +{ +	const struct of_device_id *match; +	const struct dwc2_core_params *params; +	struct dwc2_core_params defparams; +	struct dwc2_hsotg *hsotg; +	struct resource *res; +	int retval; +	int irq; + +	if (usb_disabled()) +		return -ENODEV; + +	match = of_match_device(dwc2_of_match_table, &dev->dev); +	if (match && match->data) { +		params = match->data; +	} else { +		/* Default all params to autodetect */ +		dwc2_set_all_params(&defparams, -1); +		params = &defparams; + +		/* +		 * Disable descriptor dma mode by default as the HW can support +		 * it, but does not support it for SPLIT transactions. +		 */ +		defparams.dma_desc_enable = 0; +	} + +	hsotg = devm_kzalloc(&dev->dev, sizeof(*hsotg), GFP_KERNEL); +	if (!hsotg) +		return -ENOMEM; + +	hsotg->dev = &dev->dev; + +	/* +	 * Use reasonable defaults so platforms don't have to provide these. +	 */ +	if (!dev->dev.dma_mask) +		dev->dev.dma_mask = &dev->dev.coherent_dma_mask; +	retval = dma_set_coherent_mask(&dev->dev, DMA_BIT_MASK(32)); +	if (retval) +		return retval; + +	irq = platform_get_irq(dev, 0); +	if (irq < 0) { +		dev_err(&dev->dev, "missing IRQ resource\n"); +		return irq; +	} + +	res = platform_get_resource(dev, IORESOURCE_MEM, 0); +	hsotg->regs = devm_ioremap_resource(&dev->dev, res); +	if (IS_ERR(hsotg->regs)) +		return PTR_ERR(hsotg->regs); + +	dev_dbg(&dev->dev, "mapped PA %08lx to VA %p\n", +		(unsigned long)res->start, hsotg->regs); + +	retval = dwc2_hcd_init(hsotg, irq, params); +	if (retval) +		return retval; + +	platform_set_drvdata(dev, hsotg); + +	return retval; +} + +static struct platform_driver dwc2_platform_driver = { +	.driver = { +		.name = dwc2_driver_name, +		.of_match_table = dwc2_of_match_table, +	}, +	.probe = dwc2_driver_probe, +	.remove = dwc2_driver_remove, +}; + +module_platform_driver(dwc2_platform_driver); + +MODULE_DESCRIPTION("DESIGNWARE HS OTG Platform Glue"); +MODULE_AUTHOR("Matthijs Kooijman <matthijs@stdin.nl>"); +MODULE_LICENSE("Dual BSD/GPL");  | 
