[PATCH 04/15] usb: dwc3: dwc3-rtk: support the dual role dynamic switch

Stanley Chang stanley_chang at realtek.com
Wed Oct 7 04:50:24 EDT 2020


Realtek DHC SoC embeds a dual role DWC3 USB core IP. The function will support
dynamic switch host or device mode.

Signed-off-by: Stanley Chang <stanley_chang at realtek.com>
---
 drivers/usb/dwc3/Kconfig        |   8 ++
 drivers/usb/dwc3/Makefile       |   1 +
 drivers/usb/dwc3/dwc3-rtk-drd.c | 207 ++++++++++++++++++++++++++++++++
 drivers/usb/dwc3/dwc3-rtk-drd.h |  26 ++++
 drivers/usb/dwc3/dwc3-rtk.c     | 168 ++++++++++++++++++++++++++
 drivers/usb/dwc3/dwc3-rtk.h     |   1 +
 6 files changed, 411 insertions(+)
 create mode 100644 drivers/usb/dwc3/dwc3-rtk-drd.c
 create mode 100644 drivers/usb/dwc3/dwc3-rtk-drd.h

diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 5353d05d9f6c..a04d280c9835 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -61,6 +61,14 @@ config USB_DWC3_RTK
 	  RTK SoCs with DesignWare Core USB3 IP inside,
 	  say 'Y' or 'M' if you have such device.
 
+config USB_DWC3_RTK_DUAL_ROLE
+	tristate "Realtek DWC3 Platform Dual Role Driver"
+	default USB_DWC3_RTK if (USB_DWC3_DUAL_ROLE)
+	depends on USB_DWC3_DUAL_ROLE
+	help
+	  RTK SoCs with DesignWare Core USB3 IP to suport drd mode,
+	  Support Realtek dwc3 drd mode to dynamical host/device switch.
+	  say 'Y' or 'M' if you have such device.
 
 config USB_DWC3_OMAP
 	tristate "Texas Instruments OMAP5 and similar Platforms"
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index d57cb12d000a..25f8823e8811 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -52,3 +52,4 @@ obj-$(CONFIG_USB_DWC3_OF_SIMPLE)	+= dwc3-of-simple.o
 obj-$(CONFIG_USB_DWC3_ST)		+= dwc3-st.o
 obj-$(CONFIG_USB_DWC3_QCOM)		+= dwc3-qcom.o
 obj-$(CONFIG_USB_DWC3_RTK)		+= dwc3-rtk.o dwc3-rtk-debugfs.o
