summaryrefslogtreecommitdiffstats
path: root/drivers/usb/host
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/host')
-rw-r--r--drivers/usb/host/dwc2.c81
1 files changed, 66 insertions, 15 deletions
diff --git a/drivers/usb/host/dwc2.c b/drivers/usb/host/dwc2.c
index 7fbbc4bc7f..413845cdc2 100644
--- a/drivers/usb/host/dwc2.c
+++ b/drivers/usb/host/dwc2.c
@@ -837,6 +837,8 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
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;
@@ -859,8 +861,26 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
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(&regs->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;
+ }
+ }
+
do {
int actual_len = 0;
+ uint32_t hcint;
xfer_len = len - done;
if (xfer_len > max_xfer_len)
@@ -870,10 +890,29 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
else
num_packets = 1;
+ if (complete_split)
+ setbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT);
+ else if (do_split)
+ clrbits_le32(&hc_regs->hcsplt, DWC2_HCSPLT_COMPSPLT);
+
ret = transfer_chunk(hc_regs, priv->aligned_buffer, pid,
in, (char *)buffer + done, num_packets,
xfer_len, &actual_len);
+ hcint = readl(&hc_regs->hcint);
+ if (complete_split) {
+ stop_transfer = 0;
+ if (hcint & DWC2_HCINT_NYET)
+ ret = 0;
+ else
+ complete_split = 0;
+ } else if (do_split) {
+ if (hcint & DWC2_HCINT_ACK) {
+ ret = 0;
+ complete_split = 1;
+ }
+ }
+
if (ret)
break;
@@ -882,7 +921,11 @@ int chunk_msg(struct dwc2_priv *priv, struct usb_device *dev,
done += actual_len;
- } while ((done < len) && !stop_transfer);
+ /* 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);
@@ -925,31 +968,39 @@ static int _submit_control_msg(struct dwc2_priv *priv, struct usb_device *dev,
setup);
}
+ /* SETUP stage */
pid = DWC2_HC_PID_SETUP;
- ret = chunk_msg(priv, dev, pipe, &pid, 0, setup, 8);
+ 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) {
pid = DWC2_HC_PID_DATA1;
- ret = chunk_msg(priv, dev, pipe, &pid, usb_pipein(pipe), buffer,
- len);
+ 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;
- act_len = dev->act_len;
- } /* End of DATA stage */
- else
- act_len = 0;
-
- /* STATUS stage */
- if ((len == 0) || usb_pipeout(pipe))
+ status_direction = usb_pipeout(pipe);
+ } else {
+ /* No-data CONTROL always ends with an IN transaction */
status_direction = 1;
- else
- status_direction = 0;
+ }
+ /* STATUS stage */
pid = DWC2_HC_PID_DATA1;
- ret = chunk_msg(priv, dev, pipe, &pid, status_direction,
- priv->status_buffer, 0);
+ do {
+ ret = chunk_msg(priv, dev, pipe, &pid, status_direction,
+ priv->status_buffer, 0);
+ } while (ret == -EAGAIN);
if (ret)
return ret;
OpenPOWER on IntegriCloud