This patch implemented a feature to dynamic switch to host or device role under
debugfs for some physical limitation that unable to leverage connector A/B
cables (ID pin) to change roles.

The default role should be set as OTG mode. Then use below commands:

[1] switch to host:
echo host > /sys/kernel/debug/dwc3.0.auto/mode

[2] switch to device:
echo device > /sys/kernel/debug/dwc3.0.auto/mode

[3] switch to otg (default mode):
echo otg > /sys/kernel/debug/dwc3.0.auto/mode

Signed-off-by: Huang Rui <ray.hu...@amd.com>
---
 drivers/usb/dwc3/Makefile  |   2 +-
 drivers/usb/dwc3/core.c    |   4 +-
 drivers/usb/dwc3/core.h    |   6 ++
 drivers/usb/dwc3/debugfs.c |  19 ++++--
 drivers/usb/dwc3/drd.c     | 163 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/dwc3/drd.h     |  30 +++++++++
 drivers/usb/dwc3/gadget.c  |  92 ++++++++++++++++---------
 drivers/usb/dwc3/gadget.h  |   3 +
 drivers/usb/dwc3/host.c    |   2 +
 9 files changed, 280 insertions(+), 41 deletions(-)
 create mode 100644 drivers/usb/dwc3/drd.c
 create mode 100644 drivers/usb/dwc3/drd.h

diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index bb34fbc..115bbc7 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -13,7 +13,7 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) 
$(CONFIG_USB_DWC3_DUAL_ROLE)),)
 endif
 
 ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
-       dwc3-y                          += gadget.o ep0.o
+       dwc3-y                          += gadget.o ep0.o drd.o
 endif
 
 ifneq ($(CONFIG_DEBUG_FS),)
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 6138c5d..7b50584 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -61,7 +61,7 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
  * dwc3_core_soft_reset - Issues core soft reset and PHY reset
  * @dwc: pointer to our context structure
  */
-static int dwc3_core_soft_reset(struct dwc3 *dwc)
+int dwc3_core_soft_reset(struct dwc3 *dwc)
 {
        u32             reg;
        int             ret;
@@ -228,7 +228,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, 
unsigned length)
  *
  * Returns 0 on success otherwise negative errno.
  */
