[PATCH v3 5/6] usb: dwc2: hcd: don't disable/enable external vbus by system suspend/resume
From: Fabrice Gasnier
Date: Fri Nov 06 2026 - 12:42:17 EST
It's not required to remove port power upon system suspend, as explained
in commit 113f86d0c302 ("usb: dwc2: Update partial power down entering by
system suspend"). It can be addressed with hub ctrl (USB_PORT_FEAT_POWER).
PM suspend flow doesn't disable vbus while in host mode (neither by
clearing HPRT0_PWR nor by calling dwc2_vbus_supply_exit()). In sequence:
- dwc2_port_suspend() - enters clock gating (set lx_state to DWC2_L2)
- _dwc2_hcd_suspend() - nothing more to do as already in L2.
PM resume flow after wakeup (two cases as registers may have been lost)
- _dwc2_hcd_resume() - checks HPRT0_CONNSTS
1 - if set (registers not lost) - nothing more to do.
- followed by dwc2_port_resume() - exits clock gating (sets lx_state to
DWC2_L0)
2 - if NOT set - exits clock gating, re-initializes the controller,
restores HPRT0_PWR and calls dwc2_vbus_supply_init() unbalanced.
For the 2nd case, it's unbalanced / enabled each time it happens: the
vbus regulator enable count constantly increase. As a result, vbus
won't be turned off in case the role changes later to device mode.
So remove the disable/enable of external vbus in bus suspend/resume
routines to avoid this. Moreover, setting/restoring HPRT0_PWR for the
2nd case will simply make the vbus regulator enable count to be aligned
with it.
Fixes: 113f86d0c302 ("usb: dwc2: Update partial power down entering by system suspend")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Fabrice Gasnier <fabrice.gasnier@xxxxxxxxxxx>
---
Changes in v3:
- Sashiko comment (state desync leaves VBUS regulator permanently off)
Add a dwc2_vbus_supply_restore() to check regulator enable state
during hcd resume, if it has been turned off earlier, enable it,
so it is kept sync'ed with PWR bit that gets set.
Changes in v2:
- rephrase commit message since Sashiko review. Hopefully it better
explains the issue, and why HPRT0_PWR is kept synchronized with
external vbus supply.
---
drivers/usb/dwc2/hcd.c | 27 +++++++++++++++++++++------
1 file changed, 21 insertions(+), 6 deletions(-)
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
index cae9d7e13d47..54e3b5698642 100644
--- a/drivers/usb/dwc2/hcd.c
+++ b/drivers/usb/dwc2/hcd.c
@@ -147,6 +147,20 @@ static int dwc2_vbus_supply_exit(struct dwc2_hsotg *hsotg)
return 0;
}
+static int dwc2_vbus_supply_restore(struct dwc2_hsotg *hsotg)
+{
+ int ret;
+
+ if (!hsotg->vbus_supply)
+ return 0;
+
+ ret = regulator_is_enabled(hsotg->vbus_supply);
+ if (!ret)
+ return regulator_enable(hsotg->vbus_supply);
+
+ return ret < 0 ? ret : 0;
+}
+
/**
* dwc2_enable_host_interrupts() - Enables the Host mode interrupts
*
@@ -4388,10 +4402,6 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)
goto skip_power_saving;
}
- spin_unlock_irqrestore(&hsotg->lock, flags);
- dwc2_vbus_supply_exit(hsotg);
- spin_lock_irqsave(&hsotg->lock, flags);
-
/* Ask phy to be suspended */
if (!IS_ERR_OR_NULL(hsotg->uphy)) {
spin_unlock_irqrestore(&hsotg->lock, flags);
@@ -4500,9 +4510,14 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
spin_lock_irqsave(&hsotg->lock, flags);
}
- /* Enable external vbus supply after resuming the port. */
spin_unlock_irqrestore(&hsotg->lock, flags);
- dwc2_vbus_supply_init(hsotg);
+ /*
+ * Restore external vbus supply after resuming the port.
+ * Keeps vbus_supply in sync with HPRT0_PWR set here after.
+ */
+ ret = dwc2_vbus_supply_restore(hsotg);
+ if (ret)
+ return ret;
/* Wait for controller to correctly update D+/D- level */
usleep_range(3000, 5000);
--
2.43.0