The existing workaround of forcing DEVSPD to SUPER_SPEED
for HIGH_SPEED ports is causing another side effect
which causes erratic interrupts and delayed gadget
enumeration of upto 2 seconds.

Work around the run/stop issue by detecting if
it happened using debug LTSSM state and issuing
soft reset to the device controller when changing RUN_STOP
from 0 to 1.

We apply the workaround only if PRTCAP is DEVICE mode
as we don't yet support this workaround in OTG mode.

Use USB RESET event as exit condition for workaround.

Signed-off-by: Roger Quadros <rog...@ti.com>
---
 drivers/usb/dwc3/core.h   |   1 +
 drivers/usb/dwc3/gadget.c | 175 +++++++++++++++++++++++++++++++++++++---------
 2 files changed, 144 insertions(+), 32 deletions(-)

diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 2bea1ac..a724c0d 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -762,6 +762,7 @@ struct dwc3 {
 
        struct usb_gadget       gadget;
        struct usb_gadget_driver *gadget_driver;
+       struct completion       reset_event; /* used for run/stop workaround */
 
        struct usb_phy          *usb2_phy;
        struct usb_phy          *usb3_phy;
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 3ac170f..03418b8 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -35,6 +35,9 @@
 #include "gadget.h"
 #include "io.h"
 
+static void dwc3_gadget_disable_irq(struct dwc3 *dwc);
+static int dwc3_gadget_restart(struct dwc3 *dwc);
+
 /**
  * dwc3_gadget_set_test_mode - Enables USB2 Test Modes
  * @dwc: pointer to our context structure
@@ -1570,13 +1573,100 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, 
int is_on)
        struct dwc3             *dwc = gadget_to_dwc(g);
        unsigned long           flags;
        int                     ret;
+       int                     trys = 0;
 
        is_on = !!is_on;
 
+       if (is_on)
+               reinit_completion(&dwc->reset_event);
+
        spin_lock_irqsave(&dwc->lock, flags);
        ret = dwc3_gadget_run_stop(dwc, is_on, false);
        spin_unlock_irqrestore(&dwc->lock, flags);
 
+try:
+       /**
+        * WORKAROUND: DWC3 revision < 2.20a have an issue
+        * which would cause metastability state on Run/Stop
+        * bit if we try to force the IP to USB2-only mode.
+        *
+        * Because of that, we check if we hit that issue and
+        * reset core and retry if we did.
+        *
+        * We only attempt this workaround if we are in
+        * DEVICE mode (i.e. not OTG).
+        *
+        * Refers to:
+        *
+        * STAR#9000525659: Clock Domain Crossing on DCTL in
+        * USB 2.0 Mode
+        */
+       if (is_on && dwc->revision < DWC3_REVISION_220A &&
+           dwc->prtcap_mode == DWC3_GCTL_PRTCAP_DEVICE) {
+               u32 devspd, ltssm;
+               unsigned long t;
+
+               /* only applicable if devspd != SUPERSPEED */
+               devspd = dwc3_readl(dwc->regs, DWC3_DCFG) & 
DWC3_DCFG_SPEED_MASK;
+               if (devspd == DWC3_DCFG_SUPERSPEED)
+                       goto done;
+
+               /* get link state */
+               ltssm = dwc3_readl(dwc->regs, DWC3_GDBGLTSSM);
+               ltssm = (ltssm >> 22) & 0xf;
+
+               /**
+                * Need to wait for 100ms and check if ltssm != 4 to detect
+                * metastability issue. If we got a reset event then we are
+                * safe and can continue.
+                */
+               t = wait_for_completion_timeout(&dwc->reset_event,
+                                               msecs_to_jiffies(100));
+               if (t)
+                       goto done;
+
+               /**
+                * If link state != 4 we've hit the metastability issue, soft 
reset.
+                */
+               if (ltssm == 4)
+                       goto done;
+
+               dwc3_trace(trace_dwc3_gadget,
+                          "applying metastability workaround\n");
+               trys++;
+               if (trys == 2) {
+                       dev_WARN_ONCE(dwc->dev, true,
+                                     "metastability workaround failed!\n");
+                       return -ETIMEDOUT;
+               }
+
+               spin_lock_irqsave(&dwc->lock, flags);
+               /* stop gadget */
+               dwc3_gadget_disable_irq(dwc);
+               __dwc3_gadget_ep_disable(dwc->eps[0]);
+               __dwc3_gadget_ep_disable(dwc->eps[1]);
+
+               /* soft reset device and restart */
+               ret = dwc3_device_reinit(dwc);
+               if (ret) {
+                       dev_err(dwc->dev, "device reinit failed\n");
+                       return ret;
+               }
+
+               reinit_completion(&dwc->reset_event);
+               /* restart gadget */
+               ret = dwc3_gadget_restart(dwc);
+               if (ret) {
+                       dev_err(dwc->dev, "failed to re-init gadget\n");
+                       return ret;
+               }
+
+               spin_unlock_irqrestore(&dwc->lock, flags);
+               goto try;
+       }
+
+done:
+
        return ret;
 }
 