+obj-$(CONFIG_USB_DWC3_RTK_DUAL_ROLE)	+= dwc3-rtk-drd.o
diff --git a/drivers/usb/dwc3/dwc3-rtk-drd.c b/drivers/usb/dwc3/dwc3-rtk-drd.c
new file mode 100644
index 000000000000..9e18bfadb96a
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-rtk-drd.c
@@ -0,0 +1,207 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * dwc3-rtk-drd.c - Realtek DWC3 Specific Glue layer
+ *
+ * Copyright (C) 2017 Realtek Semiconductor Corporation
+ *
+ */
+
+#include "core.h"
+#include "gadget.h"
+#include "io.h"
+
+static int dwc3_check_drd_mode(struct dwc3 *dwc)
+{
+	int mode = USB_DR_MODE_UNKNOWN;
+
+	if (dwc->xhci) {
+		mode = USB_DR_MODE_HOST;
+		dev_dbg(dwc->dev, "%s Now is host\n", __func__);
+	} else if (dwc->gadget.udc) {
+		mode = USB_DR_MODE_PERIPHERAL;
+		dev_dbg(dwc->dev, "%s Now is gadget\n", __func__);
+	}
+
+	return mode;
+}
+
+static int rtk_dwc3_drd_core_soft_reset(struct dwc3 *dwc)
+{
+	int ret;
+	u32 reg;
+
+	reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+	dwc3_writel(dwc->regs, DWC3_GCTL, reg | DWC3_GCTL_DSBLCLKGTNG);
+
+	ret = dwc3_core_soft_reset(dwc);
+
+	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+
+	return ret;
+}
+
+static int rtk_dwc3_drd_event_buffers_setup(struct dwc3 *dwc)
+{
+	struct dwc3_event_buffer	*evt;
+
+	evt = dwc->ev_buf;
+	evt->lpos = 0;
+	dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0),
+			lower_32_bits(evt->dma));
+	dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(0),
+			upper_32_bits(evt->dma));
+	dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0),
+			DWC3_GEVNTSIZ_SIZE(evt->length));
+	dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), 0);
+
+	return 0;
+}
+
+static void rtk_dwc3_set_mode(struct dwc3 *dwc, u32 mode)
+{
+	u32 reg;
+
+	reg = dwc3_readl(dwc->regs, DWC3_GCTL);
+	reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
+	reg |= DWC3_GCTL_PRTCAPDIR(mode);
+	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
+}
+
+int dwc3_drd_to_host(struct dwc3 *dwc)
+{
+	int ret;
+	unsigned long timeout;
+	u32 reg;
+
+	dev_info(dwc->dev, "%s START....", __func__);
+	if (dwc3_check_drd_mode(dwc) == USB_DR_MODE_PERIPHERAL) {
+		dwc3_gadget_exit(dwc);
+	}
+	/* Do wmb */
+	wmb();
+
+	if (dwc3_check_drd_mode(dwc) == USB_DR_MODE_HOST) {
+		dev_info(dwc->dev, "%s Now is host", __func__);
+		return 0;
+	}
+
+	/* 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 = rtk_dwc3_drd_core_soft_reset(dwc);
+	if (ret) {
+		dev_err(dwc->dev, "soft reset failed\n");
+		goto err0;
+	}
+
+	ret = rtk_dwc3_drd_event_buffers_setup(dwc);
+	if (ret) {
+		dev_err(dwc->dev, "failed to setup event buffers\n");
+		goto err0;
+	}
+
+	rtk_dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
+
+	ret = dwc3_host_init(dwc);
+	if (ret)
+		dev_err(dwc->dev, "failed to init host\n");
+
+err0:
+	dev_info(dwc->dev, "%s END....", __func__);
+	return ret;
+}
+
+int dwc3_drd_to_device(struct dwc3 *dwc)
+{
+	int ret;
+	unsigned long timeout, flags = 0;
+	u32 reg;
+
+	dev_info(dwc->dev, "%s START....", __func__);
+
+	if (dwc3_check_drd_mode(dwc) == USB_DR_MODE_HOST) {
+		dev_info(dwc->dev, "%s dwc3_host_exit", __func__);
+		dwc3_host_exit(dwc);
+	}
+	/* Do wmb */
+	wmb();
+
+	if (dwc3_check_drd_mode(dwc) == USB_DR_MODE_PERIPHERAL) {
+		dev_info(dwc->dev, "%s Now is gadget", __func__);
+		return 0;
+	}
+
+	/* 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 = rtk_dwc3_drd_core_soft_reset(dwc);
+	if (ret) {
+		dev_err(dwc->dev, "soft reset failed\n");
+		goto err0;
+	}
+
+	spin_lock_irqsave(&dwc->lock, flags);
+
+	ret = rtk_dwc3_drd_event_buffers_setup(dwc);
+	if (ret) {
+		dev_err(dwc->dev, "failed to setup event buffers\n");
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		goto err0;
+	}
+
+	rtk_dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+
+	spin_unlock_irqrestore(&dwc->lock, flags);
+
+	ret = dwc3_gadget_init(dwc);
+	if (ret)
+		dev_err(dwc->dev, "failed to init gadget\n");
+
+err0:
+	dev_info(dwc->dev, "%s END....", __func__);
+	return ret;
+}
+
+int dwc3_drd_to_stop_all(struct dwc3 *dwc)
+{
+	int ret = 0;
+
+	dev_info(dwc->dev, "%s START....", __func__);
+	if (dwc3_check_drd_mode(dwc) == USB_DR_MODE_HOST)
+		dwc3_host_exit(dwc);
+	if (dwc3_check_drd_mode(dwc) == USB_DR_MODE_PERIPHERAL)
+		dwc3_gadget_exit(dwc);
+
+	/* Do wmb */
+	wmb();
+	dev_info(dwc->dev, "%s END....", __func__);
+	return ret;
+}
diff --git a/drivers/usb/dwc3/dwc3-rtk-drd.h b/drivers/usb/dwc3/dwc3-rtk-drd.h
new file mode 100644
index 000000000000..997d5c3fc301
--- /dev/null
+++ b/drivers/usb/dwc3/dwc3-rtk-drd.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/**
+ * dwc3-rtk-drd.h - Realtek DWC3 Specific Glue layer
+ *
+ * Copyright (C) 2017 Realtek Semiconductor Corporation
+ *
+ */
+
+#ifndef __DRIVERS_USB_DWC3_RTK_DRD_H
+#define __DRIVERS_USB_DWC3_RTK_DRD_H
+
+struct dwc3;
+
+int dwc3_drd_to_host(struct dwc3 *dwc);
+int dwc3_drd_to_device(struct dwc3 *dwc);
+int dwc3_drd_to_stop_all(struct dwc3 *dwc);
+
+struct dwc3_rtk;
+
+int dwc3_rtk_get_dr_mode(struct dwc3_rtk *rtk);
+int dwc3_rtk_set_dr_mode(struct dwc3_rtk *rtk, int dr_mode);
+bool dwc3_rtk_is_connected_on_device_mode(struct dwc3_rtk *dwc3_rtk);
+bool dwc3_rtk_is_support_drd_mode(struct dwc3_rtk *dwc3_rtk);
+
+#endif /* __DRIVERS_USB_DWC3_RTK_CORE_H */
+
diff --git a/drivers/usb/dwc3/dwc3-rtk.c b/drivers/usb/dwc3/dwc3-rtk.c
index 3291ee401a64..3fc31cfe6f57 100644
--- a/drivers/usb/dwc3/dwc3-rtk.c
+++ b/drivers/usb/dwc3/dwc3-rtk.c
@@ -19,6 +19,7 @@
 #include <linux/sys_soc.h>
 
 #include "dwc3-rtk.h"
