usb: dwc2: override PHY input signals with usb role switch support
authorAmelie Delaunay <amelie.delaunay@st.com>
Wed, 9 Sep 2020 09:35:10 +0000 (11:35 +0200)
committerFelipe Balbi <balbi@kernel.org>
Fri, 2 Oct 2020 10:29:13 +0000 (13:29 +0300)
This patch adds support for usb role switch to dwc2, by using overriding
control of the PHY voltage valid and ID input signals.

iddig signal (ID) can be overridden:
- when setting GUSBCFG_FORCEHOSTMODE, iddig input pin is overridden with 1;
- when setting GUSBCFG_FORCEDEVMODE, iddig input pin is overridden with 0.

avalid/bvalid/vbusvalid signals can be overridden respectively with:
- GOTGCTL_AVALOEN + GOTGCTL_AVALOVAL
- GOTGCTL_BVALOEN + GOTGCTL_BVALOVAL
- GOTGCTL_VBVALEN + GOTGCTL_VBVALOVAL

It is possible to determine valid sessions thanks to usb role switch:
- if USB_ROLE_NONE then !avalid && !bvalid && !vbusvalid
- if USB_ROLE_DEVICE then !avalid && bvalid && vbusvalid
- if USB_ROLE_HOST then avalid && !bvalid && vbusvalid

Acked-by: Minas Harutyunyan <hminas@synopsys.com>
Acked-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Signed-off-by: Amelie Delaunay <amelie.delaunay@st.com>
Signed-off-by: Felipe Balbi <balbi@kernel.org>
drivers/usb/dwc2/Kconfig
drivers/usb/dwc2/Makefile
drivers/usb/dwc2/core.h
drivers/usb/dwc2/drd.c [new file with mode: 0644]
drivers/usb/dwc2/gadget.c
drivers/usb/dwc2/platform.c

index 16e1aa3..c131719 100644 (file)
@@ -5,6 +5,7 @@ config USB_DWC2
        depends on HAS_DMA
        depends on USB || USB_GADGET
        depends on HAS_IOMEM
+       select USB_ROLE_SWITCH
        help
          Say Y here if your system has a Dual Role Hi-Speed USB
          controller based on the DesignWare HSOTG IP Core.
index 440320c..2bcd694 100644 (file)
@@ -3,7 +3,7 @@ ccflags-$(CONFIG_USB_DWC2_DEBUG)        += -DDEBUG
 ccflags-$(CONFIG_USB_DWC2_VERBOSE)     += -DVERBOSE_DEBUG
 
 obj-$(CONFIG_USB_DWC2)                 += dwc2.o
-dwc2-y                                 := core.o core_intr.o platform.o
+dwc2-y                                 := core.o core_intr.o platform.o drd.o
 dwc2-y                                 += params.o
 
 ifneq ($(filter y,$(CONFIG_USB_DWC2_HOST) $(CONFIG_USB_DWC2_DUAL_ROLE)),)
index 9deff04..7161344 100644 (file)
@@ -860,6 +860,7 @@ struct dwc2_hregs_backup {
  *                      - USB_DR_MODE_PERIPHERAL
  *                      - USB_DR_MODE_HOST
  *                      - USB_DR_MODE_OTG
+ * @role_sw:           usb_role_switch handle
  * @hcd_enabled:       Host mode sub-driver initialization indicator.
  * @gadget_enabled:    Peripheral mode sub-driver initialization indicator.
  * @ll_hw_enabled:     Status of low-level hardware resources.
@@ -1054,6 +1055,7 @@ struct dwc2_hsotg {
        struct dwc2_core_params params;
        enum usb_otg_state op_state;
        enum usb_dr_mode dr_mode;
+       struct usb_role_switch *role_sw;
        unsigned int hcd_enabled:1;
        unsigned int gadget_enabled:1;
        unsigned int ll_hw_enabled:1;
@@ -1376,6 +1378,11 @@ static inline int dwc2_is_device_mode(struct dwc2_hsotg *hsotg)
        return (dwc2_readl(hsotg, GINTSTS) & GINTSTS_CURMODE_HOST) == 0;
 }
 
+int dwc2_drd_init(struct dwc2_hsotg *hsotg);
+void dwc2_drd_suspend(struct dwc2_hsotg *hsotg);
+void dwc2_drd_resume(struct dwc2_hsotg *hsotg);
+void dwc2_drd_exit(struct dwc2_hsotg *hsotg);
+
 /*
  * Dump core registers and SPRAM
  */
@@ -1392,6 +1399,7 @@ int dwc2_hsotg_resume(struct dwc2_hsotg *dwc2);
 int dwc2_gadget_init(struct dwc2_hsotg *hsotg);
 void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
                                       bool reset);
+void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg);
 void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg);
 void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2);
 int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode);