-static int dwc3_event_buffers_setup(struct dwc3 *dwc)
+int dwc3_event_buffers_setup(struct dwc3 *dwc)
 {
        struct dwc3_event_buffer        *evt;
        int                             n;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index a1c7dc5..3a30f33 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -643,6 +643,8 @@ struct dwc3_scratchpad_array {
  * @maximum_speed: maximum speed requested (mainly for testing purposes)
  * @revision: revision register contents
  * @quirks: represents different SOCs hardware work-arounds and quirks
+ * @has_gadget: true when gadget is initialized
+ * @has_xhci: true when xhci is initialized
  * @dr_mode: requested mode of operation
  * @usb2_phy: pointer to USB2 PHY
  * @usb3_phy: pointer to USB3 PHY
@@ -750,6 +752,8 @@ struct dwc3 {
 
 #define DWC3_AMD_NL_PLAT       (1 << 0)
 
+       bool                    has_gadget;
+       bool                    has_xhci;
        enum dwc3_ep0_next      ep0_next_event;
        enum dwc3_ep0_state     ep0state;
        enum dwc3_link_state    link_state;
@@ -935,6 +939,8 @@ struct dwc3_gadget_ep_cmd_params {
 /* prototypes */
 void dwc3_set_mode(struct dwc3 *dwc, u32 mode);
 int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc);
+int dwc3_core_soft_reset(struct dwc3 *dwc);
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
 
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_host_init(struct dwc3 *dwc);
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index 9ac37fe..76384df 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -32,6 +32,7 @@
 #include "gadget.h"
 #include "io.h"
 #include "debug.h"
+#include "drd.h"
 
 #define dump_register(nm)                              \
 {                                                      \
@@ -394,7 +395,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];
 
@@ -410,10 +410,19 @@ 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);
+       switch (mode) {
+       case DWC3_GCTL_PRTCAP_DEVICE:
+               dwc3_drd_to_device(dwc);
+               break;
+       case DWC3_GCTL_PRTCAP_HOST:
+               dwc3_drd_to_host(dwc);
+               break;
+       case DWC3_GCTL_PRTCAP_OTG:
+               dwc3_drd_to_otg(dwc);
+               break;
+       default:
+               /* Should never happen */
+               break;
        }
        return count;
 }
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
new file mode 100644
index 0000000..53d9a9eb
--- /dev/null
+++ b/drivers/usb/dwc3/drd.c
@@ -0,0 +1,163 @@
+/**
+ * drd.c - DesignWare USB3 DRD Controller Dual Role Switch Funciton File
+ *
+ * Copyright (C) 2014 Advanced Micro Devices, Inc.
+ *
+ * Author: Huang Rui <ray.hu...@amd.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+
+#include "core.h"
+#include "gadget.h"
+#include "io.h"
+#include "drd.h"
+
+
+int dwc3_drd_to_host(struct dwc3 *dwc)
+{
+       int ret;
+       unsigned long flags = 0;
+
+       if (dwc->has_xhci)
+               dwc3_host_exit(dwc);
+       if (dwc->has_gadget)
+               dwc3_gadget_stop_on_switch(dwc);
+
+       ret = dwc3_core_soft_reset(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "soft reset failed\n");
+               goto err0;
+       }
+
+       spin_lock_irqsave(&dwc->lock, flags);
+       ret = dwc3_event_buffers_setup(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to setup event buffers\n");
+               goto err0;
+       }
+
+       dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+
+       ret = dwc3_host_init(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to init host\n");
+               goto err0;
+       }
+err0:
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       return ret;
+}
+
+int dwc3_drd_to_device(struct dwc3 *dwc)
+{
+       int ret;
+       unsigned long timeout, flags = 0;
+       u32 reg;
+
+       if (dwc->has_xhci)
+               dwc3_host_exit(dwc);
+       if (dwc->has_gadget)
+               dwc3_gadget_stop_on_switch(dwc);
+
+       ret = dwc3_core_soft_reset(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "soft reset failed\n");
+               goto err0;
+       }
+
+       spin_lock_irqsave(&dwc->lock, flags);
+       dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+
+       /* issue device SoftReset too */
+       timeout = jiffies + msecs_to_jiffies(500);
+       dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST);
+       do {
+               reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+               if (!(reg & DWC3_DCTL_CSFTRST))
+                       break;
+
+               if (time_after(jiffies, timeout)) {
+                       dev_err(dwc->dev, "Reset Timed Out\n");
+                       ret = -ETIMEDOUT;
+                       goto err0;
+               }
+
+               cpu_relax();
+       } while (true);
+
+       ret = dwc3_event_buffers_setup(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to setup event buffers\n");
+               goto err0;
+       }
+
+       ret = dwc3_gadget_restart(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to restart gadget\n");
+               goto err0;
+       }
+err0:
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       return ret;
+}
+
+int dwc3_drd_to_otg(struct dwc3 *dwc)
+{
+       int ret = 0;
+       unsigned long flags = 0;
+
+       if (dwc->has_xhci)
+               dwc3_host_exit(dwc);
+       if (dwc->has_gadget)
+               dwc3_gadget_stop_on_switch(dwc);
+
+       ret = dwc3_core_soft_reset(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "soft reset failed\n");
+               goto err0;
+       }
+
+       spin_lock_irqsave(&dwc->lock, flags);
+       ret = dwc3_event_buffers_setup(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to setup event buffers\n");
+               goto err0;
+       }
+
+       dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+
+       ret = dwc3_host_init(dwc);
+       if (ret) {
+               dev_err(dwc->dev, "failed to init host\n");
+               goto err0;
+       }
+
+err0:
+       spin_unlock_irqrestore(&dwc->lock, flags);
+       return ret;
+}
diff --git a/drivers/usb/dwc3/drd.h b/drivers/usb/dwc3/drd.h
new file mode 100644
index 0000000..14d4944
--- /dev/null
+++ b/drivers/usb/dwc3/drd.h
@@ -0,0 +1,30 @@
+/**
+ * drd.c - DesignWare USB3 DRD Controller Dual Role Switch Funciton Header
+ *
+ * Copyright (C) 2014 Advanced Micro Devices, Inc.
+ *
+ * Author: Huang Rui <ray.hu...@amd.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2  of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __DRIVERS_USB_DWC3_DRD_H
+#define __DRIVERS_USB_DWC3_DRD_H
+
+#include "core.h"
+#include "gadget.h"
+#include "io.h"
+
+extern int dwc3_drd_to_host(struct dwc3 *dwc);
+extern int dwc3_drd_to_device(struct dwc3 *dwc);
+extern int dwc3_drd_to_otg(struct dwc3 *dwc);
+
+#endif /* __DRIVERS_USB_DWC3_CORE_H */
+
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 8277065..84252f3 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -1501,37 +1501,13 @@ 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)
+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;
+       struct dwc3_ep          *dep;
 