@@ -1607,37 +1697,12 @@ static void dwc3_gadget_disable_irq(struct dwc3 *dwc)
 static irqreturn_t dwc3_interrupt(int irq, void *_dwc);
 static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc);
 
-static int dwc3_gadget_start(struct usb_gadget *g,
-               struct usb_gadget_driver *driver)
+static int dwc3_gadget_restart(struct dwc3 *dwc)
 {
-       struct dwc3             *dwc = gadget_to_dwc(g);
        struct dwc3_ep          *dep;
-       unsigned long           flags;
        int                     ret = 0;
-       int                     irq;
        u32                     reg;
 
-       irq = platform_get_irq(to_platform_device(dwc->dev), 0);
-       ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
-                       IRQF_SHARED, "dwc3", dwc);
-       if (ret) {
-               dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
-                               irq, ret);
-               goto err0;
-       }
-
-       spin_lock_irqsave(&dwc->lock, flags);
-
-       if (dwc->gadget_driver) {
-               dev_err(dwc->dev, "%s is already bound to %s\n",
-                               dwc->gadget.name,
-                               dwc->gadget_driver->driver.name);
-               ret = -EBUSY;
-               goto err1;
-       }
-
-       dwc->gadget_driver      = driver;
-
        reg = dwc3_readl(dwc->regs, DWC3_DCFG);
        reg &= ~(DWC3_DCFG_SPEED_MASK);
 
@@ -1649,12 +1714,15 @@ static int dwc3_gadget_start(struct usb_gadget *g,
         * Because of that, we cannot configure the IP to any
         * speed other than the SuperSpeed
         *
+        * For non OTG mode we can attempt softreset workaround.
+        *
         * Refers to:
         *
         * STAR#9000525659: Clock Domain Crossing on DCTL in
         * USB 2.0 Mode
         */
-       if (dwc->revision < DWC3_REVISION_220A) {
+       if ((dwc->revision < DWC3_REVISION_220A) &&
+            (dwc->prtcap_mode == DWC3_GCTL_PRTCAP_OTG)) {
                reg |= DWC3_DCFG_SUPERSPEED;
        } else {
                switch (dwc->maximum_speed) {
@@ -1689,7 +1757,7 @@ static int dwc3_gadget_start(struct usb_gadget *g,
                        false);
        if (ret) {
                dev_err(dwc->dev, "failed to enable %s\n", dep->name);
-               goto err2;
+               return ret;
        }
 
        dep = dwc->eps[1];
@@ -1697,7 +1765,7 @@ static int dwc3_gadget_start(struct usb_gadget *g,
                        false);
        if (ret) {
                dev_err(dwc->dev, "failed to enable %s\n", dep->name);
-               goto err3;
+               goto err;
        }
 
        /* begin to receive SETUP packets */
@@ -1706,12 +1774,50 @@ static int dwc3_gadget_start(struct usb_gadget *g,
 
        dwc3_gadget_enable_irq(dwc);
 
-       spin_unlock_irqrestore(&dwc->lock, flags);
-
        return 0;
 
-err3:
+err:
        __dwc3_gadget_ep_disable(dwc->eps[0]);
+       return ret;
+}
+
+static int dwc3_gadget_start(struct usb_gadget *g,
+               struct usb_gadget_driver *driver)
+{
+       struct dwc3             *dwc = gadget_to_dwc(g);
+       unsigned long           flags;
+       int                     ret = 0;
+       int                     irq;
+
+       irq = platform_get_irq(to_platform_device(dwc->dev), 0);
+       ret = request_threaded_irq(irq, dwc3_interrupt, dwc3_thread_interrupt,
+                       IRQF_SHARED, "dwc3", dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
+                               irq, ret);
+               goto err0;
+       }
+
+       spin_lock_irqsave(&dwc->lock, flags);
+
+       if (dwc->gadget_driver) {
+               dev_err(dwc->dev, "%s is already bound to %s\n",
+                               dwc->gadget.name,
+                               dwc->gadget_driver->driver.name);
+               ret = -EBUSY;
+               goto err1;
+       }
+
+       dwc->gadget_driver      = driver;
+
+       ret = dwc3_gadget_restart(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "gadget start failed\n");
+               goto err2;
+       }
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       return 0;
 
 err2:
        dwc->gadget_driver = NULL;
@@ -2321,6 +2427,9 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc)
                        dwc3_gadget_disconnect_interrupt(dwc);
        }
 
+       /* notify run/stop metastability workaround */
+       complete(&dwc->reset_event);
+
        dwc3_reset_gadget(dwc);
 
        reg = dwc3_readl(dwc->regs, DWC3_DCTL);
@@ -2868,6 +2977,8 @@ int dwc3_gadget_init(struct dwc3 *dwc)
         */
        dwc->gadget.quirk_ep_out_aligned_size = true;
 
+       init_completion(&dwc->reset_event);
+
        /*
         * REVISIT: Here we should clear all pending IRQs to be
         * sure we're starting from a well known location.
-- 
2.5.0

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to