[PATCH v2 5/5] usb: dwc2: platform: fix stm32mp15 detection logic power management

From: Fabrice Gasnier

Date: Tue Oct 13 2026 - 13:36:04 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).

In __dwc2_lowlevel_hw_disable() in case the dwc2_stm32_hw_disable() fails,
return the error, without disabling other HW resources to avoid possible
inconsistent state (e.g. usb33d supply not disabled, so other resources
are kept in the same state).

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>
---
Changes in v2:
- fix probe error path in case devm_regulator_get or dwc2_stm32_hw_enable
fails as raised by Sashiko. Clear activate_stm_id_vb_detection flag so
the dwc2_stm32_hw_disable() routine won't call regulator_disable()
respectively with the ERR_PTR returned or unbalanced.
- update commit message to describe error handling on HW disable.
---
drivers/usb/dwc2/platform.c | 173 +++++++++++++++++++++++++-------------------
1 file changed, 98 insertions(+), 75 deletions(-)

diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c
index 5aa552672078..5841efef6b35 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,26 @@ 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);
+ /* skips low level dwc2_stm32_hw_disable() in case of error */
+ hsotg->params.activate_stm_id_vb_detection = 0;
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");
+ hsotg->params.activate_stm_id_vb_detection = 0;
+ 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 +719,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 +740,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 +764,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