X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=drivers%2Fusb%2Fhost%2Fdwc2.c;h=b2f4bc685af165c535eb0ecc2e73f583a333a862;hb=08b24722f0ac9159697360faed35d12852592eb3;hp=e8142ac0922f1974c2b36c00de24eaa223d11215;hpb=fc9b0b80435cda721fbdbe507c9e4f388b0ea62b;p=oweals%2Fu-boot.git diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c index e8142ac092..b2f4bc685a 100644 --- a/drivers/usb/host/dwc2.c +++ b/drivers/usb/host/dwc2.c @@ -6,10 +6,14 @@ */ #include +#include #include #include #include +#include +#include #include +#include #include #include "dwc2.h" @@ -20,44 +24,36 @@ #define DWC2_STATUS_BUF_SIZE 64 #define DWC2_DATA_BUF_SIZE (64 * 1024) -/* We need doubleword-aligned buffers for DMA transfers */ -DEFINE_ALIGN_BUFFER(uint8_t, aligned_buffer, DWC2_DATA_BUF_SIZE, 8); -DEFINE_ALIGN_BUFFER(uint8_t, status_buffer, DWC2_STATUS_BUF_SIZE, 8); - #define MAX_DEVICE 16 #define MAX_ENDPOINT 16 -static int bulk_data_toggle[MAX_DEVICE][MAX_ENDPOINT]; -static int control_data_toggle[MAX_DEVICE][MAX_ENDPOINT]; - -static int root_hub_devnum; -static struct dwc2_core_regs *regs = - (struct dwc2_core_regs *)CONFIG_USB_DWC2_REG_ADDR; +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 + u8 in_data_toggle[MAX_DEVICE][MAX_ENDPOINT]; + u8 out_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 @@ -102,7 +98,8 @@ static void dwc_otg_flush_tx_fifo(struct dwc2_core_regs *regs, const int num) writel(DWC2_GRSTCTL_TXFFLSH | (num << DWC2_GRSTCTL_TXFNUM_OFFSET), ®s->grstctl); - ret = wait_for_bit(®s->grstctl, DWC2_GRSTCTL_TXFFLSH, 0); + ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_TXFFLSH, + false, 1000, false); if (ret) printf("%s: Timeout!\n", __func__); @@ -120,7 +117,8 @@ static void dwc_otg_flush_rx_fifo(struct dwc2_core_regs *regs) int ret; writel(DWC2_GRSTCTL_RXFFLSH, ®s->grstctl); - ret = wait_for_bit(®s->grstctl, DWC2_GRSTCTL_RXFFLSH, 0); + ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_RXFFLSH, + false, 1000, false); if (ret) printf("%s: Timeout!\n", __func__); @@ -137,13 +135,15 @@ static void dwc_otg_core_reset(struct dwc2_core_regs *regs) int ret; /* Wait for AHB master IDLE state. */ - ret = wait_for_bit(®s->grstctl, DWC2_GRSTCTL_AHBIDLE, 1); + ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_AHBIDLE, + true, 1000, false); if (ret) printf("%s: Timeout!\n", __func__); /* Core Soft Reset */ writel(DWC2_GRSTCTL_CSFTRST, ®s->grstctl); - ret = wait_for_bit(®s->grstctl, DWC2_GRSTCTL_CSFTRST, 0); + ret = wait_for_bit(__func__, ®s->grstctl, DWC2_GRSTCTL_CSFTRST, + false, 1000, false); if (ret) printf("%s: Timeout!\n", __func__); @@ -228,8 +228,8 @@ static void dwc_otg_core_host_init(struct dwc2_core_regs *regs) clrsetbits_le32(®s->hc_regs[i].hcchar, DWC2_HCCHAR_EPDIR, DWC2_HCCHAR_CHEN | DWC2_HCCHAR_CHDIS); - ret = wait_for_bit(®s->hc_regs[i].hcchar, - DWC2_HCCHAR_CHEN, 0); + ret = wait_for_bit(__func__, ®s->hc_regs[i].hcchar, + DWC2_HCCHAR_CHEN, false, 1000, false); if (ret) printf("%s: Timeout!\n", __func__); } @@ -263,7 +263,9 @@ static void dwc_otg_core_init(struct dwc2_core_regs *regs) /* Program the ULPI External VBUS bit if needed */ #ifdef CONFIG_DWC2_PHY_ULPI_EXT_VBUS - usbcfg |= DWC2_GUSBCFG_ULPI_EXT_VBUS_DRV; + usbcfg |= (DWC2_GUSBCFG_ULPI_EXT_VBUS_DRV | + DWC2_GUSBCFG_ULPI_INT_VBUS_INDICATOR | + DWC2_GUSBCFG_INDICATOR_PASSTHROUGH); #else usbcfg &= ~DWC2_GUSBCFG_ULPI_EXT_VBUS_DRV; #endif @@ -398,18 +400,18 @@ static void dwc_otg_core_init(struct dwc2_core_regs *regs) * @param hc Information needed to initialize the host channel */ static void dwc_otg_hc_init(struct dwc2_core_regs *regs, uint8_t hc_num, - uint8_t dev_addr, uint8_t ep_num, uint8_t ep_is_in, - uint8_t ep_type, uint16_t max_packet) + 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]; - const 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); + 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); - /* Clear old interrupt conditions for this host channel. */ - writel(0x3fff, &hc_regs->hcint); + if (dev->speed == USB_SPEED_LOW) + hcchar |= DWC2_HCCHAR_LSPDDEV; /* * Program the HCCHARn register with the endpoint characteristics @@ -417,15 +419,29 @@ static void dwc_otg_hc_init(struct dwc2_core_regs *regs, uint8_t hc_num, */ writel(hcchar, &hc_regs->hcchar); - /* Program the HCSPLIT register for SPLITs */ + /* Program the HCSPLIT register, default to no SPLIT */ writel(0, &hc_regs->hcsplt); } +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); +} + /* * DWC2 to USB API interface */ /* Direction: In ; Request: Status */ -static int dwc_otg_submit_rh_msg_in_status(struct usb_device *dev, void *buffer, +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; @@ -463,7 +479,11 @@ static int dwc_otg_submit_rh_msg_in_status(struct usb_device *dev, void *buffer, if (hprt0 & DWC2_HPRT0_PRTPWR) port_status |= USB_PORT_STAT_POWER; - port_status |= USB_PORT_STAT_HIGH_SPEED; + 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; @@ -595,13 +615,13 @@ static int dwc_otg_submit_rh_msg_in_configuration(struct usb_device *dev, } /* Direction: In */ -static int dwc_otg_submit_rh_msg_in(struct usb_device *dev, - void *buffer, int txlen, - struct devrequest *cmd) +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(dev, buffer, + 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, @@ -616,10 +636,12 @@ static int dwc_otg_submit_rh_msg_in(struct usb_device *dev, } /* Direction: Out */ -static int dwc_otg_submit_rh_msg_out(struct usb_device *dev, - void *buffer, int txlen, - struct devrequest *cmd) +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); @@ -666,7 +688,7 @@ static int dwc_otg_submit_rh_msg_out(struct usb_device *dev, } break; case (USB_REQ_SET_ADDRESS << 8): - root_hub_devnum = wValue; + priv->root_hub_devnum = wValue; break; case (USB_REQ_SET_CONFIGURATION << 8): break; @@ -683,8 +705,8 @@ static int dwc_otg_submit_rh_msg_out(struct usb_device *dev, return stat; } -static int dwc_otg_submit_rh_msg(struct usb_device *dev, unsigned long pipe, - void *buffer, int txlen, +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; @@ -695,131 +717,221 @@ static int dwc_otg_submit_rh_msg(struct usb_device *dev, unsigned long pipe, } if (cmd->requesttype & USB_DIR_IN) - stat = dwc_otg_submit_rh_msg_in(dev, buffer, txlen, cmd); + stat = dwc_otg_submit_rh_msg_in(priv, dev, buffer, txlen, cmd); else - stat = dwc_otg_submit_rh_msg_out(dev, buffer, txlen, cmd); + stat = dwc_otg_submit_rh_msg_out(priv, dev, buffer, txlen, cmd); mdelay(1); return stat; } -/* U-Boot USB transmission interface */ -int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, - int len) +int wait_for_chhltd(struct dwc2_hc_regs *hc_regs, uint32_t *sub, u8 *toggle) +{ + int ret; + uint32_t hcint, hctsiz; + + ret = wait_for_bit(__func__, &hc_regs->hcint, DWC2_HCINT_CHHLTD, true, + 1000, false); + 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, +}; + +static int transfer_chunk(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; + + 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(aligned_buffer, buffer, xfer_len); + + flush_dcache_range((unsigned long)aligned_buffer, + (unsigned long)aligned_buffer + + roundup(xfer_len, ARCH_DMA_MINALIGN)); + } + + writel(phys_to_bus((unsigned long)aligned_buffer), &hc_regs->hcdma); + + /* Clear old interrupt conditions for this host channel. */ + writel(0x3fff, &hc_regs->hcint); + + /* 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 | + DWC2_HCCHAR_ODDFRM, + (1 << DWC2_HCCHAR_MULTICNT_OFFSET) | + (odd_frame << DWC2_HCCHAR_ODDFRM_OFFSET) | + DWC2_HCCHAR_CHEN); + + ret = wait_for_chhltd(hc_regs, &sub, pid); + if (ret < 0) + return ret; + + if (in) { + xfer_len -= sub; + + invalidate_dcache_range((unsigned long)aligned_buffer, + (unsigned long)aligned_buffer + + roundup(xfer_len, ARCH_DMA_MINALIGN)); + + memcpy(buffer, aligned_buffer, xfer_len); + } + *actual_len = xfer_len; + + return ret; +} + +int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev, + unsigned long pipe, u8 *pid, int in, void *buffer, int len) { + struct dwc2_core_regs *regs = priv->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; - uint32_t hctsiz, sub, tmp; - struct dwc2_hc_regs *hc_regs = ®s->hc_regs[DWC2_HC_CHANNEL]; - uint32_t hcint; + int ret = 0; + int do_split = 0; + int complete_split = 0; uint32_t xfer_len; uint32_t num_packets; int stop_transfer = 0; - unsigned int timeout = 1000000; - - if (devnum == root_hub_devnum) { - dev->status = 0; - return -EINVAL; - } - - if (len > DWC2_DATA_BUF_SIZE) { - printf("%s: %d is more then available buffer size (%d)\n", - __func__, len, DWC2_DATA_BUF_SIZE); - dev->status = 0; - dev->act_len = 0; - return -EINVAL; + uint32_t max_xfer_len; + int ssplit_frame_num = 0; + + 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; + + /* Initialize channel */ + dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, dev, devnum, ep, in, + eptype, max); + + /* 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); + 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; + } } - while ((done < len) && !stop_transfer) { - /* Initialize channel */ - dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, devnum, ep, - usb_pipein(pipe), DWC2_HCCHAR_EPTYPE_BULK, max); - + do { + int actual_len = 0; + uint32_t hcint; + int odd_frame = 0; xfer_len = len - done; - /* Make sure that xfer_len is a multiple of max packet size. */ - if (xfer_len > CONFIG_DWC2_MAX_TRANSFER_SIZE) - xfer_len = CONFIG_DWC2_MAX_TRANSFER_SIZE - max + 1; - if (xfer_len > 0) { + if (xfer_len > max_xfer_len) + xfer_len = max_xfer_len; + else if (xfer_len > max) num_packets = (xfer_len + max - 1) / max; - if (num_packets > CONFIG_DWC2_MAX_PACKET_COUNT) { - num_packets = CONFIG_DWC2_MAX_PACKET_COUNT; - xfer_len = num_packets * max; - } - } else { + else num_packets = 1; - } - if (usb_pipein(pipe)) - xfer_len = num_packets * max; - - writel((xfer_len << DWC2_HCTSIZ_XFERSIZE_OFFSET) | - (num_packets << DWC2_HCTSIZ_PKTCNT_OFFSET) | - (bulk_data_toggle[devnum][ep] << - DWC2_HCTSIZ_PID_OFFSET), - &hc_regs->hctsiz); - - memcpy(aligned_buffer, (char *)buffer + done, len - done); - writel((uint32_t)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); - - while (1) { - hcint = readl(&hc_regs->hcint); - - if (!(hcint & DWC2_HCINT_CHHLTD)) - continue; - - if (hcint & DWC2_HCINT_XFERCOMP) { - hctsiz = readl(&hc_regs->hctsiz); - done += xfer_len; - - sub = hctsiz & DWC2_HCTSIZ_XFERSIZE_MASK; - sub >>= DWC2_HCTSIZ_XFERSIZE_OFFSET; - - if (usb_pipein(pipe)) { - done -= sub; - if (hctsiz & DWC2_HCTSIZ_XFERSIZE_MASK) - stop_transfer = 1; - } - - tmp = hctsiz & DWC2_HCTSIZ_PID_MASK; - tmp >>= DWC2_HCTSIZ_PID_OFFSET; - if (tmp == DWC2_HC_PID_DATA1) { - bulk_data_toggle[devnum][ep] = - DWC2_HC_PID_DATA1; - } else { - bulk_data_toggle[devnum][ep] = - DWC2_HC_PID_DATA0; - } - break; - } + if (complete_split) + setbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT); + else if (do_split) + clrbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT); - if (hcint & DWC2_HCINT_STALL) { - puts("DWC OTG: Channel halted\n"); - bulk_data_toggle[devnum][ep] = - DWC2_HC_PID_DATA0; + if (eptype == DWC2_HCCHAR_EPTYPE_INTR) { + int uframe_num = readl(&host_regs->hfnum); + if (!(uframe_num & 0x1)) + odd_frame = 1; + } - stop_transfer = 1; - break; - } + ret = transfer_chunk(hc_regs, priv->aligned_buffer, pid, + in, (char *)buffer + done, num_packets, + xfer_len, &actual_len, odd_frame); - if (!--timeout) { - printf("%s: Timeout!\n", __func__); - break; + 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 (done && usb_pipein(pipe)) - memcpy(buffer, aligned_buffer, done); + if (ret) + break; + + if (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); @@ -827,195 +939,119 @@ int submit_bulk_msg(struct usb_device *dev, unsigned long pipe, void *buffer, dev->status = 0; dev->act_len = done; - return 0; + return ret; } -int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer, - int len, struct devrequest *setup) +/* U-Boot USB transmission interface */ +int _submit_bulk_msg(struct dwc2_priv *priv, struct usb_device *dev, + unsigned long pipe, void *buffer, int len) { - struct dwc2_hc_regs *hc_regs = ®s->hc_regs[DWC2_HC_CHANNEL]; - int done = 0; int devnum = usb_pipedevice(pipe); int ep = usb_pipeendpoint(pipe); - int max = usb_maxpacket(dev, pipe); - uint32_t hctsiz = 0, sub, tmp, ret; - uint32_t hcint; - const uint32_t hcint_comp_hlt_ack = DWC2_HCINT_XFERCOMP | - DWC2_HCINT_CHHLTD | DWC2_HCINT_ACK; - unsigned int timeout = 1000000; + u8* pid; - /* For CONTROL endpoint pid should start with DATA1 */ - int status_direction; - - if (devnum == root_hub_devnum) { + if ((devnum >= MAX_DEVICE) || (devnum == priv->root_hub_devnum)) { dev->status = 0; - dev->speed = USB_SPEED_HIGH; - return dwc_otg_submit_rh_msg(dev, pipe, buffer, len, setup); - } - - if (len > DWC2_DATA_BUF_SIZE) { - printf("%s: %d is more then available buffer size(%d)\n", - __func__, len, DWC2_DATA_BUF_SIZE); - dev->status = 0; - dev->act_len = 0; return -EINVAL; } - /* Initialize channel, OUT for setup buffer */ - dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, devnum, ep, 0, - DWC2_HCCHAR_EPTYPE_CONTROL, max); - - /* SETUP stage */ - writel((8 << DWC2_HCTSIZ_XFERSIZE_OFFSET) | - (1 << DWC2_HCTSIZ_PKTCNT_OFFSET) | - (DWC2_HC_PID_SETUP << DWC2_HCTSIZ_PID_OFFSET), - &hc_regs->hctsiz); - - writel((uint32_t)setup, &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); + if (usb_pipein(pipe)) + pid = &priv->in_data_toggle[devnum][ep]; + else + pid = &priv->out_data_toggle[devnum][ep]; - ret = wait_for_bit(&hc_regs->hcint, DWC2_HCINT_CHHLTD, 1); - if (ret) - printf("%s: Timeout!\n", __func__); + return chunk_msg(priv, dev, pipe, pid, usb_pipein(pipe), buffer, len); +} - hcint = readl(&hc_regs->hcint); +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 ret, act_len; + u8 pid; + /* For CONTROL endpoint pid should start with DATA1 */ + int status_direction; - if (!(hcint & DWC2_HCINT_CHHLTD) || !(hcint & DWC2_HCINT_XFERCOMP)) { - printf("%s: Error (HCINT=%08x)\n", __func__, hcint); + if (devnum == priv->root_hub_devnum) { dev->status = 0; - dev->act_len = 0; - return -EINVAL; + dev->speed = USB_SPEED_HIGH; + return dwc_otg_submit_rh_msg(priv, dev, pipe, buffer, len, + setup); } - /* Clear interrupts */ - writel(0, &hc_regs->hcintmsk); - writel(0xFFFFFFFF, &hc_regs->hcint); + /* SETUP stage */ + pid = DWC2_HC_PID_SETUP; + do { + ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8); + } while (ret == -EAGAIN); + if (ret) + return ret; + /* DATA stage */ + act_len = 0; if (buffer) { - /* DATA stage */ - dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, devnum, ep, - usb_pipein(pipe), - DWC2_HCCHAR_EPTYPE_CONTROL, max); - - /* TODO: check if len < 64 */ - control_data_toggle[devnum][ep] = DWC2_HC_PID_DATA1; - writel((len << DWC2_HCTSIZ_XFERSIZE_OFFSET) | - (1 << DWC2_HCTSIZ_PKTCNT_OFFSET) | - (control_data_toggle[devnum][ep] << - DWC2_HCTSIZ_PID_OFFSET), - &hc_regs->hctsiz); - - writel((uint32_t)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); - - while (1) { - hcint = readl(&hc_regs->hcint); - if (!(hcint & DWC2_HCINT_CHHLTD)) - continue; - - if (hcint & DWC2_HCINT_XFERCOMP) { - hctsiz = readl(&hc_regs->hctsiz); - done = len; - - sub = hctsiz & DWC2_HCTSIZ_XFERSIZE_MASK; - sub >>= DWC2_HCTSIZ_XFERSIZE_OFFSET; - - if (usb_pipein(pipe)) - done -= sub; - } - - if (hcint & DWC2_HCINT_ACK) { - tmp = hctsiz & DWC2_HCTSIZ_PID_MASK; - tmp >>= DWC2_HCTSIZ_PID_OFFSET; - if (tmp == DWC2_HC_PID_DATA0) { - control_data_toggle[devnum][ep] = - DWC2_HC_PID_DATA0; - } else { - control_data_toggle[devnum][ep] = - DWC2_HC_PID_DATA1; - } - } - - if (hcint != hcint_comp_hlt_ack) { - printf("%s: Error (HCINT=%08x)\n", - __func__, hcint); - goto out; - } - - if (!--timeout) { - printf("%s: Timeout!\n", __func__); - goto out; - } - - break; - } - } /* End of DATA stage */ - - /* STATUS stage */ - if ((len == 0) || usb_pipeout(pipe)) + pid = DWC2_HC_PID_DATA1; + do { + ret = chunk_msg(priv, dev, pipe, &pid, usb_pipein(pipe), + buffer, len); + act_len += dev->act_len; + buffer += dev->act_len; + len -= dev->act_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; - else - status_direction = 0; - - dwc_otg_hc_init(regs, DWC2_HC_CHANNEL, devnum, ep, - status_direction, DWC2_HCCHAR_EPTYPE_CONTROL, max); - - writel((1 << DWC2_HCTSIZ_PKTCNT_OFFSET) | - (DWC2_HC_PID_DATA1 << DWC2_HCTSIZ_PID_OFFSET), - &hc_regs->hctsiz); - - writel((uint32_t)status_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); - - while (1) { - hcint = readl(&hc_regs->hcint); - if (hcint & DWC2_HCINT_CHHLTD) - break; } - if (hcint != hcint_comp_hlt_ack) - printf("%s: Error (HCINT=%08x)\n", __func__, hcint); + /* STATUS stage */ + pid = DWC2_HC_PID_DATA1; + do { + ret = chunk_msg(priv, dev, pipe, &pid, status_direction, + priv->status_buffer, 0); + } while (ret == -EAGAIN); + if (ret) + return ret; -out: - dev->act_len = done; - dev->status = 0; + dev->act_len = act_len; - return done; + return 0; } -int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, - int len, int interval) +int _submit_int_msg(struct dwc2_priv *priv, struct usb_device *dev, + unsigned long pipe, void *buffer, int len, int interval) { - printf("dev = %p pipe = %#lx buf = %p size = %d int = %d\n", - dev, pipe, buffer, len, interval); - return -ENOSYS; + 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; + } } -/* U-Boot USB control interface */ -int usb_lowlevel_init(int index, enum usb_init_type init, void **controller) +static int dwc2_init_common(struct dwc2_priv *priv) { + struct dwc2_core_regs *regs = priv->regs; uint32_t snpsid; int i, j; - root_hub_devnum = 0; - snpsid = readl(®s->gsnpsid); printf("Core Release: %x.%03x\n", snpsid >> 12 & 0xf, snpsid & 0xfff); - if ((snpsid & DWC2_SNPSID_DEVID_MASK) != DWC2_SNPSID_DEVID_VER_2xx) { + 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; } @@ -1034,20 +1070,152 @@ int usb_lowlevel_init(int index, enum usb_init_type init, void **controller) for (i = 0; i < MAX_DEVICE; i++) { for (j = 0; j < MAX_ENDPOINT; j++) { - control_data_toggle[i][j] = DWC2_HC_PID_DATA1; - bulk_data_toggle[i][j] = DWC2_HC_PID_DATA0; + priv->in_data_toggle[i][j] = DWC2_HC_PID_DATA0; + priv->out_data_toggle[i][j] = DWC2_HC_PID_DATA0; } } return 0; } -int usb_lowlevel_stop(int index) +static void dwc2_uninit_common(struct dwc2_core_regs *regs) { /* Put everything in reset. */ clrsetbits_le32(®s->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