-       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;
-
+       dwc->has_gadget = true;
        reg = dwc3_readl(dwc->regs, DWC3_DCFG);
        reg &= ~(DWC3_DCFG_SPEED_MASK);
 
@@ -1579,7 +1555,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;
+               goto err0;
        }
 
        dep = dwc->eps[1];
@@ -1587,7 +1563,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 err1;
        }
 
        /* begin to receive SETUP packets */
@@ -1596,16 +1572,54 @@ static int dwc3_gadget_start(struct usb_gadget *g,
 
        dwc3_gadget_enable_irq(dwc);
 
-       spin_unlock_irqrestore(&dwc->lock, flags);
-
        return 0;
 
-err3:
+err1:
        __dwc3_gadget_ep_disable(dwc->eps[0]);
 
-err2:
+err0:
        dwc->gadget_driver = NULL;
 
+       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)
+               goto err1;
+
+       spin_unlock_irqrestore(&dwc->lock, flags);
+
+       return 0;
+
 err1:
        spin_unlock_irqrestore(&dwc->lock, flags);
 
@@ -1615,6 +1629,16 @@ err0:
        return ret;
 }
 
+
+int dwc3_gadget_stop_on_switch(struct dwc3 *dwc)
+{
+       dwc->has_gadget = false;
+       __dwc3_gadget_ep_disable(dwc->eps[0]);
+       __dwc3_gadget_ep_disable(dwc->eps[1]);
+
+       return 0;
+}
+
 static int dwc3_gadget_stop(struct usb_gadget *g,
                struct usb_gadget_driver *driver)
 {
@@ -2669,6 +2693,7 @@ int dwc3_gadget_init(struct dwc3 *dwc)
                goto err3;
        }
 
+       dwc->has_gadget                 = true;
        dwc->gadget.ops                 = &dwc3_gadget_ops;
        dwc->gadget.max_speed           = USB_SPEED_SUPER;
        dwc->gadget.speed               = USB_SPEED_UNKNOWN;
@@ -2729,6 +2754,7 @@ err0:
 
 void dwc3_gadget_exit(struct dwc3 *dwc)
 {
+       dwc->has_gadget = false;
        usb_del_gadget_udc(&dwc->gadget);
 
        dwc3_gadget_free_endpoints(dwc);
diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h
index 178ad89..b3f628a 100644
--- a/drivers/usb/dwc3/gadget.h
+++ b/drivers/usb/dwc3/gadget.h
@@ -103,4 +103,7 @@ static inline u32 dwc3_gadget_ep_get_transfer_index(struct 
dwc3 *dwc, u8 number)
        return DWC3_DEPCMD_GET_RSC_IDX(res_id);
 }
 
+int dwc3_gadget_restart(struct dwc3 *dwc);
+int dwc3_gadget_stop_on_switch(struct dwc3 *dwc);
+
 #endif /* __DRIVERS_USB_DWC3_GADGET_H */
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index dcb8ca0..dc6d5f0 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -40,6 +40,7 @@ int dwc3_host_init(struct dwc3 *dwc)
        xhci->dev.dma_parms     = dwc->dev->dma_parms;
 
        dwc->xhci = xhci;
+       dwc->has_xhci = true;
 
        ret = platform_device_add_resources(xhci, dwc->xhci_resources,
                                                DWC3_XHCI_RESOURCES_NUM);
@@ -77,5 +78,6 @@ err0:
 
 void dwc3_host_exit(struct dwc3 *dwc)
 {
+       dwc->has_xhci = false;
        platform_device_unregister(dwc->xhci);
 }
-- 
1.8.1.2

--
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