| // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) |
| /* |
| * 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. |
| */ |
| #include "diag_common.h" |
| #include "diag_misc.h" |
| #include "Galois_memmap.h" |
| #include "soc.h" |
| #include "diag_gic.h" |
| #include "diag_cpu.h" |
| #include "diag_misc.h" |
| #include "global.h" |
| #include "usb_memmap.h" |
| #include "hw.h" |
| #include "core.h" |
| #include "util.h" |
| |
| #define MAX_RETRY 10 |
| |
| int g_msg_queue = 0; |
| const hcd_dwc2_ops_t *dwc2_msg; |
| struct dwc2_hsotg dwc2_drv; |
| |
| unsigned long g_current_pipe = 0; |
| uint8_t *aligned_buffer_addr=(uint8_t *)USB_MASS_BULK_DATA_BASE; |
| uint8_t *status_buffer_addr=(uint8_t *)USB_MASS_BULK_CSW_BASE; |
| volatile struct dwc2_core_regs *g_regs = (struct dwc2_core_regs *)ACPU_MEMMAP_USBAHB_REG_BASE; |
| static int g_got_hc_int = 0; |
| int g_dwc2_chhltd_int_en = 1; |
| int g_dwc2_dma_mode=0; |
| int g_dwc2_host_channel = 0; |
| u8 *g_pid = 0; |
| u32 *g_hctsiz = 0; |
| unsigned int g_port_reset_delay = 100; // 100 x 10 ms , 1 second |
| unsigned int g_prtrst_duration_ms = 1000; // 1 second for port reset pulse duration, fix compatibility with some USB3.0 drive |
| static int dwc2_eptype[] = { |
| DWC2_HCCHAR_EPTYPE_ISOC, |
| DWC2_HCCHAR_EPTYPE_INTR, |
| DWC2_HCCHAR_EPTYPE_CONTROL, |
| DWC2_HCCHAR_EPTYPE_BULK, |
| }; |
| int root_hub_devnum; |
| |
| int _dwc2_clrbits_le32(volatile u32 *addr, unsigned long clear) |
| { |
| unsigned long data; |
| data = readl(addr); |
| data &= ~clear; |
| writel(data, addr); |
| return 0; |
| } |
| |
| int _dwc2_setbits_le32(volatile u32 *addr, unsigned long set) |
| { |
| unsigned long data; |
| data = readl(addr); |
| data |= set; |
| writel(data, addr); |
| return 0; |
| } |
| |
| int _dwc2_clrsetbits_le32(volatile u32 *addr, unsigned long clear, unsigned long set) |
| { |
| _dwc2_clrbits_le32(addr, clear); |
| _dwc2_setbits_le32(addr, set); |
| return 0; |
| } |
| |
| int _dwc2_wait_for_bit_le32(const u32 *reg, u32 mask, const bool set,const unsigned int timeout_ms, const bool breakable) |
| { |
| u32 val; |
| int count_us=0; |
| UNUSED(breakable); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"_dwc2_wait_for_bit_le32: addr=0x%x mask=0x%x, set=%d, timeout_ms=%d..", reg, mask, set, timeout_ms); |
| while (1) { |
| val = readl(reg); |
| if (!set) |
| val = ~val; |
| if ((val & mask) == mask){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"done!\n"); |
| return 0; |
| } |
| if ((count_us/100) > (int)timeout_ms) |
| break; |
| udelay(1); |
| count_us++; |
| } |
| dbg_printf(PRN_INFO | PRN_BUFFERED, "_dwc2_wait_for_bit_le32: Timeout (reg=%p mask=%08x wait_set=%i)\n", reg, mask, set); |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * dwc2_hsotg_wait_bit_set - Waits for bit to be set. |
| * @hsotg: Programming view of DWC_otg controller. |
| * @offset: Register's offset where bit/bits must be set. |
| * @mask: Mask of the bit/bits which must be set. |
| * @timeout: Timeout to wait. |
| * |
| * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. |
| */ |
| int dwc2_hsotg_wait_bit_set(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, |
| u32 timeout) |
| { |
| u32 i; |
| |
| for (i = 0; i < timeout; i++) { |
| if (dwc2_readl(hsotg, offset) & mask) |
| return 0; |
| udelay(1); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * dwc2_hsotg_wait_bit_clear - Waits for bit to be clear. |
| * @hsotg: Programming view of DWC_otg controller. |
| * @offset: Register's offset where bit/bits must be set. |
| * @mask: Mask of the bit/bits which must be set. |
| * @timeout: Timeout to wait. |
| * |
| * Return: 0 if bit/bits are set or -ETIMEDOUT in case of timeout. |
| */ |
| int dwc2_hsotg_wait_bit_clear(struct dwc2_hsotg *hsotg, u32 offset, u32 mask, |
| u32 timeout) |
| { |
| u32 i; |
| |
| for (i = 0; i < timeout; i++) { |
| if (!(dwc2_readl(hsotg, offset) & mask)) |
| return 0; |
| udelay(1); |
| } |
| |
| return -ETIMEDOUT; |
| } |
| |
| /** |
| * 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; |
| |
| debug("Flush Tx FIFO %d\n", num); |
| |
| /* Wait for AHB master IDLE state */ |
| if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) |
| debug("%s: HANG! AHB Idle GRSCTL\n", |
| __func__); |
| |
| greset = GRSTCTL_TXFFLSH; |
| greset |= num << GRSTCTL_TXFNUM_SHIFT & GRSTCTL_TXFNUM_MASK; |
| dwc2_writel(hsotg, greset, GRSTCTL); |
| |
| if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_TXFFLSH, 10000)) |
| debug("%s: HANG! timeout GRSTCTL GRSTCTL_TXFFLSH\n", |
| __func__); |
| |
| /* 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; |
| |
| debug("%s()\n", __func__); |
| |
| /* Wait for AHB master IDLE state */ |
| if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) |
| debug("%s: HANG! AHB Idle GRSCTL\n", |
| __func__); |
| |
| greset = GRSTCTL_RXFFLSH; |
| dwc2_writel(hsotg, greset, GRSTCTL); |
| |
| /* Wait for RxFIFO flush done */ |
| if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_RXFFLSH, 10000)) |
| debug("%s: HANG! timeout GRSTCTL GRSTCTL_RXFFLSH\n", |
| __func__); |
| |
| /* Wait for at least 3 PHY Clocks */ |
| udelay(1); |
| } |
| |
| static void dwc2_set_dma_mode() |
| { |
| if ((g_dwc2_dma_mode)&&(!(g_regs->host_regs.hcfg & DWC2_HCFG_DESCDMA))){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"HCFG: DESC mode\n"); |
| _dwc2_setbits_le32(&g_regs->host_regs.hcfg, DWC2_HCFG_DESCDMA); |
| } |
| else if ((!g_dwc2_dma_mode) && (g_regs->host_regs.hcfg & DWC2_HCFG_DESCDMA)){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"HCFG: BUFFERED DMA mode\n"); |
| _dwc2_clrbits_le32(&g_regs->host_regs.hcfg, DWC2_HCFG_DESCDMA); |
| } |
| } |
| |
| /* |
| * Do core a soft reset of the core. Be careful with this because it |
| * resets all the internal state machines of the core. |
| */ |
| int dwc2_core_reset(struct dwc2_hsotg *hsotg) |
| { |
| u32 greset; |
| //bool wait_for_host_mode = false; |
| |
| debug("%s()\n", __func__); |
| |
| /* |
| * If the current mode is host, either due to the force mode |
| * bit being set (which persists after core reset) or the |
| * connector id pin, a core soft reset will temporarily reset |
| * the mode to device. A delay from the IDDIG debounce filter |
| * will occur before going back to host mode. |
| * |
| * Determine whether we will go back into host mode after a |
| * reset and account for this delay after the reset. |
| */ |
| |
| |
| /* Core Soft Reset */ |
| greset = dwc2_readl(hsotg, GRSTCTL); |
| greset |= GRSTCTL_CSFTRST; |
| dwc2_writel(hsotg, greset, GRSTCTL); |
| |
| if (dwc2_hsotg_wait_bit_clear(hsotg, GRSTCTL, GRSTCTL_CSFTRST, 50)) { |
| debug("%s: HANG! Soft Reset timeout GRSTCTL GRSTCTL_CSFTRST\n", |
| __func__); |
| return -EBUSY; |
| } |
| |
| /* Wait for AHB master IDLE state */ |
| if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 50)) { |
| debug("%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n", |
| __func__); |
| return -EBUSY; |
| } |
| |
| /* |
| * Wait for core to come out of reset. |
| * NOTE: This long sleep is _very_ important, otherwise the core will |
| * not stay in host mode after a connector ID change! |
| */ |
| mdelay(10/*100*/); // TIA TEMP : Reduce |
| |
| mdelay(100); |
| |
| return 0; |
| } |
| |
| int dwc2_gahbcfg_init(struct dwc2_hsotg *hsotg) |
| { |
| uint8_t brst_sz = CONFIG_DWC2_DMA_BURST_SIZE; |
| uint32_t ahbcfg = 0; |
| |
| |
| /* Program the GAHBCFG Register. */ |
| switch (dwc2_readl(hsotg, GHWCFG2) & DWC2_HWCFG2_ARCHITECTURE_MASK) { |
| case DWC2_HWCFG2_ARCHITECTURE_SLAVE_ONLY: |
| break; |
| |
| case DWC2_HWCFG2_ARCHITECTURE_EXT_DMA: |
| while (brst_sz > 1) { |
| ahbcfg |= ahbcfg + (1 << DWC2_GAHBCFG_HBURSTLEN_OFFSET); |
| ahbcfg &= DWC2_GAHBCFG_HBURSTLEN_MASK; |
| brst_sz >>= 1; |
| } |
| ahbcfg |= DWC2_GAHBCFG_DMAENABLE; |
| break; |
| |
| case DWC2_HWCFG2_ARCHITECTURE_INT_DMA: |
| ahbcfg |= DWC2_GAHBCFG_HBURSTLEN_INCR4; |
| ahbcfg |= DWC2_GAHBCFG_DMAENABLE; |
| break; |
| } |
| |
| dwc2_writel(hsotg, ahbcfg, GAHBCFG); |
| return 0; |
| } |
| |
| static int dwc2_hs_phy_init(struct dwc2_hsotg *hsotg) |
| { |
| u32 usbcfg; |
| int retval = 0; |
| |
| usbcfg = dwc2_readl(hsotg, GUSBCFG); |
| /* High speed PHY */ |
| usbcfg &= ~(DWC2_GUSBCFG_ULPI_UTMI_SEL | DWC2_GUSBCFG_PHYIF); |
| usbcfg &= DWC2_GUSBCFG_ULPI_UTMI_SEL_OFFSET; /* UTMI+ SELECTION */ |
| |
| /* UTMI+ interface 0-8bits, 1-16bits*/ |
| //usbcfg |= DWC2_GUSBCFG_PHYIF; |
| |
| dwc2_writel(hsotg, usbcfg, GUSBCFG); |
| |
| /* Reset after setting the PHY parameters */ |
| retval = dwc2_core_reset(hsotg); |
| if (retval) { |
| debug("%s(): Reset failed, aborting\n", |
| __func__); |
| return retval; |
| } |
| |
| usbcfg = dwc2_readl(hsotg, GUSBCFG); |
| usbcfg &= ~(DWC2_GUSBCFG_ULPI_FSLS | DWC2_GUSBCFG_ULPI_CLK_SUS_M); |
| |
| if (hsotg->hnp_srp_disable){ |
| usbcfg |= DWC2_GUSBCFG_FORCEHOSTMODE; |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"Force HOST MODE\n"); |
| } |
| |
| dwc2_writel(hsotg, usbcfg, GUSBCFG); |
| return retval; |
| } |
| |
| /* |
| * 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; |
| |
| /* High speed PHY running at full speed or high speed */ |
| val = HCFG_FSLSPCLKSEL_30_60_MHZ; |
| |
| debug("Initializing HCFG.FSLSPClkSel to %08x\n", val); |
| hcfg = dwc2_readl(hsotg, HCFG); |
| hcfg &= ~HCFG_FSLSPCLKSEL_MASK; |
| hcfg |= val << HCFG_FSLSPCLKSEL_SHIFT; |
| dwc2_writel(hsotg, hcfg, HCFG); |
| } |
| |
| static void dwc2_config_fifos(struct dwc2_hsotg *hsotg) |
| { |
| uint32_t nptxfifosize = 0; |
| uint32_t ptxfifosize = 0; |
| |
| if (dwc2_readl(hsotg, GHWCFG2) & GHWCFG2_DYNAMIC_FIFO) { |
| /* Rx FIFO */ |
| dwc2_writel(hsotg, CONFIG_DWC2_HOST_RX_FIFO_SIZE, GRXFSIZ); |
| |
| /* Non-periodic Tx FIFO */ |
| nptxfifosize |= CONFIG_DWC2_HOST_NPERIO_TX_FIFO_SIZE << |
| DWC2_FIFOSIZE_DEPTH_OFFSET; |
| nptxfifosize |= CONFIG_DWC2_HOST_RX_FIFO_SIZE << |
| DWC2_FIFOSIZE_STARTADDR_OFFSET; |
| dwc2_writel(hsotg, nptxfifosize, GNPTXFSIZ); |
| |
| /* Periodic Tx FIFO */ |
| ptxfifosize |= CONFIG_DWC2_HOST_PERIO_TX_FIFO_SIZE << |
| DWC2_FIFOSIZE_DEPTH_OFFSET; |
| ptxfifosize |= (CONFIG_DWC2_HOST_RX_FIFO_SIZE + |
| CONFIG_DWC2_HOST_NPERIO_TX_FIFO_SIZE) << |
| DWC2_FIFOSIZE_STARTADDR_OFFSET; |
| dwc2_writel(hsotg, ptxfifosize, HPTXFSIZ); |
| } |
| } |
| |
| /* |
| * 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 u32 dwc2_read_hprt0(struct dwc2_hsotg *hsotg) |
| { |
| u32 hprt0 = dwc2_readl(hsotg, HPRT0); |
| |
| hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG); |
| return hprt0; |
| } |
| |
| int dwc2_core_init(struct dwc2_hsotg *hsotg) |
| { |
| u32 usbcfg; |
| int retval; |
| |
| debug("%s(%p)\n", __func__, hsotg); |
| |
| usbcfg = dwc2_readl(hsotg, GUSBCFG); |
| |
| /* Set ULPI External VBUS bit if needed */ |
| usbcfg &= ~GUSBCFG_ULPI_EXT_VBUS_DRV; |
| if (hsotg->phy_ulpi_ext_vbus) |
| usbcfg |= GUSBCFG_ULPI_EXT_VBUS_DRV; |
| |
| /* Set external TS Dline pulsing bit if needed */ |
| usbcfg &= ~GUSBCFG_TERMSELDLPULSE; |
| if (hsotg->ts_dline) |
| usbcfg |= GUSBCFG_TERMSELDLPULSE; |
| |
| dwc2_writel(hsotg, usbcfg, GUSBCFG); |
| |
| /* |
| * Reset the Controller |
| * |
| * We only need to reset the controller if this is a re-init. |
| * For the first init we know for sure that earlier code reset us (it |
| * needed to in order to properly detect various parameters). |
| */ |
| retval = dwc2_core_reset(hsotg); |
| if (retval) { |
| debug("%s(): Reset failed, aborting\n", |
| __func__); |
| return retval; |
| } |
| |
| |
| |
| /* |
| * This needs to happen in FS mode before any other programming occurs |
| */ |
| retval = dwc2_hs_phy_init(hsotg); |
| if (retval) |
| return retval; |
| |
| |
| debug("Waiting for ginsts b0 set to 1 (acknowledge host mode), gusbcfg=0x%x\n", dwc2_readl(hsotg, GUSBCFG)); |
| while( !(dwc2_readl(hsotg, GINTSTS) & 0x1)){ |
| mdelay(10); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"."); |
| } |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"HOST mode acknowledged!..continue\n"); |
| |
| |
| /* Program the GAHBCFG Register */ |
| retval = dwc2_gahbcfg_init(hsotg); |
| if (retval) |
| return retval; |
| |
| /* Program the GUSBCFG register */ |
| //dwc2_gusbcfg_init(hsotg); |
| mdelay(1); |
| |
| /* Program the GOTGCTL register */ |
| //otgctl = dwc2_readl(hsotg, GOTGCTL); |
| //otgctl &= ~GOTGCTL_OTGVER; |
| //dwc2_writel(hsotg, otgctl, GOTGCTL); |
| |
| /* Clear the SRP success bit for FS-I2c */ |
| //hsotg->srp_success = 0; |
| |
| /* Enable common interrupts */ |
| //dwc2_enable_common_interrupts(hsotg); |
| |
| return 0; |
| } |
| |
| int dwc2_core_host_init(struct dwc2_hsotg *hsotg) |
| { |
| u32 hcfg, otgctl, usbcfg; |
| |
| /* Set HS/FS Timeout Calibration to 7 (max available value). |
| * The number of PHY clocks that the application programs in |
| * this field is added to the high/full speed interpacket timeout |
| * duration in the core to account for any additional delays |
| * introduced by the PHY. This can be required, because the delay |
| * introduced by the PHY in generating the linestate condition |
| * can vary from one PHY to another. |
| */ |
| usbcfg = dwc2_readl(hsotg, GUSBCFG); |
| usbcfg |= GUSBCFG_TOUTCAL(7); |
| dwc2_writel(hsotg, usbcfg, GUSBCFG); |
| |
| /* Restart the Phy Clock */ |
| dwc2_writel(hsotg, 0, PCGCTL); |
| |
| |
| /* Initialize Host Configuration Register */ |
| dwc2_init_fs_ls_pclk_sel(hsotg); |
| if (hsotg->speed == DWC2_SPEED_PARAM_FULL || |
| hsotg->speed == DWC2_SPEED_PARAM_LOW) { |
| hcfg = dwc2_readl(hsotg, HCFG); |
| hcfg |= HCFG_FSLSSUPP; |
| dwc2_writel(hsotg, hcfg, HCFG); |
| } |
| |
| /* Configure data FIFO sizes */ |
| dwc2_config_fifos(hsotg); |
| |
| dwc2_set_dma_mode(); |
| |
| /* TODO - check this */ |
| /* Clear Host Set HNP Enable in the OTG Control Register */ |
| otgctl = dwc2_readl(hsotg, GOTGCTL); |
| otgctl &= ~GOTGCTL_HSTSETHNPEN; |
| dwc2_writel(hsotg, otgctl, 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 = dwc2_readl(hsotg, GOTGCTL); |
| otgctl &= ~GOTGCTL_HSTSETHNPEN; |
| dwc2_writel(hsotg, otgctl, GOTGCTL); |
| |
| if (!hsotg->dma_desc_enable) { |
| int num_channels, i; |
| u32 hcchar; |
| |
| /* Flush out any leftover queued requests */ |
| num_channels = hsotg->host_channels; |
| debug("Number of Host channels: %d\n", num_channels); |
| |
| for (i = 0; i < num_channels; i++) { |
| hcchar = dwc2_readl(hsotg, HCCHAR(i)); |
| hcchar &= ~HCCHAR_CHENA; |
| hcchar |= HCCHAR_CHDIS; |
| hcchar &= ~HCCHAR_EPDIR; |
| dwc2_writel(hsotg, hcchar, HCCHAR(i)); |
| } |
| |
| /* Halt all channels to put them into a known state */ |
| for (i = 0; i < num_channels; i++) { |
| hcchar = dwc2_readl(hsotg, HCCHAR(i)); |
| hcchar |= HCCHAR_CHENA | HCCHAR_CHDIS; |
| hcchar &= ~HCCHAR_EPDIR; |
| dwc2_writel(hsotg, hcchar, HCCHAR(i)); |
| debug("%s: Halt channel %d\n", |
| __func__, i); |
| |
| if (dwc2_hsotg_wait_bit_clear(hsotg, HCCHAR(i), |
| HCCHAR_CHENA, 1000)) { |
| debug("Unable to clear enable on channel %d\n", |
| i); |
| } |
| } |
| } |
| |
| /* Enable ACG feature in host mode, if supported */ |
| //dwc2_enable_acg(hsotg); |
| |
| /* Turn on the vbus power */ |
| if (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) { |
| u32 hprt0 = dwc2_read_hprt0(hsotg); |
| |
| debug("Init: Power Port (%d)\n", |
| !!(hprt0 & HPRT0_PWR)); |
| if (!(hprt0 & HPRT0_PWR)) { |
| hprt0 |= HPRT0_PWR; |
| dwc2_writel(hsotg, hprt0, HPRT0); |
| } |
| } |
| |
| //dwc2_enable_host_interrupts(hsotg); |
| //dwc2_enable_host_interrupt(); |
| |
| |
| return 0; |
| } |
| |
| /* |
| * Prepares a host channel for transferring packets to/from a specific |
| * endpoint. The HCCHARn register is set up with the characteristics specified |
| * in _hc. Host channel interrupts that may need to be serviced while this |
| * transfer is in progress are enabled. |
| * |
| * @param regs Programming view of DWC_otg controller |
| * @param hc Information needed to initialize the host channel |
| */ |
| static void dwc2_fill_chnl_chars(struct dwc2_core_regs *regs, uint8_t hc_num, |
| struct usb_device *dev, uint8_t dev_addr, uint8_t ep_num, |
| uint8_t ep_is_in, uint8_t ep_type, uint16_t max_packet) |
| { |
| struct dwc2_hc_regs *hc_regs = ®s->hc_regs[hc_num]; |
| uint32_t hcchar = (dev_addr << DWC2_HCCHAR_DEVADDR_OFFSET) | |
| (ep_num << DWC2_HCCHAR_EPNUM_OFFSET) | |
| (ep_is_in << DWC2_HCCHAR_EPDIR_OFFSET) | |
| (ep_type << DWC2_HCCHAR_EPTYPE_OFFSET) | |
| (max_packet << DWC2_HCCHAR_MPS_OFFSET); |
| |
| if (dev->speed == USB_SPEED_LOW) |
| hcchar |= DWC2_HCCHAR_LSPDDEV; |
| |
| /* |
| * Program the HCCHARn register with the endpoint characteristics |
| * for the current transfer. |
| */ |
| writel(hcchar, &hc_regs->hcchar); |
| |
| /* Program the HCSPLIT register, default to no SPLIT */ |
| writel(0, &hc_regs->hcsplt); |
| } |
| |
| /* |
| * DWC2 to USB API interface |
| */ |
| void dwc2_dump_interrupt_status() |
| { |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"gintsts=0x%x haint=%x hcint%d=%x\n", |
| g_regs->gintsts, |
| g_regs->host_regs.haint, |
| DWC2_HC_CHANNEL, |
| g_regs->hc_regs[DWC2_HC_CHANNEL].hcint); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"gintmsk=0x%x haintmsk=%x hcint%dmsk=%x\n", |
| g_regs->gintmsk, |
| g_regs->host_regs.haintmsk, |
| DWC2_HC_CHANNEL, |
| g_regs->hc_regs[DWC2_HC_CHANNEL].hcintmsk); |
| } |
| |
| void dwc2_clear_host_interrupt() |
| { |
| g_regs->hc_regs[DWC2_HC_CHANNEL].hcint = g_regs->hc_regs[DWC2_HC_CHANNEL].hcint; // clear interrupts that were triggered. |
| } |
| |
| static void _dwc2_set_data_toggle() |
| { |
| if (g_pid && g_hctsiz){ |
| *g_pid = ((*g_hctsiz) & DWC2_HCTSIZ_PID_MASK) >> DWC2_HCTSIZ_PID_OFFSET; |
| } |
| } |
| |
| void dwc2_isr() |
| { |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"USB INTERRUPT: gintsts=0x%x haint=%x hcint%d=%x\n", |
| g_regs->gintsts, |
| g_regs->host_regs.haint, |
| DWC2_HC_CHANNEL, |
| g_regs->hc_regs[DWC2_HC_CHANNEL].hcint); |
| |
| g_got_hc_int = g_regs->hc_regs[DWC2_HC_CHANNEL].hcint; |
| dwc2_clear_host_interrupt(); |
| |
| if (g_got_hc_int & DWC2_HCINT_XFERCOMP){ |
| if (g_msg_queue){ |
| _dwc2_set_data_toggle(); |
| // Currently map callback based on PIPE type (BULK, INT, ISO) |
| if (usb_pipebulk(g_current_pipe)){ |
| usb_mass_callback(); |
| } |
| else if (usb_pipeint(g_current_pipe)){ |
| //usb_hid_callback(); |
| } |
| else if (usb_pipeisoc(g_current_pipe)){ |
| |
| } |
| } |
| } |
| else{ |
| dbg_printf(PRN_RES,"INCOMPLETE USB TRANSFER (0x%x)\n", g_got_hc_int); |
| } |
| } |
| |
| void dwc2_enable_host_interrupt() |
| { |
| g_regs->gintmsk = 1<<25; // Enable channel interrupt |
| g_regs->host_regs.haintmsk = 1<<DWC2_HC_CHANNEL; // host channel x interrupts enable |
| g_regs->hc_regs[DWC2_HC_CHANNEL].hcintmsk = DWC2_HCINT_CHHLTD; // Channel halted interrupt |
| g_regs->gahbcfg |= 1; // Enable INT on AHB to ARM core |
| |
| //register_isr(dwc2_isr, IRQ_usb0Intr); |
| set_irq_enable(IRQ_usb0Intr); |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"ENABLE INT:"); |
| dwc2_dump_interrupt_status(); |
| } |
| |
| void dwc2_disable_host_interrupt() |
| { |
| int cpuID; |
| |
| cpuID=getMPid(); |
| diag_GICSetInt(cpuID, MP_BERLIN_INTR_ID(IRQ_usb0Intr), 0); |
| g_regs->gintmsk &= ~(1<<25); // Enable channel interrupt |
| g_regs->host_regs.haintmsk &= ~(1<<DWC2_HC_CHANNEL); // host channel x interrupts disable |
| g_regs->hc_regs[DWC2_HC_CHANNEL].hcintmsk &= ~DWC2_HCINT_CHHLTD; // Channel halted interrupt |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"DISABLE INT:"); |
| dwc2_dump_interrupt_status(); |
| } |
| |
| int dwc2_wait_for_interrupt(struct dwc2_hc_regs *hc_regs, uint32_t *sub, u8 *toggle) |
| { |
| int ret; |
| uint32_t hcint, hctsiz; |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED, "dwc2_wait_for_interrupt\n"); |
| |
| if (!g_dwc2_chhltd_int_en){ // polling - NO INTERRUPT |
| ret = _dwc2_wait_for_bit_le32(&hc_regs->hcint, DWC2_HCINT_CHHLTD, true, |
| 1000, false); |
| } |
| else{ // USE HW interrupt and ISR |
| int to_loop_cnt = 10000; |
| ret = 0; |
| while(!g_got_hc_int){ |
| udelay(100); // wait for 1 seconds |
| if (!to_loop_cnt--){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"INTERRUPT NOT TRIGGER! HCINT=0x%08lx\n",hc_regs->hcint); |
| ret = 1; |
| break; |
| } |
| } // Wait for interrupt |
| } |
| |
| if (ret) |
| return ret; |
| |
| if (g_dwc2_chhltd_int_en){ |
| hcint = g_got_hc_int; |
| g_got_hc_int = 0; |
| } |
| else |
| hcint = readl(&hc_regs->hcint); |
| hctsiz = readl(&hc_regs->hctsiz); |
| *sub = (hctsiz & DWC2_HCTSIZ_XFERSIZE_MASK) >> |
| DWC2_HCTSIZ_XFERSIZE_OFFSET; |
| *toggle = (hctsiz & DWC2_HCTSIZ_PID_MASK) >> DWC2_HCTSIZ_PID_OFFSET; |
| |
| dbg_printf( PRN_INFO | PRN_BUFFERED, "HCINT=%08x sub=%u toggle=%d\n",hcint, *sub, |
| *toggle); |
| |
| if (hcint & DWC2_HCINT_XFERCOMP){ |
| if (!(hcint & DWC2_HCINT_ACK)){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"NOACK!!"); |
| } |
| return 0; |
| } |
| |
| if (hcint & (DWC2_HCINT_NAK | DWC2_HCINT_FRMOVRUN)) |
| return -EAGAIN; |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"Error (HCINT=%08x)\n",hcint); |
| return -EINVAL; |
| } |
| |
| static int packet_send(struct dwc2_hc_regs *hc_regs, void *aligned_buffer, |
| u8 *pid, int in, void *buffer, int num_packets, |
| int xfer_len, int *actual_len, int odd_frame) |
| { |
| int ret = 0; |
| uint32_t sub; |
| int data; |
| //struct dwc2_desc_dma *desctable = (struct dwc2_desc_dma *)USB_SCATGAT_DESC_QH; |
| int desc_count=0; // number of descriptors |
| unsigned int do_ping; |
| |
| UNUSED(aligned_buffer); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"packet_send: pid=%d xfer_len=%u pkts=%u buffer=0x%x\n", |
| *pid, xfer_len, num_packets, buffer); |
| |
| dwc2_set_dma_mode(); // Set DMA mode buffered or Scatter/Gather |
| |
| if (usb_pipebulk(g_current_pipe) && (!in) && (pdev_usb->speed == USB_SPEED_HIGH)){ /* perform ping protocol if BULK OUT . Necessary for compatibility with some USB 2.0 flash drive */ |
| do_ping = 1<<31; |
| } |
| else{ |
| do_ping = 0; |
| } |
| |
| if (!g_dwc2_dma_mode){ // Buffered DMA mode |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"packet_send.buffered_dma\n"); |
| writel((unsigned long)buffer, &hc_regs->hcdma); |
| BFM_HOST_Bus_Read32(&hc_regs->hcdma, &data); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"before hcdma:%x\n", data); |
| writel( do_ping | |
| (xfer_len << DWC2_HCTSIZ_XFERSIZE_OFFSET) | |
| (num_packets << DWC2_HCTSIZ_PKTCNT_OFFSET) | |
| (*pid << DWC2_HCTSIZ_PID_OFFSET), |
| &hc_regs->hctsiz); |
| } |
| |
| BFM_HOST_Bus_Read32(&hc_regs->hctsiz, &data); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"PRE HCTSIZ mode(%d): 0x%x\n", g_dwc2_dma_mode, data); |
| |
| /* Clear old interrupt conditions for this host channel. */ |
| writel(0x3fff, &hc_regs->hcint); |
| |
| if (g_dwc2_chhltd_int_en){ |
| dwc2_enable_host_interrupt(); |
| } |
| /* Set host channel enable after all other setup is complete. */ |
| _dwc2_clrsetbits_le32(&hc_regs->hcchar, DWC2_HCCHAR_MULTICNT_MASK | |
| DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS | |
| DWC2_HCCHAR_ODDFRM, |
| (1 << DWC2_HCCHAR_MULTICNT_OFFSET) | |
| (odd_frame << DWC2_HCCHAR_ODDFRM_OFFSET) | |
| DWC2_HCCHAR_CHEN); |
| |
| if (g_msg_queue){ |
| g_pid = pid; |
| g_hctsiz = &hc_regs->hctsiz; |
| return 0; |
| } |
| |
| ret = dwc2_wait_for_interrupt(hc_regs, &sub, pid); |
| |
| if (g_dwc2_chhltd_int_en){ |
| dwc2_clear_host_interrupt(); |
| dwc2_disable_host_interrupt(); |
| } |
| |
| if (g_dwc2_dma_mode){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"DESCDMA post [count=%d]:", desc_count); |
| writel( (desc_count << 8) | |
| (*pid << DWC2_HCTSIZ_PID_OFFSET), |
| &hc_regs->hctsiz); |
| } |
| BFM_HOST_Bus_Read32(&hc_regs->hctsiz, &data); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"POST HCTSIZ mode(%d): 0x%x\n",g_dwc2_dma_mode, data); |
| |
| BFM_HOST_Bus_Read32(&hc_regs->hcdma, &data); |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"after hcdma:%x\n", data); |
| |
| if (ret < 0) |
| return ret; |
| |
| if (in) { |
| if (!g_dwc2_dma_mode){ |
| xfer_len -= sub; |
| } |
| } |
| *actual_len = xfer_len; |
| |
| return ret; |
| } |
| |
| static void dwc_otg_hc_init_split(struct dwc2_hc_regs *hc_regs, |
| uint8_t hub_devnum, uint8_t hub_port) |
| { |
| uint32_t hcsplt = 0; |
| |
| hcsplt = DWC2_HCSPLT_SPLTENA; |
| hcsplt |= hub_devnum << DWC2_HCSPLT_HUBADDR_OFFSET; |
| hcsplt |= hub_port << DWC2_HCSPLT_PRTADDR_OFFSET; |
| |
| /* Program the HCSPLIT register for SPLITs */ |
| writel(hcsplt, &hc_regs->hcsplt); |
| } |
| |
| int packet_message(struct dwc2_hsotg *hsotg, struct usb_device *dev, |
| unsigned long pipe, u8 *pid, int in, void *buffer, int len) |
| { |
| struct dwc2_core_regs *regs = hsotg->regs; |
| struct dwc2_hc_regs *hc_regs = ®s->hc_regs[DWC2_HC_CHANNEL]; |
| struct dwc2_host_regs *host_regs = ®s->host_regs; |
| int devnum = usb_pipedevice(pipe); |
| int ep = usb_pipeendpoint(pipe); |
| int max = usb_maxpacket(dev, pipe); |
| int eptype = dwc2_eptype[usb_pipetype(pipe)]; |
| int done = 0; |
| int ret = 0; |
| int do_split = 0; |
| int complete_split = 0; |
| uint32_t xfer_len; |
| uint32_t num_packets; |
| int stop_transfer = 0; |
| uint32_t max_xfer_len; |
| int ssplit_frame_num = 0; |
| |
| g_current_pipe = pipe; |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"packet_message: pipe=%x pid=%d in=%d len=%d buffer=0x%x ep=%d maxpktsz=%d eptype=%d \n", |
| pipe, *pid, in, len, buffer, ep, max,eptype); |
| |
| max_xfer_len = CONFIG_DWC2_MAX_PACKET_COUNT * max; |
| if (max_xfer_len > CONFIG_DWC2_MAX_TRANSFER_SIZE) |
| max_xfer_len = CONFIG_DWC2_MAX_TRANSFER_SIZE; |
| if (max_xfer_len > DWC2_DATA_BUF_SIZE) |
| max_xfer_len = DWC2_DATA_BUF_SIZE; |
| |
| /* Make sure that max_xfer_len is a multiple of max packet size. */ |
| num_packets = max_xfer_len / max; |
| max_xfer_len = num_packets * max; |
| |
| /* Initialize channel */ |
| dwc2_fill_chnl_chars(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in, |
| eptype, max); |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"after dwc2_fill_chnl_chars\n"); |
| |
| |
| /* Check if the target is a FS/LS device behind a HS hub */ |
| if (dev->speed != USB_SPEED_HIGH) { |
| uint8_t hub_addr; |
| uint8_t hub_port; |
| uint32_t hprt0 = readl(®s->hprt0); |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED," Checking Hub\n"); |
| |
| if ((hprt0 & DWC2_HPRT0_PRTSPD_MASK) == |
| DWC2_HPRT0_PRTSPD_HIGH) { |
| usb_find_usb2_hub_address_port(dev, &hub_addr, |
| &hub_port); |
| dwc_otg_hc_init_split(hc_regs, hub_addr, hub_port); |
| |
| do_split = 1; |
| num_packets = 1; |
| max_xfer_len = max; |
| } |
| } |
| |
| do { |
| int actual_len = 0; |
| uint32_t hcint; |
| int odd_frame = 0; |
| xfer_len = len - done; |
| |
| if (xfer_len > max_xfer_len) |
| xfer_len = max_xfer_len; |
| else if (xfer_len > (unsigned long) max) |
| num_packets = (xfer_len + max - 1) / max; |
| else |
| num_packets = 1; |
| |
| if (complete_split) |
| _dwc2_setbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT); |
| else if (do_split) |
| _dwc2_clrbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT); |
| |
| if (eptype == DWC2_HCCHAR_EPTYPE_INTR) { |
| int uframe_num = readl(&host_regs->hfnum); |
| if (!(uframe_num & 0x1)) |
| odd_frame = 1; |
| } |
| |
| ret = dwc2_msg->packet_snd(hc_regs, /*hsotg->aligned_buffer*/ /*aligned_buffer_addr*/ NULL, pid, |
| in, (char *)buffer + done, num_packets, |
| xfer_len, &actual_len, odd_frame); |
| |
| if (g_msg_queue){ |
| return ret; |
| } |
| |
| hcint = readl(&hc_regs->hcint); |
| if (complete_split) { |
| stop_transfer = 0; |
| if (hcint & DWC2_HCINT_NYET) { |
| ret = 0; |
| int frame_num = DWC2_HFNUM_MAX_FRNUM & |
| readl(&host_regs->hfnum); |
| if (((frame_num - ssplit_frame_num) & |
| DWC2_HFNUM_MAX_FRNUM) > 4) |
| ret = -EAGAIN; |
| } else |
| complete_split = 0; |
| } else if (do_split) { |
| if (hcint & DWC2_HCINT_ACK) { |
| ssplit_frame_num = DWC2_HFNUM_MAX_FRNUM & |
| readl(&host_regs->hfnum); |
| ret = 0; |
| complete_split = 1; |
| } |
| } |
| |
| if (ret) |
| break; |
| |
| if ((unsigned long)actual_len < xfer_len){ |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"USB ABORT Transfer (actual=%d < requested=%d)\n", actual_len, xfer_len); |
| stop_transfer = 1; |
| } |
| |
| done += actual_len; |
| |
| /* Transactions are done when when either all data is transferred or |
| * there is a short transfer. In case of a SPLIT make sure the CSPLIT |
| * is executed. |
| */ |
| } while (((done < len) && !stop_transfer) || complete_split); |
| |
| writel(0, &hc_regs->hcintmsk); |
| writel(0xFFFFFFFF, &hc_regs->hcint); |
| |
| dev->status = 0; |
| dev->transfer_len = done; |
| |
| return ret; |
| } |
| |
| int _snd_blk_msg(struct dwc2_hsotg *hsotg, struct usb_device *dev, |
| unsigned long pipe, void *buffer, int len) |
| { |
| int devnum = usb_pipedevice(pipe); |
| int ep = usb_pipeendpoint(pipe); |
| u8* pid; |
| |
| if ((devnum >= MAX_DEVICE) || (devnum == hsotg->root_hub_devnum)) { |
| dev->status = 0; |
| return -EINVAL; |
| } |
| if (usb_pipein(pipe)) |
| pid = &hsotg->in_data_toggle[devnum][ep]; |
| else |
| pid = &hsotg->out_data_toggle[devnum][ep]; |
| |
| return dwc2_msg->packet_msg(hsotg, dev, pipe, pid, usb_pipein(pipe), buffer, len); |
| } |
| |
| static int _snd_ctl_msg(struct dwc2_hsotg *hsotg, struct usb_device *dev, |
| unsigned long pipe, void *buffer, int len, |
| struct devrequest *setup) |
| { |
| int devnum = usb_pipedevice(pipe); |
| int ret, transfer_len; |
| u8 pid; |
| /* For CONTROL endpoint pid should start with DATA1 */ |
| int status_direction; |
| |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"_snd_ctl_msg: devnum=%d pipe=0x%x buffer=%x len=%d setup=0x%x\n", |
| devnum, pipe, buffer, len, setup); |
| |
| /* SETUP stage */ |
| pid = DWC2_HC_PID_SETUP; |
| do { |
| dbg_printf(PRN_INFO | PRN_BUFFERED,"Control Setup:"); |
| dbg_dump_buffer( (unsigned char *) setup, 8); |
| ret = dwc2_msg->packet_msg(hsotg, dev, pipe, &pid, 0, setup, 8); |
| } while (ret == -EAGAIN); |
| if (ret) |
| return ret; |
| |
| /* DATA stage */ |
| transfer_len = 0; |
| if (buffer) { |
| pid = DWC2_HC_PID_DATA1; |
| do { |
| ret = dwc2_msg->packet_msg(hsotg, dev, pipe, &pid, usb_pipein(pipe), |
| buffer, len); |
| transfer_len += dev->transfer_len; |
| { |
| unsigned char *b = (unsigned char *)buffer; |
| |
| b += dev->transfer_len; |
| buffer = (void *)b; |
| } |
| len -= dev->transfer_len; |
| } while (ret == -EAGAIN); |
| if (ret) |
| return ret; |
| status_direction = usb_pipeout(pipe); |
| } else { |
| /* No-data CONTROL always ends with an IN transaction */ |
| status_direction = 1; |
| } |
| |
| /* STATUS stage */ |
| pid = DWC2_HC_PID_DATA1; |
| do { |
| ret = dwc2_msg->packet_msg(hsotg, dev, pipe, &pid, status_direction, |
| hsotg->status_buffer, 0); |
| } while (ret == -EAGAIN); |
| if (ret) |
| return ret; |
| |
| dev->transfer_len = transfer_len; |
| |
| return 0; |
| } |
| |
| int dwc2_wait_device_detection() |
| { |
| struct dwc2_core_regs *regs; |
| unsigned long data; |
| const char spd[4][12]={"HIGH","FULL","LOW","RESERVED"}; |
| int timeout=g_port_reset_delay; |
| |
| regs = (struct dwc2_core_regs *) ACPU_MEMMAP_USBAHB_REG_BASE; |
| |
| dbg_printf(PRN_RES,"Wait PortChange.."); |
| |
| while(1){ |
| BFM_HOST_Bus_Read32(®s->hprt0, &data); |
| dbg_printf(PRN_INFO,"hprt=0x%x\n",data); |
| |
| if (data&8){ // Check PrtEnchng bit |
| int speed = (data>>17)&3; |
| int enable = data&4; |
| int attached = data&1; |
| int detected = data&2; |
| |
| dbg_printf(PRN_RES,"done!\n"); |
| dbg_printf(PRN_RES,"Device Present: Speed:%s Port:%s Dev: %s,%s \n", |
| spd[speed], |
| enable?"ENABLE":"DISABLE", |
| detected?"DETECTED":"NO_DETECT", |
| attached?"ATTACHED":"DETACHED"); |
| switch(speed){ |
| case 0: |
| pdev_usb->speed = USB_SPEED_HIGH; |
| if (enable && attached) |
| return 1; |
| break; |
| case 1: |
| pdev_usb->speed = USB_SPEED_FULL; |
| if (enable && attached) |
| return 1; |
| break; |
| case 2: |
| pdev_usb->speed = USB_SPEED_LOW; |
| if (enable && attached) |
| return 1; |
| break; |
| default: |
| dbg_printf(PRN_RES,"ERROR: INVALID SPEED. WIll force HS from now\n"); |
| pdev_usb->speed = USB_SPEED_HIGH; |
| return 0; |
| break; |
| } |
| } |
| mdelay(10); |
| |
| if (!timeout--){ |
| dbg_printf(PRN_RES,"Connection TimeOut!\n"); |
| return 0; // time out |
| } |
| } |
| return 0; |
| } |
| |
| int dwc2_hw_init(struct dwc2_hsotg *hsotg) |
| { |
| struct dwc2_core_regs *regs = hsotg->regs; |
| uint32_t snpsid; |
| int i, j; |
| //int data; |
| int res=0; |
| debug("%s,%d\n",__func__,__LINE__); |
| |
| //g_regs = (struct dwc2_core_regs *)ACPU_MEMMAP_USBAHB_REG_BASE; |
| |
| snpsid = readl(®s->gsnpsid); |
| dbg_printf(PRN_RES,"Core Release: %x.%03x\n", snpsid >> 12 & 0xf, snpsid & 0xfff); |
| |
| if ((snpsid & GSNPSID_ID_MASK) != DWC2_OTG_ID && |
| (snpsid & GSNPSID_ID_MASK) != DWC2_FS_IOT_ID && |
| (snpsid & GSNPSID_ID_MASK) != DWC2_HS_IOT_ID) { |
| debug("Bad value for GSNPSID: 0x%08x\n", |
| snpsid); |
| return -ENODEV; |
| } |
| |
| debug("Core Release: %1x.%1x%1x%1x (snpsid=%x)\n", |
| snpsid >> 12 & 0xf, snpsid >> 8 & 0xf, |
| snpsid >> 4 & 0xf, snpsid & 0xf, snpsid); |
| |
| dwc2_core_init(hsotg); |
| dwc2_core_host_init(hsotg); |
| dbg_printf(PRN_RES,"Wait PortConnectInterrupt.."); |
| j=g_port_reset_delay; |
| while(j){ |
| mdelay(10); |
| if (dwc2_readl(hsotg, HPRT0) & HPRT0_CONNDET){ |
| dbg_printf(PRN_RES,"done!\n"); |
| break; |
| } |
| dbg_printf(PRN_RES,"."); |
| if (g_port_reset_delay) // wait forever if g_port_reset_delay 0 |
| j--; |
| } |
| |
| for(i=0;i<MAX_RETRY;i++){ //Try 10 times because port detection is not stable on VELOCE. |
| /* |
| * Reset the port. During a HNP mode switch the reset |
| * needs to occur within 1ms and have a duration of at |
| * least 50ms. |
| */ |
| u32 hprt0 = dwc2_read_hprt0(hsotg); |
| hprt0 |= HPRT0_RST; |
| dwc2_writel(hsotg, hprt0, HPRT0); |
| dbg_printf(PRN_RES,"Port RESET started.."); |
| |
| mdelay(g_prtrst_duration_ms); // wait 50 msec per user guide, or more |
| dbg_printf(PRN_RES,"done!\n"); |
| |
| hprt0 = dwc2_read_hprt0(hsotg); |
| hprt0 &= ~HPRT0_RST; |
| dwc2_writel(hsotg, hprt0, HPRT0); |
| |
| if (dwc2_wait_device_detection()){ |
| break; |
| } |
| else{ |
| dbg_printf(PRN_RES,"Retry INIT...\n"); |
| dwc2_core_init(hsotg); |
| dwc2_core_host_init(hsotg); |
| } |
| } |
| if (i == MAX_RETRY) |
| return 1; |
| |
| for (i = 0; i < MAX_DEVICE; i++) { |
| for (j = 0; j < MAX_EPS_CHANNELS; j++) { |
| hsotg->in_data_toggle[i][j] = DWC2_HC_PID_DATA0; |
| hsotg->out_data_toggle[i][j] = DWC2_HC_PID_DATA0; |
| } |
| } |
| |
| mdelay(3000); // Add delay for problematic flash drive |
| |
| return res; |
| } |
| |
| void dwc2_host_reset(struct dwc2_core_regs *regs) |
| { |
| /* Put everything in reset. */ |
| _dwc2_clrsetbits_le32(®s->hprt0, DWC2_HPRT0_PRTENA | |
| DWC2_HPRT0_PRTCONNDET | DWC2_HPRT0_PRTENCHNG | |
| DWC2_HPRT0_PRTOVRCURRCHNG, |
| DWC2_HPRT0_PRTRST); |
| } |
| |
| int que_blk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, int len) |
| { |
| g_msg_queue = 1; |
| return dwc2_msg->blk(&dwc2_drv, dev, pipe, buffer, len); |
| } |
| |
| int snd_ctl_msg(struct usb_device *dev, unsigned long pipe, void *buffer, |
| int len, struct devrequest *setup) |
| { |
| g_msg_queue = 0; |
| return dwc2_msg->ctl(&dwc2_drv, dev, pipe, buffer, len, setup); |
| } |
| |
| int snd_blk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, |
| int len) |
| { |
| g_msg_queue = 0; |
| |
| return dwc2_msg->blk(&dwc2_drv, dev, pipe, buffer, len); |
| } |
| |
| static const hcd_dwc2_ops_t hcd_hcd_dwc2_ops = { |
| .packet_msg = packet_message, |
| .packet_snd = packet_send, |
| .ctl = _snd_ctl_msg, |
| .blk = _snd_blk_msg, |
| }; |
| |
| void dwc2_hcd_setup_ops(const hcd_dwc2_ops_t **dwc2_ops) |
| { |
| *dwc2_ops = &hcd_hcd_dwc2_ops; |
| } |
| |
| void dwc2_init(struct dwc2_hsotg *hsotg) |
| { |
| hsotg->root_hub_devnum = 0; |
| hsotg->regs = (struct dwc2_core_regs *) ACPU_MEMMAP_USBAHB_REG_BASE; |
| hsotg->reg_base = ACPU_MEMMAP_USBAHB_REG_BASE; |
| hsotg->aligned_buffer = aligned_buffer_addr; |
| hsotg->status_buffer = status_buffer_addr; |
| hsotg->phy_ulpi_ext_vbus = true; //use external vbus |
| hsotg->hnp_srp_disable = true; // will force host mode |
| hsotg->ts_dline = false; |
| hsotg->phy_ulpi_ddr = false; |
| hsotg->ulpi_fs_ls = false; |
| hsotg->speed = DWC2_SPEED_PARAM_HIGH; |
| hsotg->dma_desc_enable = false; |
| hsotg->host_channels = 1 + ((dwc2_readl(hsotg, GHWCFG2) & GHWCFG2_NUM_HOST_CHAN_MASK) >> |
| GHWCFG2_NUM_HOST_CHAN_SHIFT); |
| dwc2_hcd_setup_ops(&dwc2_msg); |
| if (g_dwc2_chhltd_int_en == 1) { |
| register_isr(dwc2_isr, IRQ_usb0Intr); |
| } |
| |
| } |
| |