+#include "dwc3-rtk-drd.h"
 #include "core.h"
 #include "io.h"
 
@@ -41,6 +42,48 @@ static const struct soc_device_attribute rtk_soc_hercules[] = {
 	}
 };
 
+bool dwc3_rtk_is_support_drd_mode(struct dwc3_rtk *dwc3_rtk)
+{
+	if (!dwc3_rtk) {
+		pr_err("%s: ERROR: dwc3_rtk is NULL!", __func__);
+		return false;
+	}
+
+	return dwc3_rtk->support_drd_mode;
+}
+
+bool dwc3_rtk_is_connected_on_device_mode(struct dwc3_rtk *dwc3_rtk)
+{
+	bool connected = true;
+	int no_host_connect = 0;
+	int no_run_gadget = 0;
+	u32 dsts, dctl;
+
+	if (!dwc3_rtk) {
+		pr_err("%s: ERROR: dwc3_rtk is NULL!", __func__);
+		return connected;
+	}
+	if (dwc3_rtk->cur_dr_mode != USB_DR_MODE_PERIPHERAL) {
+		dev_info(dwc3_rtk->dev,
+			    "%s: Error: not in device mode (cur_dr_mode=%x)\n",
+			    __func__, dwc3_rtk->cur_dr_mode);
+		return connected;
+	}
+
+	dsts = dwc3_readl(dwc3_rtk->dwc->regs, DWC3_DSTS);
+	dctl = dwc3_readl(dwc3_rtk->dwc->regs, DWC3_DCTL);
+
+	dev_info(dwc3_rtk->dev, "%s: Device mode check DSTS=%x DCTL=%x\n",
+		    __func__,
+		    dsts, dctl);
+	no_host_connect = DWC3_DSTS_USBLNKST(dsts) >= DWC3_LINK_STATE_SS_DIS;
+	no_run_gadget = (dctl & BIT(31)) == 0x0;
+	if (no_host_connect || no_run_gadget)
+		connected = false;
+
+	return connected;
+}
+
 static void switch_u2_dr_mode(struct dwc3_rtk *rtk, int dr_mode)
 {
 	switch (dr_mode) {
@@ -63,6 +106,121 @@ static void switch_u2_dr_mode(struct dwc3_rtk *rtk, int dr_mode)
 	}
 }
 
