gk7205v200: flip dwc3 to host before unbinding xhci (otg) (#114)

Booting the otg profile with /etc/usbmode = device made S72usbmode's
`usb-mode device` log, on every single boot:

    xhci-hcd xhci-hcd.0.auto: Host not halted after 16000 microseconds.
    xhci-hcd xhci-hcd.0.auto: Host controller not halted, aborting reset.

dwc3_core_init() leaves GCTL.PRTCAPDIR = OTG(3) and this 4.9 dwc3 never
moves off OTG on its own. xhci-hcd binds and registers both buses on top
of that anyway, but it is not actually driving the port -- so unbinding
straight from OTG asks xhci_halt() to halt a host that was never really
running, and it times out.

The gadget composed regardless, so this was cosmetic in practice, but it
left the host controller un-halted: exactly the live-DMA-engine state the
unbind is there to avoid.

Flip the controller to host before unbinding, so the already-bound xHCI
is genuinely running and the unbind halts cleanly. A switch from host is
already in that state, so only boot pays for the extra flip.

Measured on gk7205v200 (Xiongmai IPC_GK7205V200_50H20AI_S38): 5/5 cold
boots warned before, 0/5 after. Runtime host<->device round trips stay
clean and still take ~0.35s, with no reboot and no majestic restart.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dmitry Ilyin
2026-07-31 18:21:02 +03:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 96633d5577
commit 94cce13948
@@ -99,6 +99,20 @@ to_device() {
need_debugfs
if X=$(xhci_device) && [ -e "/sys/bus/platform/drivers/xhci-hcd/$X" ]; then
# Coming straight from boot the controller is still in its power-on
# GCTL.PRTCAPDIR = OTG(3) (see the header note: nothing moves it off
# OTG on its own). xhci-hcd binds and registers both buses on top of
# that anyway, but it is not actually driving the port -- so unbinding
# directly from OTG asks xhci_halt() to halt a host that was never
# really running, and it times out:
# xhci-hcd.0.auto: Host not halted after 16000 microseconds.
# xhci-hcd.0.auto: Host controller not halted, aborting reset.
# Leaving the controller un-halted is precisely the live-DMA-engine
# state this unbind exists to avoid. Flip to host first so the already
# bound xHCI is genuinely running, and the unbind then halts cleanly.
# A switch from host is already in that state, so only boot pays for it.
[ "$(cat "$MODE_FILE")" = host ] || echo host > "$MODE_FILE"
echo "usb-mode: unbinding host controller $X"
echo "$X" > /sys/bus/platform/drivers/xhci-hcd/unbind
fi