usb: dwc3: core: Prevent phy suspend during init
authorThinh Nguyen <Thinh.Nguyen@synopsys.com>
Wed, 17 Apr 2024 23:14:36 +0000 (23:14 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 17 May 2024 09:51:03 +0000 (11:51 +0200)
commit 6d735722063a945de56472bdc6bfcb170fd43b86 upstream.

GUSB3PIPECTL.SUSPENDENABLE and GUSB2PHYCFG.SUSPHY should be cleared
during initialization. Suspend during initialization can result in
undefined behavior due to clock synchronization failure, which often
seen as core soft reset timeout.

The programming guide recommended these bits to be cleared during
initialization for DWC_usb3.0 version 1.94 and above (along with
DWC_usb31 and DWC_usb32). The current check in the driver does not
account if it's set by default setting from coreConsultant.

This is especially the case for DRD when switching mode to ensure the
phy clocks are available to change mode. Depending on the
platforms/design, some may be affected more than others. This is noted
in the DWC_usb3x programming guide under the above registers.

Let's just disable them during driver load and mode switching. Restore
them when the controller initialization completes.

Note that some platforms workaround this issue by disabling phy suspend
through "snps,dis_u3_susphy_quirk" and "snps,dis_u2_susphy_quirk" when
they should not need to.

Cc: stable@vger.kernel.org
Fixes: 9ba3aca8fe82 ("usb: dwc3: Disable phy suspend after power-on reset")
Signed-off-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com>
Link: https://lore.kernel.org/r/20da4e5a0c4678c9587d3da23f83bdd6d77353e9.1713394973.git.Thinh.Nguyen@synopsys.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c
drivers/usb/dwc3/host.c

index 0f0269d28c371a37f9c118eb916b6323c5797ec1..a469d052478949cc0209431b1a91262d38f5d66b 100644 (file)
@@ -102,6 +102,27 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc)
        return 0;
 }
 