@@ -1417,6 +1425,7 @@ static inline int dwc2_gadget_init(struct dwc2_hsotg *hsotg)
 { return 0; }
 static inline void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *dwc2,
                                                     bool reset) {}
+static inline void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg) {}
 static inline void dwc2_hsotg_core_connect(struct dwc2_hsotg *hsotg) {}
 static inline void dwc2_hsotg_disconnect(struct dwc2_hsotg *dwc2) {}
 static inline int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg,
diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c
new file mode 100644 (file)
index 0000000..2d4176f
--- /dev/null
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drd.c - DesignWare USB2 DRD Controller Dual-role support
+ *
+ * Copyright (C) 2020 STMicroelectronics
+ *
+ * Author(s): Amelie Delaunay <amelie.delaunay@st.com>
+ */
+
+#include <linux/iopoll.h>
+#include <linux/platform_device.h>
+#include <linux/usb/role.h>
+#include "core.h"
+
+static void dwc2_ovr_init(struct dwc2_hsotg *hsotg)
+{
+       unsigned long flags;
+       u32 gotgctl;
+
+       spin_lock_irqsave(&hsotg->lock, flags);
+
+       gotgctl = dwc2_readl(hsotg, GOTGCTL);
+       gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN;
+       gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
+       gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
+       dwc2_writel(hsotg, gotgctl, GOTGCTL);
+
+       dwc2_force_mode(hsotg, false);
+
+       spin_unlock_irqrestore(&hsotg->lock, flags);
+}
+
+static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid)
+{
+       u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
+
+       /* Check if A-Session is already in the right state */
+       if ((valid && (gotgctl & GOTGCTL_ASESVLD)) ||
+           (!valid && !(gotgctl & GOTGCTL_ASESVLD)))
+               return -EALREADY;
+
+       if (valid)
+               gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL;
+       else
+               gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL);
+       dwc2_writel(hsotg, gotgctl, GOTGCTL);
+
+       return 0;
+}
+
+static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid)
+{
+       u32 gotgctl = dwc2_readl(hsotg, GOTGCTL);
+
+       /* Check if B-Session is already in the right state */
+       if ((valid && (gotgctl & GOTGCTL_BSESVLD)) ||
+           (!valid && !(gotgctl & GOTGCTL_BSESVLD)))
+               return -EALREADY;
+
+       if (valid)
+               gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL;
+       else
+               gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL);
+       dwc2_writel(hsotg, gotgctl, GOTGCTL);
+
+       return 0;
+}
+
+static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role)
+{
+       struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw);
+       unsigned long flags;
+       int already = 0;
+
+       /* Skip session not in line with dr_mode */
+       if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) ||
+           (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL))
+               return -EINVAL;
+
+#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
+       IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
+       /* Skip session if core is in test mode */
+       if (role == USB_ROLE_NONE && hsotg->test_mode) {
+               dev_dbg(hsotg->dev, "Core is in test mode\n");
+               return -EBUSY;
+       }
+#endif
+
+       spin_lock_irqsave(&hsotg->lock, flags);
+
+       if (role == USB_ROLE_HOST) {
+               already = dwc2_ovr_avalid(hsotg, true);
+       } else if (role == USB_ROLE_DEVICE) {
+               already = dwc2_ovr_bvalid(hsotg, true);
+               /* This clear DCTL.SFTDISCON bit */
+               dwc2_hsotg_core_connect(hsotg);
+       } else {
+               if (dwc2_is_device_mode(hsotg)) {
+                       if (!dwc2_ovr_bvalid(hsotg, false))
+                               /* This set DCTL.SFTDISCON bit */
+                               dwc2_hsotg_core_disconnect(hsotg);
+               } else {
+                       dwc2_ovr_avalid(hsotg, false);
+               }
+       }
+
+       spin_unlock_irqrestore(&hsotg->lock, flags);
+
+       if (!already && hsotg->dr_mode == USB_DR_MODE_OTG)
+               /* This will raise a Connector ID Status Change Interrupt */
+               dwc2_force_mode(hsotg, role == USB_ROLE_HOST);
+
+       dev_dbg(hsotg->dev, "%s-session valid\n",
+               role == USB_ROLE_NONE ? "No" :
+               role == USB_ROLE_HOST ? "A" : "B");
+
+       return 0;
+}
+
+int dwc2_drd_init(struct dwc2_hsotg *hsotg)
+{
+       struct usb_role_switch_desc role_sw_desc = {0};
+       struct usb_role_switch *role_sw;
+       int ret;
+
+       if (!device_property_read_bool(hsotg->dev, "usb-role-switch"))
+               return 0;
+
+       role_sw_desc.driver_data = hsotg;
+       role_sw_desc.fwnode = dev_fwnode(hsotg->dev);
+       role_sw_desc.set = dwc2_drd_role_sw_set;
+       role_sw_desc.allow_userspace_control = true;
+
+       role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc);
+       if (IS_ERR(role_sw)) {
+               ret = PTR_ERR(role_sw);
+               dev_err(hsotg->dev,
+                       "failed to register role switch: %d\n", ret);
+               return ret;
+       }
+
+       hsotg->role_sw = role_sw;
+
+       /* Enable override and initialize values */
+       dwc2_ovr_init(hsotg);
+
+       return 0;
+}
+
+void dwc2_drd_suspend(struct dwc2_hsotg *hsotg)
+{
+       u32 gintsts, gintmsk;
+
+       if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
+               gintmsk = dwc2_readl(hsotg, GINTMSK);
+               gintmsk &= ~GINTSTS_CONIDSTSCHNG;
+               dwc2_writel(hsotg, gintmsk, GINTMSK);
+               gintsts = dwc2_readl(hsotg, GINTSTS);
+               dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
+       }
+}
+
+void dwc2_drd_resume(struct dwc2_hsotg *hsotg)
+{
+       u32 gintsts, gintmsk;
+
+       if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) {
+               gintsts = dwc2_readl(hsotg, GINTSTS);
+               dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS);
+               gintmsk = dwc2_readl(hsotg, GINTMSK);
+               gintmsk |= GINTSTS_CONIDSTSCHNG;
+               dwc2_writel(hsotg, gintmsk, GINTMSK);
+       }
+}
+
+void dwc2_drd_exit(struct dwc2_hsotg *hsotg)
+{
+       if (hsotg->role_sw)
+               usb_role_switch_unregister(hsotg->role_sw);
+}
index 5b9d239..16c5f97 100644 (file)
@@ -3530,7 +3530,7 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg *hsotg,
                dwc2_readl(hsotg, DOEPCTL0));
 }
 
