DWC2 Driver: Unable to power cycle vbus regulator enabled USB port more than once.

Open
#7,349 9 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
45/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
c, linux

Research direction

Start by tracing the DWC2 entry points named in the report: _dwc2_hcd_resume, dwc2_core_host_init, and dwc2_vbus_supply_init, alongside ClearPortFeature(PORT_POWER). Reproduce with uhubctl on the CM5 and compare behavior with root-hub autosuspend disabled. Done means repeated off/on cycles reliably disable the regulator and its usage count does not remain above zero.

Written by the indexing model from the issue text.

Description

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.

Dominant language
C
Stars
13.2k
Forks
5.5k
Avg merge
2d 21h
Merged PRs (30d)
21

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from raspberrypi/linux

All issues in raspberrypi/linux

Similar issues

More C issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.