+static void switch_dwc3_dr_mode(struct dwc3_rtk *rtk, int dr_mode)
+{
+#ifdef CONFIG_USB_DWC3_RTK_DUAL_ROLE
+	switch (dr_mode) {
+	case USB_DR_MODE_PERIPHERAL:
+		dev_info(rtk->dev, "%s dr_mode=USB_DR_MODE_PERIPHERAL\n",
+			    __func__);
+		dwc3_drd_to_device(rtk->dwc);
+		break;
+	case USB_DR_MODE_HOST:
+		dev_info(rtk->dev, "%s dr_mode=USB_DR_MODE_HOST\n",
+			    __func__);
+		dwc3_drd_to_host(rtk->dwc);
+		break;
+	default:
+		dev_info(rtk->dev, "%s dr_mode=%d\n", __func__, dr_mode);
+		dwc3_drd_to_stop_all(rtk->dwc);
+	}
+#else
+	dev_info(rtk->dev, "Not support CONFIG_USB_DWC3_RTK_DUAL_ROLE\n");
+#endif /* CONFIG_USB_DWC3_RTK_DUAL_ROLE */
+}
+
+int dwc3_rtk_get_dr_mode(struct dwc3_rtk *rtk)
+{
+	return rtk->cur_dr_mode;
+}
+
+int dwc3_rtk_set_dr_mode(struct dwc3_rtk *rtk, int dr_mode)
+{
+	if (!rtk->support_drd_mode)
+		return rtk->cur_dr_mode;
+
+	if (!rtk->dwc) {
+		dev_err(rtk->dev, "%s Error! dwc3 is NULL", __func__);
+		return rtk->cur_dr_mode;
+	}
+
+	dev_dbg(rtk->dev, "%s START....", __func__);
+
+	rtk->cur_dr_mode = dr_mode;
+	rtk->dwc->dr_mode = dr_mode;
+
+	switch_dwc3_dr_mode(rtk, dr_mode);
+	mdelay(10);
+	switch_u2_dr_mode(rtk, dr_mode);
+
+	dev_dbg(rtk->dev, "%s END....", __func__);
+
+	return rtk->cur_dr_mode;
+}
+
+static ssize_t set_dr_mode_show(struct device *dev,
+			      struct device_attribute *attr,
+			      char *buf)
+{
+	struct dwc3_rtk *rtk = dev_get_drvdata(dev);
+	char *ptr = buf;
+	int count = PAGE_SIZE;
+	int n;
+
+	n = scnprintf(ptr, count,
+		     "Now cur_dr_mode is %s (default dwc3 dr_mode is %s)\n",
+		    ({ char *tmp;
+		switch (rtk->cur_dr_mode) {
+		case USB_DR_MODE_PERIPHERAL:
+		    tmp = "USB_DR_MODE_PERIPHERAL"; break;
+		case USB_DR_MODE_HOST:
+		    tmp = "USB_DR_MODE_HOST"; break;
+		default:
+		    tmp = "USB_DR_MODE_UNKNOWN"; break;
+		    } tmp; }),
+		    ({ char *tmp;
+		switch (rtk->default_dwc3_dr_mode) {
+		case USB_DR_MODE_PERIPHERAL:
+		    tmp = "USB_DR_MODE_PERIPHERAL"; break;
+		case USB_DR_MODE_HOST:
+		    tmp = "USB_DR_MODE_HOST"; break;
+		default:
+		    tmp = "USB_DR_MODE_UNKNOWN"; break;
+		    } tmp; }));
+
+	ptr += n;
+	count -= n;
+
+	n = scnprintf(ptr, count,
+		     "write host -> switch to Host mode\n");
+	ptr += n;
+	count -= n;
+
+	n = scnprintf(ptr, count,
+		     "write device -> switch to Device mode\n");
+	ptr += n;
+	count -= n;
+
+	return ptr - buf;
+}
+
+static ssize_t set_dr_mode_store(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	struct dwc3_rtk *rtk = dev_get_drvdata(dev);
+
+	if (!strncmp(buf, "host", 4))
+		dwc3_rtk_set_dr_mode(rtk, USB_DR_MODE_HOST);
+	else if (!strncmp(buf, "device", 6))
+		dwc3_rtk_set_dr_mode(rtk, USB_DR_MODE_PERIPHERAL);
+	else
+		dwc3_rtk_set_dr_mode(rtk, 0);
+
+	return count;
+}
+static DEVICE_ATTR_RW(set_dr_mode);
+
 static int dwc3_rtk_init(struct dwc3_rtk *rtk)
 {
 	struct device		*dev = rtk->dev;
@@ -122,6 +280,8 @@ static int dwc3_rtk_probe_dwc3core(struct dwc3_rtk *rtk)
 	} else {
 		dev_err(dev, "dwc3_rtk node is NULL\n");
 	}
+	if (rtk->support_drd_mode)
+		device_create_file(dev, &dev_attr_set_dr_mode);
 
 	return ret;
 }
@@ -207,6 +367,11 @@ static int dwc3_rtk_probe(struct platform_device *pdev)
 				    __func__, readl(usb_hmac_ctr0));
 		}
 
+		rtk->support_drd_mode = false;
+		if (of_property_read_bool(node, "drd_mode")) {
+			dev_info(rtk->dev, "%s: support drd_mode\n", __func__);
+			rtk->support_drd_mode = true;
+		}
 	}
 
 	if (node) {
@@ -240,6 +405,9 @@ static int dwc3_rtk_remove(struct platform_device *pdev)
 {
 	struct dwc3_rtk	*rtk = platform_get_drvdata(pdev);
 
+	if (rtk->support_drd_mode)
+		device_remove_file(rtk->dev, &dev_attr_set_dr_mode);
+
 	dwc3_rtk_debugfs_exit(rtk);
 
 	of_platform_depopulate(rtk->dev);
diff --git a/drivers/usb/dwc3/dwc3-rtk.h b/drivers/usb/dwc3/dwc3-rtk.h
index 9b4e7683def7..eeefcecd6f22 100644
--- a/drivers/usb/dwc3/dwc3-rtk.h
+++ b/drivers/usb/dwc3/dwc3-rtk.h
@@ -34,6 +34,7 @@ struct dwc3_rtk {
 
 	int default_dwc3_dr_mode; /* define by dwc3 driver, and it is fixed */
 	int cur_dr_mode; /* current dr mode */
+	bool support_drd_mode; /* if support Host/device switch */
 
 	/* For debugfs */
 	struct dentry		*debug_dir;
-- 
2.28.0




More information about the linux-realtek-soc mailing list