-static void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
+void dwc2_hsotg_core_disconnect(struct dwc2_hsotg *hsotg)
 {
        /* set the soft-disconnect bit */
        dwc2_set_bit(hsotg, DCTL, DCTL_SFTDISCON);
index c8844ae..e282067 100644 (file)
@@ -323,6 +323,8 @@ static int dwc2_driver_remove(struct platform_device *dev)
        if (hsotg->gadget_enabled)
                dwc2_hsotg_remove(hsotg);
 
+       dwc2_drd_exit(hsotg);
+
        if (hsotg->params.activate_stm_id_vb_detection)
                regulator_disable(hsotg->usb33d);
 
@@ -542,10 +544,17 @@ static int dwc2_driver_probe(struct platform_device *dev)
                dwc2_writel(hsotg, ggpio, GGPIO);
        }
 
+       retval = dwc2_drd_init(hsotg);
+       if (retval) {
+               if (retval != -EPROBE_DEFER)
+                       dev_err(hsotg->dev, "failed to initialize dual-role\n");
+               goto error_init;
+       }
+
        if (hsotg->dr_mode != USB_DR_MODE_HOST) {
                retval = dwc2_gadget_init(hsotg);
                if (retval)
-                       goto error_init;
+                       goto error_drd;
                hsotg->gadget_enabled = 1;
        }
 
@@ -571,7 +580,7 @@ static int dwc2_driver_probe(struct platform_device *dev)
                if (retval) {
                        if (hsotg->gadget_enabled)
                                dwc2_hsotg_remove(hsotg);
-                       goto error_init;
+                       goto error_drd;
                }
                hsotg->hcd_enabled = 1;
        }
@@ -603,6 +612,9 @@ error_debugfs:
        dwc2_debugfs_exit(hsotg);
        if (hsotg->hcd_enabled)
                dwc2_hcd_remove(hsotg);
+error_drd:
+       dwc2_drd_exit(hsotg);
+
 error_init:
        if (hsotg->params.activate_stm_id_vb_detection)
                regulator_disable(hsotg->usb33d);
@@ -621,6 +633,8 @@ static int __maybe_unused dwc2_suspend(struct device *dev)
        if (is_device_mode)
                dwc2_hsotg_suspend(dwc2);
 
+       dwc2_drd_suspend(dwc2);
+
        if (dwc2->params.activate_stm_id_vb_detection) {
                unsigned long flags;
                u32 ggpio, gotgctl;
@@ -701,6 +715,8 @@ static int __maybe_unused dwc2_resume(struct device *dev)
        /* Need to restore FORCEDEVMODE/FORCEHOSTMODE */
        dwc2_force_dr_mode(dwc2);
 
+       dwc2_drd_resume(dwc2);
+
        if (dwc2_is_device_mode(dwc2))
                ret = dwc2_hsotg_resume(dwc2);