[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