Describe the bug
I have a CM5 where I need to switch off USB 5v VBUS to the USB2 (OTG) USB Port, this is implemented on our PCB.
While implementing my voltage regulator driver I noticed that I can only power cycle the USB port once, further power cycle requests (i.e. via uhubctl) fail to disable the regulator. And the regulator usage count stays >0.
I've asked claude for possible reasons, this is what it responded with:
● Found the definitive mechanism. dwc2_core_host_init only re-asserts HPRT0_PWR if it was cleared — and it's called from dwc2_hcd_reinit during _dwc2_hcd_resume. Now let me confirm the call
chain and the final write at line 4548:
The root cause is now fully identified:
_dwc2_hcd_resume is registered as the bus_resume hook (line 5052-5053). When the USB bus resumes from a suspended state (lx_state == DWC2_L2), it does three things that fight
ClearPortFeature(PORT_POWER):
1. Line 4508: dwc2_hcd_reinit → dwc2_core_host_init (line 2262-2270): if op_state == OTG_STATE_A_HOST (which is always set for dr_mode = "host") and HPRT0_PWR is currently 0 — it writes it
back to 1.
2. Line 4538: dwc2_vbus_supply_init — unconditional regulator_enable, incrementing use_count.
3. Line 4548: explicitly writes HPRT0_PWR | HPRT0_CONNDET | HPRT0_ENACHG to HPRT0 — forcing HPRT0_PWR = 1 regardless.
The trigger sequence:
1. ClearPortFeature(PORT_POWER) clears HPRT0_PWR = 0, calls dwc2_vbus_supply_exit
2. If the bus was in lx_state = DWC2_L2 (auto-suspended), OR the device disconnect event causes the USB core to call bus_resume to process the port status change...
3. _dwc2_hcd_resume runs: lx_state == L2 check passes, CONNSTS == 0 check passes (device just disconnected), falls through to the full resume path
4. HPRT0_PWR is re-written to 1, use_count is incremented again
Why the bus is in L2: USB autosuspend. When the connected device goes idle (default: 2 seconds), the USB stack auto-suspends it, then auto-suspends the root hub, calling bus_suspend →
_dwc2_hcd_suspend → sets lx_state = L2.
---
Fix: disable root hub autosuspend
echo -1 > /sys/bus/usb/devices/usb1/power/autosuspend_delay
With autosuspend disabled, _dwc2_hcd_suspend (bus_suspend) is never called, lx_state stays at DWC2_L0, and _dwc2_hcd_resume exits immediately at line 4455:
if (hsotg->lx_state != DWC2_L2)
goto unlock; // ← exits here, no HPRT0_PWR re-assertion
To make this permanent, add a udev rule:
# /etc/udev/rules.d/99-usb-no-autosuspend.rules
ACTION=="add", SUBSYSTEM=="usb", DRIVER=="usb", ATTR{idVendor}=="0000", \
ATTR{power/autosuspend_delay}="-1"
Or target the root hub specifically:
ACTION=="add", SUBSYSTEM=="usb", KERNELS=="usb1", ATTR{power/autosuspend_delay}="-1"
The deeper issue: DWC2's bus_resume is designed to unconditionally restore port power (it has no awareness that ClearPortFeature(PORT_POWER) was intentional). ClearPortFeature(PORT_POWER)
and bus autosuspend/resume are fighting each other. Disabling autosuspend removes bus_suspend/resume from the equation entirely, leaving use_count managed only by _dwc2_hcd_start (initial
enable) and ClearPortFeature/SetPortFeature (user's uhubctl calls).
Disabling autosuspend does indeed work around the issue.
Steps to reproduce the behaviour
Couple a regulator to the USB port via a dtoverlay (untested example!):
/dts-v1/;
/plugin/;
/{
compatible = "brcm,bcm2708";
fragment@0 {
target-path = "/";
__overlay__ {
usb_power_regulator: fixedregulator@0 {
compatible = "regulator-fixed";
regulator-name = "fixed-supply";
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
gpio = <&gpios 14 0>;
enable-active-high;
};
};
};
fragment@1 {
target = <&usb>;
dwc2_usb: __overlay__ {
power-domains = <0 0>;
compatible = "brcm,bcm2835-usb";
dr_mode = "host";
g-np-tx-fifo-size = <32>;
g-rx-fifo-size = <558>;
g-tx-fifo-size = <512 512 512 512 512 256 256>;
status = "okay";
vbus-supply = <&usb_power_regulator>;
};
};
};
And use uhubctrl to disable the USB port (CM5):
uhubctl -l 5 -p 1 -a off
verify the regulator switched to disabled via sysfs then re-enable it:
uhubctl -l 5 -p 1 -a on
Observe the regulator is back on, then repeat and observe the regulator doesn't get disabled anymore
Device (s)
Raspberry Pi CM5
System
CM5, custom receiver PCB, Custom Yocto based OS, RPI-Linux 6.12.75
Logs
No response
Additional context
If this is by design, feel free to close, the work-around also works for me.
Describe the bug
I have a CM5 where I need to switch off USB 5v VBUS to the USB2 (OTG) USB Port, this is implemented on our PCB.
While implementing my voltage regulator driver I noticed that I can only power cycle the USB port once, further power cycle requests (i.e. via uhubctl) fail to disable the regulator. And the regulator usage count stays >0.
I've asked claude for possible reasons, this is what it responded with:
Disabling autosuspend does indeed work around the issue.
Steps to reproduce the behaviour
Couple a regulator to the USB port via a dtoverlay (untested example!):
And use uhubctrl to disable the USB port (CM5):
uhubctl -l 5 -p 1 -a offverify the regulator switched to disabled via sysfs then re-enable it:
uhubctl -l 5 -p 1 -a onObserve the regulator is back on, then repeat and observe the regulator doesn't get disabled anymore
Device (s)
Raspberry Pi CM5
System
CM5, custom receiver PCB, Custom Yocto based OS, RPI-Linux 6.12.75
Logs
No response
Additional context
If this is by design, feel free to close, the work-around also works for me.