+void dwc3_enable_susphy(struct dwc3 *dwc, bool enable)
+{
+       u32 reg;
+
+       reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+       if (enable && !dwc->dis_u3_susphy_quirk)
+               reg |= DWC3_GUSB3PIPECTL_SUSPHY;
+       else
+               reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
+
+       dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+
+       reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+       if (enable && !dwc->dis_u2_susphy_quirk)
+               reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+       else
+               reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
+
+       dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+}
+
 void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
 {
        u32 reg;
@@ -593,11 +614,8 @@ static int dwc3_core_ulpi_init(struct dwc3 *dwc)
  */
 static int dwc3_phy_setup(struct dwc3 *dwc)
 {
-       unsigned int hw_mode;
        u32 reg;
 
-       hw_mode = DWC3_GHWPARAMS0_MODE(dwc->hwparams.hwparams0);
-
        reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
 
        /*
@@ -607,21 +625,16 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
        reg &= ~DWC3_GUSB3PIPECTL_UX_EXIT_PX;
 
        /*
-        * Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY
-        * to '0' during coreConsultant configuration. So default value
-        * will be '0' when the core is reset. Application needs to set it
-        * to '1' after the core initialization is completed.
+        * Above DWC_usb3.0 1.94a, it is recommended to set
+        * DWC3_GUSB3PIPECTL_SUSPHY to '0' during coreConsultant configuration.
+        * So default value will be '0' when the core is reset. Application
+        * needs to set it to '1' after the core initialization is completed.
+        *
+        * Similarly for DRD controllers, GUSB3PIPECTL.SUSPENDENABLE must be
+        * cleared after power-on reset, and it can be set after core
+        * initialization.
         */
-       if (!DWC3_VER_IS_WITHIN(DWC3, ANY, 194A))
-               reg |= DWC3_GUSB3PIPECTL_SUSPHY;
-
-       /*
-        * For DRD controllers, GUSB3PIPECTL.SUSPENDENABLE must be cleared after
-        * power-on reset, and it can be set after core initialization, which is
-        * after device soft-reset during initialization.
-        */
-       if (hw_mode == DWC3_GHWPARAMS0_MODE_DRD)
-               reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
+       reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
 
        if (dwc->u2ss_inp3_quirk)
                reg |= DWC3_GUSB3PIPECTL_U2SSINP3OK;
@@ -647,9 +660,6 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
        if (dwc->tx_de_emphasis_quirk)
                reg |= DWC3_GUSB3PIPECTL_TX_DEEPH(dwc->tx_de_emphasis);
 
-       if (dwc->dis_u3_susphy_quirk)
-               reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
-
        if (dwc->dis_del_phy_power_chg_quirk)
                reg &= ~DWC3_GUSB3PIPECTL_DEPOCHANGE;
 
@@ -697,24 +707,15 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
        }
 
        /*
-        * Above 1.94a, it is recommended to set DWC3_GUSB2PHYCFG_SUSPHY to
-        * '0' during coreConsultant configuration. So default value will
-        * be '0' when the core is reset. Application needs to set it to
-        * '1' after the core initialization is completed.
+        * Above DWC_usb3.0 1.94a, it is recommended to set
+        * DWC3_GUSB2PHYCFG_SUSPHY to '0' during coreConsultant configuration.
+        * So default value will be '0' when the core is reset. Application
+        * needs to set it to '1' after the core initialization is completed.
+        *
+        * Similarly for DRD controllers, GUSB2PHYCFG.SUSPHY must be cleared
+        * after power-on reset, and it can be set after core initialization.
         */
-       if (!DWC3_VER_IS_WITHIN(DWC3, ANY, 194A))
-               reg |= DWC3_GUSB2PHYCFG_SUSPHY;
-
-       /*
-        * For DRD controllers, GUSB2PHYCFG.SUSPHY must be cleared after
-        * power-on reset, and it can be set after core initialization, which is
-        * after device soft-reset during initialization.
-        */
-       if (hw_mode == DWC3_GHWPARAMS0_MODE_DRD)
-               reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
-
-       if (dwc->dis_u2_susphy_quirk)
-               reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
+       reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
 
        if (dwc->dis_enblslpm_quirk)
                reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM;
@@ -996,21 +997,6 @@ static int dwc3_core_init(struct dwc3 *dwc)
        if (ret)
                goto err1;
 
-       if (hw_mode == DWC3_GHWPARAMS0_MODE_DRD &&
-           !DWC3_VER_IS_WITHIN(DWC3, ANY, 194A)) {
-               if (!dwc->dis_u3_susphy_quirk) {
-                       reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
-                       reg |= DWC3_GUSB3PIPECTL_SUSPHY;
-                       dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
-               }
-
-               if (!dwc->dis_u2_susphy_quirk) {
-                       reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
-                       reg |= DWC3_GUSB2PHYCFG_SUSPHY;
-                       dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
-               }
-       }
-
        dwc3_core_setup_global_control(dwc);
        dwc3_core_num_eps(dwc);
 
index d64f7edc70c1372b2672eb3204671850e6d0a0fb..8c8e17cc13440730e0bfe7c6f15aa8fabd878edb 100644 (file)
@@ -1517,6 +1517,7 @@ int dwc3_event_buffers_setup(struct dwc3 *dwc);
 void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
 
 int dwc3_core_soft_reset(struct dwc3 *dwc);
+void dwc3_enable_susphy(struct dwc3 *dwc, bool enable);
 
 #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
 int dwc3_host_init(struct dwc3 *dwc);
index 86cf3b2b66e9037d16554a9a8a30f7f9153a30b8..af35278a5e8f6c9439973c9c6807658cc4f11492 100644 (file)
@@ -2775,6 +2775,7 @@ static int __dwc3_gadget_start(struct dwc3 *dwc)
        dwc3_ep0_out_start(dwc);
 
        dwc3_gadget_enable_irq(dwc);
+       dwc3_enable_susphy(dwc, true);
 
        return 0;
 
@@ -4512,6 +4513,7 @@ void dwc3_gadget_exit(struct dwc3 *dwc)
        if (!dwc->gadget)
                return;
 
+       dwc3_enable_susphy(dwc, false);
        usb_del_gadget(dwc->gadget);
        dwc3_gadget_free_endpoints(dwc);
        usb_put_gadget(dwc->gadget);
index 012b54cb847feef4972bb7ee5416a0f7aff58b26..9adcf3a7e9782d7b7b84328f027e7fd33fcc3f68 100644 (file)
@@ -9,9 +9,30 @@
 
 #include <linux/acpi.h>
 #include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
 
+#include "../host/xhci-plat.h"
 #include "core.h"
 
+static void dwc3_xhci_plat_start(struct usb_hcd *hcd)
+{
+       struct platform_device *pdev;
+       struct dwc3 *dwc;
+
+       if (!usb_hcd_is_primary_hcd(hcd))
+               return;
+
+       pdev = to_platform_device(hcd->self.controller);
+       dwc = dev_get_drvdata(pdev->dev.parent);
+
+       dwc3_enable_susphy(dwc, true);
+}
+
+static const struct xhci_plat_priv dwc3_xhci_plat_quirk = {
+       .plat_start = dwc3_xhci_plat_start,
+};
+
 static int dwc3_host_get_irq(struct dwc3 *dwc)
 {
        struct platform_device  *dwc3_pdev = to_platform_device(dwc->dev);
@@ -117,6 +138,11 @@ int dwc3_host_init(struct dwc3 *dwc)
                }
        }
 
+       ret = platform_device_add_data(xhci, &dwc3_xhci_plat_quirk,
+                                      sizeof(struct xhci_plat_priv));
+       if (ret)
+               goto err;
+
        ret = platform_device_add(xhci);
        if (ret) {
                dev_err(dwc->dev, "failed to register xHCI device\n");
@@ -131,6 +157,7 @@ err:
 
 void dwc3_host_exit(struct dwc3 *dwc)
 {
+       dwc3_enable_susphy(dwc, false);
        platform_device_unregister(dwc->xhci);
        dwc->xhci = NULL;
 }