blob: 958a49aa819a7df600b0a8dfe0943a0cce7fbce9 [file] [log] [blame]
// 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 = &regs->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 = &regs->hc_regs[DWC2_HC_CHANNEL];
struct dwc2_host_regs *host_regs = &regs->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(&regs->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(&regs->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(&regs->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(&regs->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);
}
}