usb: dwc3: workaround: U1/U2 -> U0 transiton
authorFelipe Balbi <balbi@ti.com>
Fri, 14 Oct 2011 10:00:30 +0000 (13:00 +0300)
committerFelipe Balbi <balbi@ti.com>
Mon, 12 Dec 2011 09:48:34 +0000 (11:48 +0200)
RTL revisions <1.83a have an issue where, depending
on the link partner, the USB link might do multiple
entry/exit of low power states before a transfer
takes place causing degraded throughput.

The suggested workaround is to clear bits
12:9 of DCTL register if we see a transition
from U1|U2 to U0 and only re-enable that on
a transfer complete IRQ and we have no pending
transfers on any of the enabled endpoints.

Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index dc2db16..b901a4d 100644 (file)
@@ -569,6 +569,7 @@ struct dwc3_hwparams {
  * @regs_size: address space size
  * @irq: IRQ number
  * @num_event_buffers: calculated number of event buffers
+ * @u1u2: only used on revisions <1.83a for workaround
  * @maximum_speed: maximum speed requested (mainly for testing purposes)
  * @revision: revision register contents
  * @mode: mode of operation
@@ -614,6 +615,7 @@ struct dwc3 {
        int                     irq;
 
        u32                     num_event_buffers;
+       u32                     u1u2;
        u32                     maximum_speed;
        u32                     revision;
        u32                     mode;
index 85cf392..0a6deea 100644 (file)
@@ -1376,6 +1376,31 @@ static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc,
                dep->flags &= ~DWC3_EP_BUSY;
                dep->res_trans_idx = 0;
        }
+
+       /*
+        * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround.
+        * See dwc3_gadget_linksts_change_interrupt() for 1st half.
+        */
+       if (dwc->revision < DWC3_REVISION_183A) {
+               u32             reg;
+               int             i;
+
+               for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) {
+                       struct dwc3_ep  *dep = dwc->eps[i];
+
+                       if (!(dep->flags & DWC3_EP_ENABLED))
+                               continue;
+
+                       if (!list_empty(&dep->req_queued))
+                               return;
+               }
+
+               reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+               reg |= dwc->u1u2;
+               dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+               dwc->u1u2 = 0;
+       }
 }
 
 static void dwc3_gadget_start_isoc(struct dwc3 *dwc,
@@ -1794,8 +1819,55 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
 static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
                unsigned int evtinfo)
 {
-       /*  The fith bit says SuperSpeed yes or no. */
-       dwc->link_state = evtinfo & DWC3_LINK_STATE_MASK;
+       enum dwc3_link_state    next = evtinfo & DWC3_LINK_STATE_MASK;
+
+       /*
+        * WORKAROUND: DWC3 Revisions <1.83a have an issue which, depending
+        * on the link partner, the USB session might do multiple entry/exit
+        * of low power states before a transfer takes place.
+        *
+        * Due to this problem, we might experience lower throughput. The
+        * suggested workaround is to disable DCTL[12:9] bits if we're
+        * transitioning from U1/U2 to U0 and enable those bits again
+        * after a transfer completes and there are no pending transfers
+        * on any of the enabled endpoints.
+        *
+        * This is the first half of that workaround.
+        *
+        * Refers to:
+        *
+        * STAR#9000446952: RTL: Device SS : if U1/U2 ->U0 takes >128us
+        * core send LGO_Ux entering U0
+        */
+       if (dwc->revision < DWC3_REVISION_183A) {
+               if (next == DWC3_LINK_STATE_U0) {
+                       u32     u1u2;
+                       u32     reg;
+
+                       switch (dwc->link_state) {
+                       case DWC3_LINK_STATE_U1:
+                       case DWC3_LINK_STATE_U2:
+                               reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+                               u1u2 = reg & (DWC3_DCTL_INITU2ENA
+                                               | DWC3_DCTL_ACCEPTU2ENA
+                                               | DWC3_DCTL_INITU1ENA
+                                               | DWC3_DCTL_ACCEPTU1ENA);
+
+                               if (!dwc->u1u2)
+                                       dwc->u1u2 = reg & u1u2;
+
+                               reg &= ~u1u2;
+
+                               dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+                               break;
+                       default:
+                               /* do nothing */
+                               break;
+                       }
+               }
+       }
+
+       dwc->link_state = next;
 
        dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state);
 }