[RFC PATCH 4/4] usb: dwc3: introduce dual role switch function with debugfs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.huang@xxxxxxx>
---
 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.huang@xxxxxxx>
+ *
+ * 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.huang@xxxxxxx>
+ *
+ * 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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux