dwc3: gadget: reinitialize core after each role change
authorRobert Baldyga <r.baldyga@samsung.com>
Mon, 23 Feb 2015 15:20:48 +0000 (16:20 +0100)
committerMarek Szyprowski <m.szyprowski@samsung.com>
Mon, 13 Apr 2015 10:44:43 +0000 (12:44 +0200)
According to the Databook in case of reconnection and role switching
the core should be completely reinitialized, excepting first connection
as peripheral when core was initialized during probing.

Signed-off-by: Robert Baldyga <r.baldyga@samsung.com>
drivers/usb/dwc3/gadget.c

index 399a33d..6ab2096 100644 (file)
@@ -1422,13 +1422,120 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g,
        return 0;
 }
 
+static int dwc3_udc_init(struct dwc3 *dwc)
+{
+       struct dwc3_ep          *dep;
+       int                     ret = 0;
+       u32                     reg;
+
+       reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+       reg &= ~(DWC3_DCFG_SPEED_MASK);
+
+       /**
+        * 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 cannot configure the IP to any
+        * speed other than the SuperSpeed
+        *
+        * Refers to:
+        *
+        * STAR#9000525659: Clock Domain Crossing on DCTL in
+        * USB 2.0 Mode
+        */
+       if (dwc->revision < DWC3_REVISION_220A) {
+               reg |= DWC3_DCFG_SUPERSPEED;
+       } else {
+               switch (dwc->maximum_speed) {
+               case USB_SPEED_LOW:
+                       reg |= DWC3_DSTS_LOWSPEED;
+                       break;
+               case USB_SPEED_FULL:
+                       reg |= DWC3_DSTS_FULLSPEED1;
+                       break;
+               case USB_SPEED_HIGH:
+                       reg |= DWC3_DSTS_HIGHSPEED;
+                       break;
+               case USB_SPEED_SUPER:   /* FALLTHROUGH */
+               case USB_SPEED_UNKNOWN: /* FALTHROUGH */
+               default:
+                       reg |= DWC3_DSTS_SUPERSPEED;
+               }
+       }
+       dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+       dwc->start_config_issued = false;
+
+       /* Start with SuperSpeed Default */
+       dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
+
+       dep = dwc->eps[0];
+       ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc,
+               NULL, false, false);
+       if (ret) {
+               dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+               goto err0;
+       }
+
+       dep = dwc->eps[1];
+       ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc,
+               NULL, false, false);
+       if (ret) {
+               dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+               goto err1;
+       }
+
+       /* begin to receive SETUP packets */
+       dwc->ep0state = EP0_SETUP_PHASE;
+       dwc3_ep0_out_start(dwc);
+
+       return 0;
+
+err1:
+       __dwc3_gadget_ep_disable(dwc->eps[0]);
+
+err0:
+       return ret;
+}
+
+static void dwc3_gadget_enable_irq(struct dwc3 *dwc);
+static void dwc3_gadget_disable_irq(struct dwc3 *dwc);
+
 static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
 {
        u32                     reg;
        u32                     timeout = 500;
+       int                     ret;
 
        reg = dwc3_readl(dwc->regs, DWC3_DCTL);
        if (is_on) {
+               /*
+                * According to the Databook in case of reconnection and
+                * role switching the core should be completely reinitialized.
+                * Exception: first connection as peripheral when core was
+                * initialized during probing.
+                */
+               if (dwc->needs_reinit) {
+                       ret = dwc3_core_init(dwc);
+                       if (ret) {
+                               dev_err(dwc->dev, "failed to reinitialize core\n");
+                               return ret;
+                       }
+
+                       dwc3_event_buffers_setup(dwc);
+               }
+
+               ret = dwc3_udc_init(dwc);
+               if (ret) {
+                       dev_err(dwc->dev, "failed to initialize udc\n");
+                       return ret;
+               }
+
+               dwc3_gadget_enable_irq(dwc);
+
+               dwc->needs_reinit = 0;
+
                if (dwc->revision <= DWC3_REVISION_187A) {
                        reg &= ~DWC3_DCTL_TRGTULST_MASK;
                        reg |= DWC3_DCTL_TRGTULST_RX_DET;
@@ -1443,6 +1550,10 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
 
                dwc->pullups_connected = true;
        } else {
+               dwc3_gadget_disable_irq(dwc);
+               __dwc3_gadget_ep_disable(dwc->eps[0]);
+               __dwc3_gadget_ep_disable(dwc->eps[1]);
+
                reg &= ~DWC3_DCTL_RUN_STOP;
 
                if (dwc->has_hibernation && !suspend)
@@ -1468,6 +1579,12 @@ static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
                udelay(1);
        } while (1);
 
+       if (!is_on) {
+               /* when everything is done, shutdown PHYs */
+               dwc3_core_exit(dwc);
+               dwc->needs_reinit = 1;
+       }
+
        dwc3_trace(trace_dwc3_gadget, "gadget %s data soft-%s",
                        dwc->gadget_driver
                        ? dwc->gadget_driver->function : "no-function",
@@ -1563,11 +1680,9 @@ static int dwc3_gadget_start(struct usb_gadget *g,
                struct usb_gadget_driver *driver)
 {
        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,
@@ -1590,80 +1705,10 @@ static int dwc3_gadget_start(struct usb_gadget *g,
 
        dwc->gadget_driver      = driver;
 
-       reg = dwc3_readl(dwc->regs, DWC3_DCFG);
-       reg &= ~(DWC3_DCFG_SPEED_MASK);
-
-       /**
-        * 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 cannot configure the IP to any
-        * speed other than the SuperSpeed
-        *
-        * Refers to:
-        *
-        * STAR#9000525659: Clock Domain Crossing on DCTL in
-        * USB 2.0 Mode
-        */
-       if (dwc->revision < DWC3_REVISION_220A) {
-               reg |= DWC3_DCFG_SUPERSPEED;
-       } else {
-               switch (dwc->maximum_speed) {
-               case USB_SPEED_LOW:
-                       reg |= DWC3_DSTS_LOWSPEED;
-                       break;
-               case USB_SPEED_FULL:
-                       reg |= DWC3_DSTS_FULLSPEED1;
-                       break;
-               case USB_SPEED_HIGH:
-                       reg |= DWC3_DSTS_HIGHSPEED;
-                       break;
-               case USB_SPEED_SUPER:   /* FALLTHROUGH */
-               case USB_SPEED_UNKNOWN: /* FALTHROUGH */
-               default:
-                       reg |= DWC3_DSTS_SUPERSPEED;
-               }
-       }
-       dwc3_writel(dwc->regs, DWC3_DCFG, reg);
-
-       dwc->start_config_issued = false;
-
-       /* Start with SuperSpeed Default */
-       dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
-
-       dep = dwc->eps[0];
-       ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false,
-                       false);
-       if (ret) {
-               dev_err(dwc->dev, "failed to enable %s\n", dep->name);
-               goto err2;
-       }
-
-       dep = dwc->eps[1];
-       ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false,
-                       false);
-       if (ret) {
-               dev_err(dwc->dev, "failed to enable %s\n", dep->name);
-               goto err3;
-       }
-
-       /* begin to receive SETUP packets */
-       dwc->ep0state = EP0_SETUP_PHASE;
-       dwc3_ep0_out_start(dwc);
-
-       dwc3_gadget_enable_irq(dwc);
-
        spin_unlock_irqrestore(&dwc->lock, flags);
 
        return 0;
 
-err3:
-       __dwc3_gadget_ep_disable(dwc->eps[0]);
-
-err2:
-       dwc->gadget_driver = NULL;
-
 err1:
        spin_unlock_irqrestore(&dwc->lock, flags);