u-boot-brain/drivers/usb/host/dwc2.c
Stefan Brüns 03460cdc3d usb: dwc2: Simplify wait_for_chhltd(), remove ignore_ack
A transfer is completed if the XFERCOMP flag is set, irrespective of the
ACK flag. BULK OUT transfers to some HS devices complete without having
the ACK flag set, which signal the devices has responded with an NYET
to the transfer (PING protocol).
The new behaviour matches the Linux kernel minus any PING protocol.

Also see 5966defabdcc (usb: dwc2: fix bulk transfers)

Signed-off-by: Stefan Brüns <stefan.bruens@rwth-aachen.de>
2016-01-23 16:21:11 +01:00

1128 lines
29 KiB
C

/*
* Copyright (C) 2012 Oleksandr Tymoshenko <gonzo@freebsd.org>
* Copyright (C) 2014 Marek Vasut <marex@denx.de>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <common.h>
#include <dm.h>
#include <errno.h>
#include <usb.h>
#include <malloc.h>
#include <memalign.h>
#include <phys2bus.h>
#include <usbroothubdes.h>
#include <asm/io.h>
#include "dwc2.h"
/* Use only HC channel 0. */
#define DWC2_HC_CHANNEL 0
#define DWC2_STATUS_BUF_SIZE 64
#define DWC2_DATA_BUF_SIZE (64 * 1024)
#define MAX_DEVICE 16
#define MAX_ENDPOINT 16
struct dwc2_priv {
#ifdef CONFIG_DM_USB
uint8_t aligned_buffer[DWC2_DATA_BUF_SIZE] __aligned(ARCH_DMA_MINALIGN);
uint8_t status_buffer[DWC2_STATUS_BUF_SIZE] __aligned(ARCH_DMA_MINALIGN);
#else
uint8_t *aligned_buffer;
uint8_t *status_buffer;
#endif
int bulk_data_toggle[MAX_DEVICE][MAX_ENDPOINT];
struct dwc2_core_regs *regs;
int root_hub_devnum;
};
#ifndef CONFIG_DM_USB
/* We need cacheline-aligned buffers for DMA transfers and dcache support */
DEFINE_ALIGN_BUFFER(uint8_t, aligned_buffer_addr, DWC2_DATA_BUF_SIZE,
ARCH_DMA_MINALIGN);
DEFINE_ALIGN_BUFFER(uint8_t, status_buffer_addr, DWC2_STATUS_BUF_SIZE,
ARCH_DMA_MINALIGN);
static struct dwc2_priv local;
#endif
/*
* DWC2 IP interface
*/
static int wait_for_bit(void *reg, const uint32_t mask, bool set)
{
unsigned int timeout = 1000000;
uint32_t val;
while (--timeout) {
val = readl(reg);
if (!set)
val = ~val;
if ((val & mask) == mask)
return 0;
udelay(1);
}
debug("%s: Timeout (reg=%p mask=%08x wait_set=%i)\n",
__func__, reg, mask, set);
return -ETIMEDOUT;
}
/*
* Initializes the FSLSPClkSel field of the HCFG register
* depending on the PHY type.
*/
static void init_fslspclksel(struct dwc2_core_regs *regs)
{
uint32_t phyclk;
#if (CONFIG_DWC2_PHY_TYPE == DWC2_PHY_TYPE_FS)
phyclk = DWC2_HCFG_FSLSPCLKSEL_48_MHZ; /* Full speed PHY */
#else
/* High speed PHY running at full speed or high speed */
phyclk = DWC2_HCFG_FSLSPCLKSEL_30_60_MHZ;
#endif
#ifdef CONFIG_DWC2_ULPI_FS_LS
uint32_t hwcfg2 = readl(&regs->ghwcfg2);
uint32_t hval = (ghwcfg2 & DWC2_HWCFG2_HS_PHY_TYPE_MASK) >>
DWC2_HWCFG2_HS_PHY_TYPE_OFFSET;
uint32_t fval = (ghwcfg2 & DWC2_HWCFG2_FS_PHY_TYPE_MASK) >>
DWC2_HWCFG2_FS_PHY_TYPE_OFFSET;
if (hval == 2 && fval == 1)
phyclk = DWC2_HCFG_FSLSPCLKSEL_48_MHZ; /* Full speed PHY */
#endif
clrsetbits_le32(&regs->host_regs.hcfg,
DWC2_HCFG_FSLSPCLKSEL_MASK,
phyclk << DWC2_HCFG_FSLSPCLKSEL_OFFSET);
}
/*
* Flush a Tx FIFO.
*
* @param regs Programming view of DWC_otg controller.
* @param num Tx FIFO to flush.
*/
static void dwc_otg_flush_tx_fifo(struct dwc2_core_regs *regs, const int num)
{
int ret;
writel(DWC2_GRSTCTL_TXFFLSH | (num << DWC2_GRSTCTL_TXFNUM_OFFSET),
&regs->grstctl);
ret = wait_for_bit(&regs->grstctl, DWC2_GRSTCTL_TXFFLSH, 0);
if (ret)
printf("%s: Timeout!\n", __func__);
/* Wait for 3 PHY Clocks */
udelay(1);
}
/*
* Flush Rx FIFO.
*
* @param regs Programming view of DWC_otg controller.
*/
static void dwc_otg_flush_rx_fifo(struct dwc2_core_regs *regs)
{
int ret;
writel(DWC2_GRSTCTL_RXFFLSH, &regs->grstctl);
ret = wait_for_bit(&regs->grstctl, DWC2_GRSTCTL_RXFFLSH, 0);
if (ret)
printf("%s: Timeout!\n", __func__);
/* Wait for 3 PHY Clocks */
udelay(1);
}
/*
* Do core a soft reset of the core. Be careful with this because it
* resets all the internal state machines of the core.
*/
static void dwc_otg_core_reset(struct dwc2_core_regs *regs)
{
int ret;
/* Wait for AHB master IDLE state. */
ret = wait_for_bit(&regs->grstctl, DWC2_GRSTCTL_AHBIDLE, 1);
if (ret)
printf("%s: Timeout!\n", __func__);
/* Core Soft Reset */
writel(DWC2_GRSTCTL_CSFTRST, &regs->grstctl);
ret = wait_for_bit(&regs->grstctl, DWC2_GRSTCTL_CSFTRST, 0);
if (ret)
printf("%s: Timeout!\n", __func__);
/*
* 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(100);
}
/*
* This function initializes the DWC_otg controller registers for
* host mode.
*
* This function flushes the Tx and Rx FIFOs and it flushes any entries in the
* request queues. Host channels are reset to ensure that they are ready for
* performing transfers.
*
* @param regs Programming view of DWC_otg controller
*
*/
static void dwc_otg_core_host_init(struct dwc2_core_regs *regs)
{
uint32_t nptxfifosize = 0;
uint32_t ptxfifosize = 0;
uint32_t hprt0 = 0;
int i, ret, num_channels;
/* Restart the Phy Clock */
writel(0, &regs->pcgcctl);
/* Initialize Host Configuration Register */
init_fslspclksel(regs);
#ifdef CONFIG_DWC2_DFLT_SPEED_FULL
setbits_le32(&regs->host_regs.hcfg, DWC2_HCFG_FSLSSUPP);
#endif
/* Configure data FIFO sizes */
#ifdef CONFIG_DWC2_ENABLE_DYNAMIC_FIFO
if (readl(&regs->ghwcfg2) & DWC2_HWCFG2_DYNAMIC_FIFO) {
/* Rx FIFO */
writel(CONFIG_DWC2_HOST_RX_FIFO_SIZE, &regs->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;
writel(nptxfifosize, &regs->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;
writel(ptxfifosize, &regs->hptxfsiz);
}
#endif
/* Clear Host Set HNP Enable in the OTG Control Register */
clrbits_le32(&regs->gotgctl, DWC2_GOTGCTL_HSTSETHNPEN);
/* Make sure the FIFOs are flushed. */
dwc_otg_flush_tx_fifo(regs, 0x10); /* All Tx FIFOs */
dwc_otg_flush_rx_fifo(regs);
/* Flush out any leftover queued requests. */
num_channels = readl(&regs->ghwcfg2);
num_channels &= DWC2_HWCFG2_NUM_HOST_CHAN_MASK;
num_channels >>= DWC2_HWCFG2_NUM_HOST_CHAN_OFFSET;
num_channels += 1;
for (i = 0; i < num_channels; i++)
clrsetbits_le32(&regs->hc_regs[i].hcchar,
DWC2_HCCHAR_CHEN | DWC2_HCCHAR_EPDIR,
DWC2_HCCHAR_CHDIS);
/* Halt all channels to put them into a known state. */
for (i = 0; i < num_channels; i++) {
clrsetbits_le32(&regs->hc_regs[i].hcchar,
DWC2_HCCHAR_EPDIR,
DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS);
ret = wait_for_bit(&regs->hc_regs[i].hcchar,
DWC2_HCCHAR_CHEN, 0);
if (ret)
printf("%s: Timeout!\n", __func__);
}
/* Turn on the vbus power. */
if (readl(&regs->gintsts) & DWC2_GINTSTS_CURMODE_HOST) {
hprt0 = readl(&regs->hprt0);
hprt0 &= ~(DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET);
hprt0 &= ~(DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG);
if (!(hprt0 & DWC2_HPRT0_PRTPWR)) {
hprt0 |= DWC2_HPRT0_PRTPWR;
writel(hprt0, &regs->hprt0);
}
}
}
/*
* This function initializes the DWC_otg controller registers and
* prepares the core for device mode or host mode operation.
*
* @param regs Programming view of the DWC_otg controller
*/
static void dwc_otg_core_init(struct dwc2_core_regs *regs)
{
uint32_t ahbcfg = 0;
uint32_t usbcfg = 0;
uint8_t brst_sz = CONFIG_DWC2_DMA_BURST_SIZE;
/* Common Initialization */
usbcfg = readl(&regs->gusbcfg);
/* Program the ULPI External VBUS bit if needed */
#ifdef CONFIG_DWC2_PHY_ULPI_EXT_VBUS
usbcfg |= DWC2_GUSBCFG_ULPI_EXT_VBUS_DRV;
#else
usbcfg &= ~DWC2_GUSBCFG_ULPI_EXT_VBUS_DRV;
#endif
/* Set external TS Dline pulsing */
#ifdef CONFIG_DWC2_TS_DLINE
usbcfg |= DWC2_GUSBCFG_TERM_SEL_DL_PULSE;
#else
usbcfg &= ~DWC2_GUSBCFG_TERM_SEL_DL_PULSE;
#endif
writel(usbcfg, &regs->gusbcfg);
/* Reset the Controller */
dwc_otg_core_reset(regs);
/*
* This programming sequence needs to happen in FS mode before
* any other programming occurs
*/
#if defined(CONFIG_DWC2_DFLT_SPEED_FULL) && \
(CONFIG_DWC2_PHY_TYPE == DWC2_PHY_TYPE_FS)
/* If FS mode with FS PHY */
setbits_le32(&regs->gusbcfg, DWC2_GUSBCFG_PHYSEL);
/* Reset after a PHY select */
dwc_otg_core_reset(regs);
/*
* 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 (readl(&regs->gintsts) & DWC2_GINTSTS_CURMODE_HOST)
init_fslspclksel(regs);
#ifdef CONFIG_DWC2_I2C_ENABLE
/* Program GUSBCFG.OtgUtmifsSel to I2C */
setbits_le32(&regs->gusbcfg, DWC2_GUSBCFG_OTGUTMIFSSEL);
/* Program GI2CCTL.I2CEn */
clrsetbits_le32(&regs->gi2cctl, DWC2_GI2CCTL_I2CEN |
DWC2_GI2CCTL_I2CDEVADDR_MASK,
1 << DWC2_GI2CCTL_I2CDEVADDR_OFFSET);
setbits_le32(&regs->gi2cctl, DWC2_GI2CCTL_I2CEN);
#endif
#else
/* High speed PHY. */
/*
* HS PHY parameters. These parameters are preserved during
* soft reset so only program the first time. Do a soft reset
* immediately after setting phyif.
*/
usbcfg &= ~(DWC2_GUSBCFG_ULPI_UTMI_SEL | DWC2_GUSBCFG_PHYIF);
usbcfg |= CONFIG_DWC2_PHY_TYPE << DWC2_GUSBCFG_ULPI_UTMI_SEL_OFFSET;
if (usbcfg & DWC2_GUSBCFG_ULPI_UTMI_SEL) { /* ULPI interface */
#ifdef CONFIG_DWC2_PHY_ULPI_DDR
usbcfg |= DWC2_GUSBCFG_DDRSEL;
#else
usbcfg &= ~DWC2_GUSBCFG_DDRSEL;
#endif
} else { /* UTMI+ interface */
#if (CONFIG_DWC2_UTMI_PHY_WIDTH == 16)
usbcfg |= DWC2_GUSBCFG_PHYIF;
#endif
}
writel(usbcfg, &regs->gusbcfg);
/* Reset after setting the PHY parameters */
dwc_otg_core_reset(regs);
#endif
usbcfg = readl(&regs->gusbcfg);
usbcfg &= ~(DWC2_GUSBCFG_ULPI_FSLS | DWC2_GUSBCFG_ULPI_CLK_SUS_M);
#ifdef CONFIG_DWC2_ULPI_FS_LS
uint32_t hwcfg2 = readl(&regs->ghwcfg2);
uint32_t hval = (ghwcfg2 & DWC2_HWCFG2_HS_PHY_TYPE_MASK) >>
DWC2_HWCFG2_HS_PHY_TYPE_OFFSET;
uint32_t fval = (ghwcfg2 & DWC2_HWCFG2_FS_PHY_TYPE_MASK) >>
DWC2_HWCFG2_FS_PHY_TYPE_OFFSET;
if (hval == 2 && fval == 1) {
usbcfg |= DWC2_GUSBCFG_ULPI_FSLS;
usbcfg |= DWC2_GUSBCFG_ULPI_CLK_SUS_M;
}
#endif
writel(usbcfg, &regs->gusbcfg);
/* Program the GAHBCFG Register. */
switch (readl(&regs->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;
}
#ifdef CONFIG_DWC2_DMA_ENABLE
ahbcfg |= DWC2_GAHBCFG_DMAENABLE;
#endif
break;
case DWC2_HWCFG2_ARCHITECTURE_INT_DMA:
ahbcfg |= DWC2_GAHBCFG_HBURSTLEN_INCR4;
#ifdef CONFIG_DWC2_DMA_ENABLE
ahbcfg |= DWC2_GAHBCFG_DMAENABLE;
#endif
break;
}
writel(ahbcfg, &regs->gahbcfg);
/* Program the GUSBCFG register for HNP/SRP. */
setbits_le32(&regs->gusbcfg, DWC2_GUSBCFG_HNPCAP | DWC2_GUSBCFG_SRPCAP);
#ifdef CONFIG_DWC2_IC_USB_CAP
setbits_le32(&regs->gusbcfg, DWC2_GUSBCFG_IC_USB_CAP);
#endif
}
/*
* 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 dwc_otg_hc_init(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;
/* Clear old interrupt conditions for this host channel. */
writel(0x3fff, &hc_regs->hcint);
/*
* Program the HCCHARn register with the endpoint characteristics
* for the current transfer.
*/
writel(hcchar, &hc_regs->hcchar);
/* Program the HCSPLIT register for SPLITs */
writel(0, &hc_regs->hcsplt);
}
/*
* DWC2 to USB API interface
*/
/* Direction: In ; Request: Status */
static int dwc_otg_submit_rh_msg_in_status(struct dwc2_core_regs *regs,
struct usb_device *dev, void *buffer,
int txlen, struct devrequest *cmd)
{
uint32_t hprt0 = 0;
uint32_t port_status = 0;
uint32_t port_change = 0;
int len = 0;
int stat = 0;
switch (cmd->requesttype & ~USB_DIR_IN) {
case 0:
*(uint16_t *)buffer = cpu_to_le16(1);
len = 2;
break;
case USB_RECIP_INTERFACE:
case USB_RECIP_ENDPOINT:
*(uint16_t *)buffer = cpu_to_le16(0);
len = 2;
break;
case USB_TYPE_CLASS:
*(uint32_t *)buffer = cpu_to_le32(0);
len = 4;
break;
case USB_RECIP_OTHER | USB_TYPE_CLASS:
hprt0 = readl(&regs->hprt0);
if (hprt0 & DWC2_HPRT0_PRTCONNSTS)
port_status |= USB_PORT_STAT_CONNECTION;
if (hprt0 & DWC2_HPRT0_PRTENA)
port_status |= USB_PORT_STAT_ENABLE;
if (hprt0 & DWC2_HPRT0_PRTSUSP)
port_status |= USB_PORT_STAT_SUSPEND;
if (hprt0 & DWC2_HPRT0_PRTOVRCURRACT)
port_status |= USB_PORT_STAT_OVERCURRENT;
if (hprt0 & DWC2_HPRT0_PRTRST)
port_status |= USB_PORT_STAT_RESET;
if (hprt0 & DWC2_HPRT0_PRTPWR)
port_status |= USB_PORT_STAT_POWER;
if ((hprt0 & DWC2_HPRT0_PRTSPD_MASK) == DWC2_HPRT0_PRTSPD_LOW)
port_status |= USB_PORT_STAT_LOW_SPEED;
else if ((hprt0 & DWC2_HPRT0_PRTSPD_MASK) ==
DWC2_HPRT0_PRTSPD_HIGH)
port_status |= USB_PORT_STAT_HIGH_SPEED;
if (hprt0 & DWC2_HPRT0_PRTENCHNG)
port_change |= USB_PORT_STAT_C_ENABLE;
if (hprt0 & DWC2_HPRT0_PRTCONNDET)
port_change |= USB_PORT_STAT_C_CONNECTION;
if (hprt0 & DWC2_HPRT0_PRTOVRCURRCHNG)
port_change |= USB_PORT_STAT_C_OVERCURRENT;
*(uint32_t *)buffer = cpu_to_le32(port_status |
(port_change << 16));
len = 4;
break;
default:
puts("unsupported root hub command\n");
stat = USB_ST_STALLED;
}
dev->act_len = min(len, txlen);
dev->status = stat;
return stat;
}
/* Direction: In ; Request: Descriptor */
static int dwc_otg_submit_rh_msg_in_descriptor(struct usb_device *dev,
void *buffer, int txlen,
struct devrequest *cmd)
{
unsigned char data[32];
uint32_t dsc;
int len = 0;
int stat = 0;
uint16_t wValue = cpu_to_le16(cmd->value);
uint16_t wLength = cpu_to_le16(cmd->length);
switch (cmd->requesttype & ~USB_DIR_IN) {
case 0:
switch (wValue & 0xff00) {
case 0x0100: /* device descriptor */
len = min3(txlen, (int)sizeof(root_hub_dev_des), (int)wLength);
memcpy(buffer, root_hub_dev_des, len);
break;
case 0x0200: /* configuration descriptor */
len = min3(txlen, (int)sizeof(root_hub_config_des), (int)wLength);
memcpy(buffer, root_hub_config_des, len);
break;
case 0x0300: /* string descriptors */
switch (wValue & 0xff) {
case 0x00:
len = min3(txlen, (int)sizeof(root_hub_str_index0),
(int)wLength);
memcpy(buffer, root_hub_str_index0, len);
break;
case 0x01:
len = min3(txlen, (int)sizeof(root_hub_str_index1),
(int)wLength);
memcpy(buffer, root_hub_str_index1, len);
break;
}
break;
default:
stat = USB_ST_STALLED;
}
break;
case USB_TYPE_CLASS:
/* Root port config, set 1 port and nothing else. */
dsc = 0x00000001;
data[0] = 9; /* min length; */
data[1] = 0x29;
data[2] = dsc & RH_A_NDP;
data[3] = 0;
if (dsc & RH_A_PSM)
data[3] |= 0x1;
if (dsc & RH_A_NOCP)
data[3] |= 0x10;
else if (dsc & RH_A_OCPM)
data[3] |= 0x8;
/* corresponds to data[4-7] */
data[5] = (dsc & RH_A_POTPGT) >> 24;
data[7] = dsc & RH_B_DR;
if (data[2] < 7) {
data[8] = 0xff;
} else {
data[0] += 2;
data[8] = (dsc & RH_B_DR) >> 8;
data[9] = 0xff;
data[10] = data[9];
}
len = min3(txlen, (int)data[0], (int)wLength);
memcpy(buffer, data, len);
break;
default:
puts("unsupported root hub command\n");
stat = USB_ST_STALLED;
}
dev->act_len = min(len, txlen);
dev->status = stat;
return stat;
}
/* Direction: In ; Request: Configuration */
static int dwc_otg_submit_rh_msg_in_configuration(struct usb_device *dev,
void *buffer, int txlen,
struct devrequest *cmd)
{
int len = 0;
int stat = 0;
switch (cmd->requesttype & ~USB_DIR_IN) {
case 0:
*(uint8_t *)buffer = 0x01;
len = 1;
break;
default:
puts("unsupported root hub command\n");
stat = USB_ST_STALLED;
}
dev->act_len = min(len, txlen);
dev->status = stat;
return stat;
}
/* Direction: In */
static int dwc_otg_submit_rh_msg_in(struct dwc2_priv *priv,
struct usb_device *dev, void *buffer,
int txlen, struct devrequest *cmd)
{
switch (cmd->request) {
case USB_REQ_GET_STATUS:
return dwc_otg_submit_rh_msg_in_status(priv->regs, dev, buffer,
txlen, cmd);
case USB_REQ_GET_DESCRIPTOR:
return dwc_otg_submit_rh_msg_in_descriptor(dev, buffer,
txlen, cmd);
case USB_REQ_GET_CONFIGURATION:
return dwc_otg_submit_rh_msg_in_configuration(dev, buffer,
txlen, cmd);
default:
puts("unsupported root hub command\n");
return USB_ST_STALLED;
}
}
/* Direction: Out */
static int dwc_otg_submit_rh_msg_out(struct dwc2_priv *priv,
struct usb_device *dev,
void *buffer, int txlen,
struct devrequest *cmd)
{
struct dwc2_core_regs *regs = priv->regs;
int len = 0;
int stat = 0;
uint16_t bmrtype_breq = cmd->requesttype | (cmd->request << 8);
uint16_t wValue = cpu_to_le16(cmd->value);
switch (bmrtype_breq & ~USB_DIR_IN) {
case (USB_REQ_CLEAR_FEATURE << 8) | USB_RECIP_ENDPOINT:
case (USB_REQ_CLEAR_FEATURE << 8) | USB_TYPE_CLASS:
break;
case (USB_REQ_CLEAR_FEATURE << 8) | USB_RECIP_OTHER | USB_TYPE_CLASS:
switch (wValue) {
case USB_PORT_FEAT_C_CONNECTION:
setbits_le32(&regs->hprt0, DWC2_HPRT0_PRTCONNDET);
break;
}
break;
case (USB_REQ_SET_FEATURE << 8) | USB_RECIP_OTHER | USB_TYPE_CLASS:
switch (wValue) {
case USB_PORT_FEAT_SUSPEND:
break;
case USB_PORT_FEAT_RESET:
clrsetbits_le32(&regs->hprt0, DWC2_HPRT0_PRTENA |
DWC2_HPRT0_PRTCONNDET |
DWC2_HPRT0_PRTENCHNG |
DWC2_HPRT0_PRTOVRCURRCHNG,
DWC2_HPRT0_PRTRST);
mdelay(50);
clrbits_le32(&regs->hprt0, DWC2_HPRT0_PRTRST);
break;
case USB_PORT_FEAT_POWER:
clrsetbits_le32(&regs->hprt0, DWC2_HPRT0_PRTENA |
DWC2_HPRT0_PRTCONNDET |
DWC2_HPRT0_PRTENCHNG |
DWC2_HPRT0_PRTOVRCURRCHNG,
DWC2_HPRT0_PRTRST);
break;
case USB_PORT_FEAT_ENABLE:
break;
}
break;
case (USB_REQ_SET_ADDRESS << 8):
priv->root_hub_devnum = wValue;
break;
case (USB_REQ_SET_CONFIGURATION << 8):
break;
default:
puts("unsupported root hub command\n");
stat = USB_ST_STALLED;
}
len = min(len, txlen);
dev->act_len = len;
dev->status = stat;
return stat;
}
static int dwc_otg_submit_rh_msg(struct dwc2_priv *priv, struct usb_device *dev,
unsigned long pipe, void *buffer, int txlen,
struct devrequest *cmd)
{
int stat = 0;
if (usb_pipeint(pipe)) {
puts("Root-Hub submit IRQ: NOT implemented\n");
return 0;
}
if (cmd->requesttype & USB_DIR_IN)
stat = dwc_otg_submit_rh_msg_in(priv, dev, buffer, txlen, cmd);
else
stat = dwc_otg_submit_rh_msg_out(priv, dev, buffer, txlen, cmd);
mdelay(1);
return stat;
}
int wait_for_chhltd(struct dwc2_core_regs *regs, uint32_t *sub, int *toggle)
{
struct dwc2_hc_regs *hc_regs = &regs->hc_regs[DWC2_HC_CHANNEL];
int ret;
uint32_t hcint, hctsiz;
ret = wait_for_bit(&hc_regs->hcint, DWC2_HCINT_CHHLTD, true);
if (ret)
return ret;
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;
debug("%s: HCINT=%08x sub=%u toggle=%d\n", __func__, hcint, *sub,
*toggle);
if (hcint & DWC2_HCINT_XFERCOMP)
return 0;
if (hcint & (DWC2_HCINT_NAK | DWC2_HCINT_FRMOVRUN))
return -EAGAIN;
debug("%s: Error (HCINT=%08x)\n", __func__, hcint);
return -EINVAL;
}
static int dwc2_eptype[] = {
DWC2_HCCHAR_EPTYPE_ISOC,
DWC2_HCCHAR_EPTYPE_INTR,
DWC2_HCCHAR_EPTYPE_CONTROL,
DWC2_HCCHAR_EPTYPE_BULK,
};
int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
unsigned long pipe, int *pid, int in, void *buffer, int len)
{
struct dwc2_core_regs *regs = priv->regs;
struct dwc2_hc_regs *hc_regs = &regs->hc_regs[DWC2_HC_CHANNEL];
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;
uint32_t sub;
uint32_t xfer_len;
uint32_t num_packets;
int stop_transfer = 0;
uint32_t max_xfer_len;
debug("%s: msg: pipe %lx pid %d in %d len %d\n", __func__, pipe, *pid,
in, len);
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;
do {
/* Initialize channel */
dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in,
eptype, max);
xfer_len = len - done;
if (xfer_len > max_xfer_len)
xfer_len = max_xfer_len;
else if (xfer_len > max)
num_packets = (xfer_len + max - 1) / max;
else
num_packets = 1;
debug("%s: chunk: pid %d xfer_len %u pkts %u\n", __func__,
*pid, xfer_len, num_packets);
writel((xfer_len << DWC2_HCTSIZ_XFERSIZE_OFFSET) |
(num_packets << DWC2_HCTSIZ_PKTCNT_OFFSET) |
(*pid << DWC2_HCTSIZ_PID_OFFSET),
&hc_regs->hctsiz);
if (!in && xfer_len) {
memcpy(priv->aligned_buffer, (char *)buffer + done,
xfer_len);
flush_dcache_range((unsigned long)priv->aligned_buffer,
(unsigned long)((void *)priv->aligned_buffer +
roundup(xfer_len, ARCH_DMA_MINALIGN)));
}
writel(phys_to_bus((unsigned long)priv->aligned_buffer),
&hc_regs->hcdma);
/* Set host channel enable after all other setup is complete. */
clrsetbits_le32(&hc_regs->hcchar, DWC2_HCCHAR_MULTICNT_MASK |
DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS,
(1 << DWC2_HCCHAR_MULTICNT_OFFSET) |
DWC2_HCCHAR_CHEN);
ret = wait_for_chhltd(regs, &sub, pid);
if (ret)
break;
if (in) {
xfer_len -= sub;
invalidate_dcache_range((unsigned long)priv->aligned_buffer,
(unsigned long)((void *)priv->aligned_buffer +
roundup(xfer_len, ARCH_DMA_MINALIGN)));
memcpy(buffer + done, priv->aligned_buffer, xfer_len);
if (sub)
stop_transfer = 1;
}
done += xfer_len;
} while ((done < len) && !stop_transfer);
writel(0, &hc_regs->hcintmsk);
writel(0xFFFFFFFF, &hc_regs->hcint);
dev->status = 0;
dev->act_len = done;
return ret;
}
/* U-Boot USB transmission interface */
int _submit_bulk_msg(struct dwc2_priv *priv, struct usb_device *dev,
unsigned long pipe, void *buffer, int len)
{
int devnum = usb_pipedevice(pipe);
int ep = usb_pipeendpoint(pipe);
if (devnum == priv->root_hub_devnum) {
dev->status = 0;
return -EINVAL;
}
return chunk_msg(priv, dev, pipe, &priv->bulk_data_toggle[devnum][ep],
usb_pipein(pipe), buffer, len);
}
static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev,
unsigned long pipe, void *buffer, int len,
struct devrequest *setup)
{
int devnum = usb_pipedevice(pipe);
int pid, ret, act_len;
/* For CONTROL endpoint pid should start with DATA1 */
int status_direction;
if (devnum == priv->root_hub_devnum) {
dev->status = 0;
dev->speed = USB_SPEED_HIGH;
return dwc_otg_submit_rh_msg(priv, dev, pipe, buffer, len,
setup);
}
pid = DWC2_HC_PID_SETUP;
ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8);
if (ret)
return ret;
if (buffer) {
pid = DWC2_HC_PID_DATA1;
ret = chunk_msg(priv, dev, pipe, &pid, usb_pipein(pipe), buffer,
len);
if (ret)
return ret;
act_len = dev->act_len;
} /* End of DATA stage */
else
act_len = 0;
/* STATUS stage */
if ((len == 0) || usb_pipeout(pipe))
status_direction = 1;
else
status_direction = 0;
pid = DWC2_HC_PID_DATA1;
ret = chunk_msg(priv, dev, pipe, &pid, status_direction,
priv->status_buffer, 0);
if (ret)
return ret;
dev->act_len = act_len;
return 0;
}
int _submit_int_msg(struct dwc2_priv *priv, struct usb_device *dev,
unsigned long pipe, void *buffer, int len, int interval)
{
unsigned long timeout;
int ret;
/* FIXME: what is interval? */
timeout = get_timer(0) + USB_TIMEOUT_MS(pipe);
for (;;) {
if (get_timer(0) > timeout) {
printf("Timeout poll on interrupt endpoint\n");
return -ETIMEDOUT;
}
ret = _submit_bulk_msg(priv, dev, pipe, buffer, len);
if (ret != -EAGAIN)
return ret;
}
}
static int dwc2_init_common(struct dwc2_priv *priv)
{
struct dwc2_core_regs *regs = priv->regs;
uint32_t snpsid;
int i, j;
snpsid = readl(&regs->gsnpsid);
printf("Core Release: %x.%03x\n", snpsid >> 12 & 0xf, snpsid & 0xfff);
if ((snpsid & DWC2_SNPSID_DEVID_MASK) != DWC2_SNPSID_DEVID_VER_2xx &&
(snpsid & DWC2_SNPSID_DEVID_MASK) != DWC2_SNPSID_DEVID_VER_3xx) {
printf("SNPSID invalid (not DWC2 OTG device): %08x\n", snpsid);
return -ENODEV;
}
dwc_otg_core_init(regs);
dwc_otg_core_host_init(regs);
clrsetbits_le32(&regs->hprt0, DWC2_HPRT0_PRTENA |
DWC2_HPRT0_PRTCONNDET | DWC2_HPRT0_PRTENCHNG |
DWC2_HPRT0_PRTOVRCURRCHNG,
DWC2_HPRT0_PRTRST);
mdelay(50);
clrbits_le32(&regs->hprt0, DWC2_HPRT0_PRTENA | DWC2_HPRT0_PRTCONNDET |
DWC2_HPRT0_PRTENCHNG | DWC2_HPRT0_PRTOVRCURRCHNG |
DWC2_HPRT0_PRTRST);
for (i = 0; i < MAX_DEVICE; i++) {
for (j = 0; j < MAX_ENDPOINT; j++)
priv->bulk_data_toggle[i][j] = DWC2_HC_PID_DATA0;
}
return 0;
}
static void dwc2_uninit_common(struct dwc2_core_regs *regs)
{
/* Put everything in reset. */
clrsetbits_le32(&regs->hprt0, DWC2_HPRT0_PRTENA |
DWC2_HPRT0_PRTCONNDET | DWC2_HPRT0_PRTENCHNG |
DWC2_HPRT0_PRTOVRCURRCHNG,
DWC2_HPRT0_PRTRST);
}
#ifndef CONFIG_DM_USB
int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
int len, struct devrequest *setup)
{
return _submit_control_msg(&local, dev, pipe, buffer, len, setup);
}
int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
int len)
{
return _submit_bulk_msg(&local, dev, pipe, buffer, len);
}
int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
int len, int interval)
{
return _submit_int_msg(&local, dev, pipe, buffer, len, interval);
}
/* U-Boot USB control interface */
int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
{
struct dwc2_priv *priv = &local;
memset(priv, '\0', sizeof(*priv));
priv->root_hub_devnum = 0;
priv->regs = (struct dwc2_core_regs *)CONFIG_USB_DWC2_REG_ADDR;
priv->aligned_buffer = aligned_buffer_addr;
priv->status_buffer = status_buffer_addr;
/* board-dependant init */
if (board_usb_init(index, USB_INIT_HOST))
return -1;
return dwc2_init_common(priv);
}
int usb_lowlevel_stop(int index)
{
dwc2_uninit_common(local.regs);
return 0;
}
#endif
#ifdef CONFIG_DM_USB
static int dwc2_submit_control_msg(struct udevice *dev, struct usb_device *udev,
unsigned long pipe, void *buffer, int length,
struct devrequest *setup)
{
struct dwc2_priv *priv = dev_get_priv(dev);
debug("%s: dev='%s', udev=%p, udev->dev='%s', portnr=%d\n", __func__,
dev->name, udev, udev->dev->name, udev->portnr);
return _submit_control_msg(priv, udev, pipe, buffer, length, setup);
}
static int dwc2_submit_bulk_msg(struct udevice *dev, struct usb_device *udev,
unsigned long pipe, void *buffer, int length)
{
struct dwc2_priv *priv = dev_get_priv(dev);
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
return _submit_bulk_msg(priv, udev, pipe, buffer, length);
}
static int dwc2_submit_int_msg(struct udevice *dev, struct usb_device *udev,
unsigned long pipe, void *buffer, int length,
int interval)
{
struct dwc2_priv *priv = dev_get_priv(dev);
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
return _submit_int_msg(priv, udev, pipe, buffer, length, interval);
}
static int dwc2_usb_ofdata_to_platdata(struct udevice *dev)
{
struct dwc2_priv *priv = dev_get_priv(dev);
fdt_addr_t addr;
addr = dev_get_addr(dev);
if (addr == FDT_ADDR_T_NONE)
return -EINVAL;
priv->regs = (struct dwc2_core_regs *)addr;
return 0;
}
static int dwc2_usb_probe(struct udevice *dev)
{
struct dwc2_priv *priv = dev_get_priv(dev);
return dwc2_init_common(priv);
}
static int dwc2_usb_remove(struct udevice *dev)
{
struct dwc2_priv *priv = dev_get_priv(dev);
dwc2_uninit_common(priv->regs);
return 0;
}
struct dm_usb_ops dwc2_usb_ops = {
.control = dwc2_submit_control_msg,
.bulk = dwc2_submit_bulk_msg,
.interrupt = dwc2_submit_int_msg,
};
static const struct udevice_id dwc2_usb_ids[] = {
{ .compatible = "brcm,bcm2835-usb" },
{ .compatible = "snps,dwc2" },
{ }
};
U_BOOT_DRIVER(usb_dwc2) = {
.name = "dwc2_usb",
.id = UCLASS_USB,
.of_match = dwc2_usb_ids,
.ofdata_to_platdata = dwc2_usb_ofdata_to_platdata,
.probe = dwc2_usb_probe,
.remove = dwc2_usb_remove,
.ops = &dwc2_usb_ops,
.priv_auto_alloc_size = sizeof(struct dwc2_priv),
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};
#endif