[PATCH 5/5] usb: dwc2: platform: fix stm32mp15 detection logic power management
From: Fabrice Gasnier
Date: Thu Oct 08 2026 - 09:36:09 EST
On stm32mp15, the stm detection logic can be left enabled even though the
other hw resources gets disabled from the probe (peripheral mode only):
/* Gadget code manages lowlevel hw on its own */
if (hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)
dwc2_lowlevel_hw_disable(hsotg);
Then when going to system low power, stm detection logic resources now
remains enabled since dwc2_suspend() checks:
if (!dwc2->ll_hw_enabled)
return 0;
In case the stm32mp15 goes standby, the registers contents gets lost
and may not be restored so dwc2 becomes unusable. This is the case when
no gadget driver has been bound until that point.
So move the stm detection logic enable/disable procedures to dedicated
dwc2_stm32_hw_[enable/disable] routines. Call these routines
from the __dwc2_lowlevel_hw_[enable/disable] routines.
Factorize some of the probing code. Replace regulator_enable() and
GGPIO register poking by direct call to dwc2_stm32_hw_enable().
The dwc2_lowlevel_hw_enable() call done earlier during probing isn't
able to do it: the activate_stm_id_vb_detection is only known after
the dwc2_init_params() has run (and probe the usb33d-supply).
Fixes: 2b94b054ac49 ("usb: dwc2: fix hang during suspend if set as peripheral")
Fixes: a415083a11cc ("usb: dwc2: add support for STM32MP15 SoCs USB OTG HS and FS")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Fabrice Gasnier <fabrice.gasnier@xxxxxxxxxxx>
---
drivers/usb/dwc2/platform.c | 170 +++++++++++++++++++++++++-------------------
1 file changed, 95 insertions(+), 75 deletions(-)
diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c
index 643d67ce28ea..ddab95b443a9 100644
--- a/drivers/usb/dwc2/platform.c
+++ b/drivers/usb/dwc2/platform.c
@@ -91,6 +91,80 @@ static int dwc2_get_dr_mode(struct dwc2_hsotg *hsotg)
return 0;
}
+static int dwc2_stm32_hw_enable(struct dwc2_hsotg *hsotg)
+{
+ unsigned long flags;
+ u32 ggpio, gotgctl;
+ int ret;
+
+ /* params are only known, once dwc2_init_params() has been probed */
+ if (!hsotg->params.activate_stm_id_vb_detection)
+ return 0;
+
+ ret = regulator_enable(hsotg->usb33d);
+ if (ret)
+ return ret;
+
+ ggpio = dwc2_readl(hsotg, GGPIO);
+ ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
+ ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
+ dwc2_writel(hsotg, ggpio, GGPIO);
+
+ /* ID/VBUS detection startup time */
+ usleep_range(5000, 7000);
+
+ spin_lock_irqsave(&hsotg->lock, flags);
+ /* Remove overrides when resuming from the disable */
+ gotgctl = dwc2_readl(hsotg, GOTGCTL);
+ gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS;
+ gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN |
+ GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL);
+ dwc2_writel(hsotg, gotgctl, GOTGCTL);
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+
+ return 0;
+}
+
+static int dwc2_stm32_hw_disable(struct dwc2_hsotg *hsotg)
+{
+ unsigned long flags;
+ u32 ggpio, gotgctl;
+ int ret;
+
+ if (!hsotg->params.activate_stm_id_vb_detection)
+ return 0;
+
+ /*
+ * Need to force the mode to the current mode to avoid Mode
+ * Mismatch Interrupt when ID detection will be disabled.
+ */
+ dwc2_force_mode(hsotg, !dwc2_is_device_mode(hsotg));
+
+ spin_lock_irqsave(&hsotg->lock, flags);
+ gotgctl = dwc2_readl(hsotg, GOTGCTL);
+ /* bypass debounce filter, enable overrides */
+ gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
+ gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN;
+ /* Force A / B session if needed */
+ if (gotgctl & GOTGCTL_ASESVLD)
+ gotgctl |= GOTGCTL_AVALOVAL;
+ if (gotgctl & GOTGCTL_BSESVLD)
+ gotgctl |= GOTGCTL_BVALOVAL;
+ dwc2_writel(hsotg, gotgctl, GOTGCTL);
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+
+ ggpio = dwc2_readl(hsotg, GGPIO);
+ ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN;
+ ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN;
+ dwc2_writel(hsotg, ggpio, GGPIO);
+
+ ret = regulator_disable(hsotg->usb33d);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
{
struct platform_device *pdev = to_platform_device(hsotg->dev);
@@ -129,8 +203,22 @@ static int __dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg)
if (ret)
goto err_dis_clk;
+ ret = dwc2_stm32_hw_enable(hsotg);
+ if (ret)
+ goto err_dis_phy;
+
return 0;
+err_dis_phy:
+ if (hsotg->uphy) {
+ usb_phy_shutdown(hsotg->uphy);
+ } else if (hsotg->plat && hsotg->plat->phy_exit) {
+ hsotg->plat->phy_exit(pdev, hsotg->plat->phy_type);
+ } else {
+ phy_power_off(hsotg->phy);
+ phy_exit(hsotg->phy);
+ }
+
err_dis_clk:
if (hsotg->clk)
clk_disable_unprepare(hsotg->clk);
@@ -166,6 +254,10 @@ static int __dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg)
struct platform_device *pdev = to_platform_device(hsotg->dev);
int ret = 0;
+ ret = dwc2_stm32_hw_disable(hsotg);
+ if (ret)
+ return ret;
+
if (hsotg->uphy) {
usb_phy_shutdown(hsotg->uphy);
} else if (hsotg->plat && hsotg->plat->phy_exit) {
@@ -346,9 +438,6 @@ static void dwc2_driver_remove(struct platform_device *dev)
dwc2_drd_exit(hsotg);
- if (hsotg->params.activate_stm_id_vb_detection)
- regulator_disable(hsotg->usb33d);
-
if (hsotg->ll_hw_enabled)
dwc2_lowlevel_hw_disable(hsotg);
}
@@ -541,33 +630,23 @@ static int dwc2_driver_probe(struct platform_device *dev)
goto error;
if (hsotg->params.activate_stm_id_vb_detection) {
- u32 ggpio;
-
hsotg->usb33d = devm_regulator_get(hsotg->dev, "usb33d");
if (IS_ERR(hsotg->usb33d)) {
retval = PTR_ERR(hsotg->usb33d);
dev_err_probe(hsotg->dev, retval, "failed to request usb33d supply\n");
goto error;
}
- retval = regulator_enable(hsotg->usb33d);
+ retval = dwc2_stm32_hw_enable(hsotg);
if (retval) {
- dev_err_probe(hsotg->dev, retval, "failed to enable usb33d supply\n");
+ dev_err_probe(hsotg->dev, retval, "dwc2_stm32_hw_enable failed\n");
goto error;
}
-
- ggpio = dwc2_readl(hsotg, GGPIO);
- ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
- ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
- dwc2_writel(hsotg, ggpio, GGPIO);
-
- /* ID/VBUS detection startup time */
- usleep_range(5000, 7000);
}
retval = dwc2_drd_init(hsotg);
if (retval) {
dev_err_probe(hsotg->dev, retval, "failed to initialize dual-role\n");
- goto error_init;
+ goto error;
}
if (hsotg->dr_mode != USB_DR_MODE_HOST) {
@@ -637,9 +716,6 @@ static int dwc2_driver_probe(struct platform_device *dev)
error_drd:
dwc2_drd_exit(hsotg);
-error_init:
- if (hsotg->params.activate_stm_id_vb_detection)
- regulator_disable(hsotg->usb33d);
error:
if (hsotg->ll_hw_enabled)
dwc2_lowlevel_hw_disable(hsotg);
@@ -661,37 +737,6 @@ static int __maybe_unused dwc2_suspend(struct device *dev)
dwc2_drd_suspend(dwc2);
- if (dwc2->params.activate_stm_id_vb_detection) {
- unsigned long flags;
- u32 ggpio, gotgctl;
-
- /*
- * Need to force the mode to the current mode to avoid Mode
- * Mismatch Interrupt when ID detection will be disabled.
- */
- dwc2_force_mode(dwc2, !is_device_mode);
-
- spin_lock_irqsave(&dwc2->lock, flags);
- gotgctl = dwc2_readl(dwc2, GOTGCTL);
- /* bypass debounce filter, enable overrides */
- gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS;
- gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN;
- /* Force A / B session if needed */
- if (gotgctl & GOTGCTL_ASESVLD)
- gotgctl |= GOTGCTL_AVALOVAL;
- if (gotgctl & GOTGCTL_BSESVLD)
- gotgctl |= GOTGCTL_BVALOVAL;
- dwc2_writel(dwc2, gotgctl, GOTGCTL);
- spin_unlock_irqrestore(&dwc2->lock, flags);
-
- ggpio = dwc2_readl(dwc2, GGPIO);
- ggpio &= ~GGPIO_STM32_OTG_GCCFG_IDEN;
- ggpio &= ~GGPIO_STM32_OTG_GCCFG_VBDEN;
- dwc2_writel(dwc2, ggpio, GGPIO);
-
- regulator_disable(dwc2->usb33d);
- }
-
if (dwc2->ll_hw_enabled &&
(is_device_mode || dwc2_host_can_poweroff_phy(dwc2))) {
ret = __dwc2_lowlevel_hw_disable(dwc2);
@@ -716,31 +761,6 @@ static int __maybe_unused dwc2_resume(struct device *dev)
}
dwc2->phy_off_for_suspend = false;
- if (dwc2->params.activate_stm_id_vb_detection) {
- unsigned long flags;
- u32 ggpio, gotgctl;
-
- ret = regulator_enable(dwc2->usb33d);
- if (ret)
- return ret;
-
- ggpio = dwc2_readl(dwc2, GGPIO);
- ggpio |= GGPIO_STM32_OTG_GCCFG_IDEN;
- ggpio |= GGPIO_STM32_OTG_GCCFG_VBDEN;
- dwc2_writel(dwc2, ggpio, GGPIO);
-
- /* ID/VBUS detection startup time */
- usleep_range(5000, 7000);
-
- spin_lock_irqsave(&dwc2->lock, flags);
- gotgctl = dwc2_readl(dwc2, GOTGCTL);
- gotgctl &= ~GOTGCTL_DBNCE_FLTR_BYPASS;
- gotgctl &= ~(GOTGCTL_BVALOEN | GOTGCTL_AVALOEN |
- GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL);
- dwc2_writel(dwc2, gotgctl, GOTGCTL);
- spin_unlock_irqrestore(&dwc2->lock, flags);
- }
-
if (!dwc2->role_sw) {
/* Need to restore FORCEDEVMODE/FORCEHOSTMODE */
dwc2_force_dr_mode(dwc2);
--
2.43.0