OSDN Git Service

usb: dwc3: core: make dwc3_set_mode() work properly
authorRoger Quadros <rogerq@ti.com>
Tue, 4 Apr 2017 09:49:18 +0000 (12:49 +0300)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Tue, 11 Apr 2017 07:58:30 +0000 (10:58 +0300)
We can't have both Host and Peripheral roles active at the same time
because of one detail on DWC3: it shares the same memory area for both
Host and Peripheral registers.

When swapping roles we must reinitialize the new role every
time. Let's make sure this works for our debugfs interface.

Signed-off-by: Roger Quadros <rogerq@ti.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/debugfs.c

index 68e6a7b..458e7c6 100644 (file)
@@ -100,7 +100,10 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
        return 0;
 }
 
-void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+static void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
+static int dwc3_event_buffers_setup(struct dwc3 *dwc);
+
+static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
 {
        u32 reg;
 
@@ -108,8 +111,69 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
        reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
        reg |= DWC3_GCTL_PRTCAPDIR(mode);
        dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+}
+
+static void __dwc3_set_mode(struct work_struct *work)
+{
+       struct dwc3 *dwc = work_to_dwc(work);
+       unsigned long flags;
+       int ret;
+
+       if (!dwc->desired_dr_role)
+               return;
+
+       if (dwc->desired_dr_role == dwc->current_dr_role)
+               return;
+
+       if (dwc->dr_mode != USB_DR_MODE_OTG)
+               return;
+
+       switch (dwc->current_dr_role) {
+       case DWC3_GCTL_PRTCAP_HOST:
+               dwc3_host_exit(dwc);
+               break;
+       case DWC3_GCTL_PRTCAP_DEVICE:
+               dwc3_gadget_exit(dwc);
+               dwc3_event_buffers_cleanup(dwc);
+               break;
+       default:
+               break;
+       }
+
+       spin_lock_irqsave(&dwc->lock, flags);
+
+       dwc3_set_prtcap(dwc, dwc->desired_dr_role);
 
-       dwc->current_dr_role = mode;
+       dwc->current_dr_role = dwc->desired_dr_role;
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       switch (dwc->desired_dr_role) {
+       case DWC3_GCTL_PRTCAP_HOST:
+               ret = dwc3_host_init(dwc);
+               if (ret)
+                       dev_err(dwc->dev, "failed to initialize host\n");
+               break;
+       case DWC3_GCTL_PRTCAP_DEVICE:
+               dwc3_event_buffers_setup(dwc);
+               ret = dwc3_gadget_init(dwc);
+               if (ret)
+                       dev_err(dwc->dev, "failed to initialize peripheral\n");
+               break;
+       default:
+               break;
+       }
+}
+
+void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&dwc->lock, flags);
+       dwc->desired_dr_role = mode;
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       queue_work(system_power_efficient_wq, &dwc->drd_work);
 }
 
 u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type)
@@ -721,21 +785,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
                goto err4;
        }
 
-       switch (dwc->dr_mode) {
-       case USB_DR_MODE_PERIPHERAL:
-               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
-               break;
-       case USB_DR_MODE_HOST:
-               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
-               break;
-       case USB_DR_MODE_OTG:
-               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
-               break;
-       default:
-               dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode);
-               break;
-       }
-
        /*
         * ENDXFER polling is available on version 3.10a and later of
         * the DWC_usb3 controller. It is NOT available in the
@@ -853,6 +902,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
 
        switch (dwc->dr_mode) {
        case USB_DR_MODE_PERIPHERAL:
+               dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
                ret = dwc3_gadget_init(dwc);
                if (ret) {
                        if (ret != -EPROBE_DEFER)
@@ -861,6 +911,7 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
                }
                break;
        case USB_DR_MODE_HOST:
+               dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
                ret = dwc3_host_init(dwc);
                if (ret) {
                        if (ret != -EPROBE_DEFER)
@@ -869,19 +920,8 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
                }
                break;
        case USB_DR_MODE_OTG:
-               ret = dwc3_host_init(dwc);
-               if (ret) {
-                       if (ret != -EPROBE_DEFER)
-                               dev_err(dev, "failed to initialize host\n");
-                       return ret;
-               }
-
-               ret = dwc3_gadget_init(dwc);
-               if (ret) {
-                       if (ret != -EPROBE_DEFER)
-                               dev_err(dev, "failed to initialize gadget\n");
-                       return ret;
-               }
+               INIT_WORK(&dwc->drd_work, __dwc3_set_mode);
+               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
                break;
        default:
                dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -901,8 +941,9 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc)
                dwc3_host_exit(dwc);
                break;
        case USB_DR_MODE_OTG:
-               dwc3_host_exit(dwc);
+               dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
                dwc3_gadget_exit(dwc);
+               flush_work(&dwc->drd_work);
                break;
        default:
                /* do nothing */
index 3464dbf..1fe23e3 100644 (file)
@@ -28,6 +28,7 @@
 #include <linux/mm.h>
 #include <linux/debugfs.h>
 #include <linux/wait.h>
+#include <linux/workqueue.h>
 
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
@@ -760,6 +761,7 @@ struct dwc3_scratchpad_array {
 
 /**
  * struct dwc3 - representation of our controller
+ * @drd_work - workqueue used for role swapping
  * @ep0_trb: trb which is used for the ctrl_req
  * @setup_buf: used while precessing STD USB requests
  * @ep0_trb: dma address of ep0_trb
@@ -782,6 +784,7 @@ struct dwc3_scratchpad_array {
  * @revision: revision register contents
  * @dr_mode: requested mode of operation
  * @current_dr_role: current role of operation when in dual-role mode
+ * @desired_dr_role: desired role of operation when in dual-role mode
  * @hsphy_mode: UTMI phy mode, one of following:
  *             - USBPHY_INTERFACE_MODE_UTMI
  *             - USBPHY_INTERFACE_MODE_UTMIW
@@ -855,6 +858,7 @@ struct dwc3_scratchpad_array {
  *                 increments or 0 to disable.
  */
 struct dwc3 {
+       struct work_struct      drd_work;
        struct dwc3_trb         *ep0_trb;
        void                    *bounce;
        void                    *scratchbuf;
@@ -893,6 +897,7 @@ struct dwc3 {
 
        enum usb_dr_mode        dr_mode;
        u32                     current_dr_role;
+       u32                     desired_dr_role;
        enum usb_phy_interface  hsphy_mode;
 
        u32                     fladj;
@@ -1002,7 +1007,7 @@ struct dwc3 {
        u16                     imod_interval;
 };
 
-/* -------------------------------------------------------------------------- */
+#define work_to_dwc(w)         (container_of((w), struct dwc3, drd_work))
 
 /* -------------------------------------------------------------------------- */
 
index e72f263..7be963d 100644 (file)
@@ -319,7 +319,6 @@ static ssize_t dwc3_mode_write(struct file *file,
 {
        struct seq_file         *s = file->private_data;
        struct dwc3             *dwc = s->private;
-       unsigned long           flags;
        u32                     mode = 0;
        char                    buf[32];
 
@@ -335,11 +334,8 @@ static ssize_t dwc3_mode_write(struct file *file,
        if (!strncmp(buf, "otg", 3))
                mode = DWC3_GCTL_PRTCAP_OTG;
 
-       if (mode) {
-               spin_lock_irqsave(&dwc->lock, flags);
-               dwc3_set_mode(dwc, mode);
-               spin_unlock_irqrestore(&dwc->lock, flags);
-       }
+       dwc3_set_mode(dwc, mode);
+
        return count;
 }