Identity
| Part | ON Semi FUSB302B (FUSB302BMPX) |
| Role | USB Type-C controller: CC line detection + orientation, BMC PHY for USB-PD messaging, FIFO-backed message TX/RX |
| Bus / address | DTS &i2c4 addr 0x22; FreeBSD logs shifted addr 0x44 on iicbus3 |
| GPIO / IRQ | INT_N on GPIO1_A2; IRQ-driven worker per 5ca6317 fusb302: switch from 10ms polling to IRQ-driven worker |
| Datasheet | FUSB302B datasheet (ON Semi) |
| Pine64 wiki | PinePhone Pro hardware |
| Schematic | sheet 5 (USB-C / PD) |
Status — ● working
Sink works end-to-end on every fresh boot: CC orientation, VBUS detection,
Source_Capabilities decode, Request/Accept handshake, and a 9 V / 3 A
contract that flows into the rk818 input current limit. The bring-up arc
and live verification recipe are documented in essay 16
and the USB-C / PD recipe. The post-Hard_Reset
recovery code is now in the overlay, but still needs a hardware soak to
prove both received and locally-sent resets avoid PE_DISABLED. Source-role
is bench-proven: on 2026-05-08, with hw.dwc3.force_host=1 and
dev.fusb302.0.source_role=1, the phone enumerated a real Anker USB-C
hub with a USB 2.0 hub IC, an ASIX AX88179A Gigabit-Ethernet adapter, a
Norelsys NS1081 mass-storage bridge, and a Chicony USB keyboard. PD
source-side messaging (PE_SRC_*, VDM/alt-mode) is still not implemented.
Driver
- Our tree:
src/sys/dev/iicbus/fusb302.c(1885 lines) — full Type-C state machine + PD r3.0 sink policy engine, BMC PHY framing, IRQ-driven worker, sysctls. - Linux mainline:
drivers/usb/typec/tcpm/fusb302.c(chip driver) anddrivers/usb/typec/tcpm/tcpm.c(generic Type-C / PD policy engine).
Linux splits the chip-specific FUSB302 driver from the generic TCPM
state machine; we collapse both into a single file. That works for
sink-only on a chip with this much I/O surface, but the cross-driver
audit flagged two structural issues from the merge: PE_DISABLED is
a FreeBSD-only state (Linux doesn’t have it; recovery is a timed
sequence with PD_T_SAFE_0V), and the post-Hard_Reset BC_LVL=0
watchdog that triggers it was racing the source’s brief Rp drop. The
overlay now writes FUSB302_RESET = RESET_PD on HARDRST, resets the
BC_LVL watchdog state, adds PE_SNK_HARD_RESET_RECOVERY, and gives
locally-sent Hard_Reset a timed PD_T_PS_HARD_RESET state. See the
audit for register-level specifics.
PD_T_SENDER_RESP_MS = 100 remains intentionally generous for this
polling driver. The sink-side Hard_Reset paths now carry the important
timers explicitly: PD_T_PS_HARD_RESET_MS = 30 for Hard_Reset we send,
and PD_T_SAFE_0V_MS = 650 for Hard_Reset received from a source.
Open work
Hard_Reset recovery:Landed; see Parity verification below.RESET_PDwrite, BC_LVL watchdog suppression,PD_T_SAFE_0Vdeadline. Resolves thePE_DISABLEDwedge.BumpLanded; see Parity verification below.PE_SNK_HARD_RESETsend delay fromDELAY(5000)(5 ms) to a timed 30 msPD_T_PS_HARD_RESETstate.Re-confirm bus address.Resolved: DTS/source uses&i2c4addr 0x22; FreeBSD logs the same chip as shifted addr 0x44 oniicbus3.
Source-role: bench-proven
dev.fusb302.0.source_role=1 asks the power_supply layer to put
rk818-charger into source mode, advertises Rp on both CC pins, and
polls the source-side CC comparators as source_cc1 / source_cc2.
The request is refused when the cached rk818 battery voltage is below
dev.fusb302.0.source_min_voltage_mv so a weak battery cannot be asked
to power a hub. f754656 typec: guard source role on battery voltage c3ed085 rk818: avoid live i2c in voltage callback
ebd8587 fusb302: return non-retry error for source guard
FUSB302 attach is now tolerant of transient I2C NACKs during boot:
register reads and writes retry briefly before returning the bus error.
This was added after a hub-attached reboot left the controller as an
unattached unknown child on iicbus3 with Reset failed: 2.
4e5d139 fusb302: retry transient i2c transfer failures
On 2026-05-08, with kernel #203 (carrying 5a1cb12 dwc3: log host-attach state unconditionally for
DWC3 host-attach diagnostics), hw.dwc3.force_host=1 set via
nextboot, an Anker USB-C hub plugged in, and the battery at 3697 mV:
# sudo sysctl dev.fusb302.0.source_role=1
fusb3020: source mode: polling as DFP, advertising default Rp on CC1/CC2
rk818_pmu0: rk818: type-C role -> source (5V→VBUS) (DCDC_EN=0xff)
fusb3020: source attach: CC1=Ra CC2=Rd, advertising default Rp
ugen4.2: <VIA Labs, Inc. USB2.0 Hub>
ugen4.3: <ASIX AX88179A>
ugen4.4: <Norelsys NS1081> umass0
ugen4.5: <CHICONY USB Keyboard> hkbd0
ugen4.6: <AnkerInnovations Limited Anker USB-C Hub Device>
See appendix: USB-C source role and host-mode bring-up for the full receipt and decode.
Still scaffolded / open
hw.dwc3.force_host=1is a boot-time override; FUSB302 state changes do not yet drive a runtime DWC3 role switch.rk_typec_phydoes not swap SS+/SS- for cable orientation, so downstream USB 3 endpoints behind the hub stay silent (everything enumerates as USB 2.0 HIGH).- There is no
PE_SRC_*source-policy engine: source-mode VBUS rises but no PDSource_Capabilitiesare sent, so the partner can never request more than 5 V default current. - DisplayPort alt-mode also needs PD VDM negotiation (Discover Identity /
SVIDs / Modes / Enter Mode / DP Configure), TCPHY DP lane programming,
and a FreeBSD
cdn_dpDRM bridge driver.
Parity verification
Four changes from the 2026-04-30 cross-driver audit landed in src/sys/dev/iicbus/fusb302.c:
INTA.HARDRSThandler writesFUSB302_RESET = FUSB302_RESET_PD(BIT(1)) — flushes the chip’s protocol layer, matching Linux’sfusb302_pd_reset()(drivers/usb/typec/tcpm/fusb302.c).- Same handler resets
bc_lvl_zero_ticks = 0so the BC_LVL=0 watchdog at line ~1296 doesn’t fire on the post-Hard_Reset CC blip and slam us toPE_DISABLED. - New
PE_SNK_HARD_RESET_RECOVERYstate withPD_T_SAFE_0V_MS = 650deadline before falling intoPE_SNK_STARTUP. Collapses Linux’sSNK_HARD_RESET_SINK_OFF → SNK_HARD_RESET_SINK_ON → SNK_STARTUP(drivers/usb/typec/tcpm/tcpm.c::_tcpm_pd_hard_reset) into a single timed transition since we don’t toggle VBUS ourselves. - Locally-sent
PE_SNK_HARD_RESETnow sends the chip Hard_Reset token once, waitsPD_T_PS_HARD_RESET_MS = 30, then returns toPE_SNK_STARTUP; no more 5 ms busy-wait in the policy-engine tick.
Bench predicates:
- Pre-fix baseline. Trigger a Hard_Reset (physical USB-C replug, or send one from a PD test tool).
sysctl dev.fusb302.0.pe_stateshowsDisabled, and onlydev.fusb302.0.reattach=1recovers. That was the wedge. - Post-fix expected sequence. After a Hard_Reset,
sysctl dev.fusb302.0.pe_stateshould walkHard_Reset_Recovery → SNK_Startup → SNK_Discovery → SNK_Wait_For_Capabilities → SNK_Evaluate_Capability → SNK_Select_Capability → SNK_Transition_Sink → SNK_Readywithin ~2 seconds. Battery charging resumes (visible viahw.acpi.battery.rate > 0) without manual intervention. - Falsifier — change 2 didn’t take. If
pe_stateever readsDisabledpost-fix, the BC_LVL=0 watchdog reset wasn’t applied; revisit the HARDRST handler order. - Falsifier — change 1 didn’t take. If
pe_statereachesSNK_Readybut PD renegotiation fails (noSource_Capabilitiesreceived withinPD_T_TYPEC_SINK_WAIT_MS = 600 ms), the chip’s protocol layer wasn’t flushed; the SOP detector or message-id state desynchronized from the partner. - Falsifier — change 3 didn’t take. If
pe_statejumps directly toSNK_Startup(skippingHard_Reset_Recovery), the new state-machine case was never compiled in.
Source-role / OTG (bench-proven 2026-05-08)
Bench recipe:
# kernel #203 or later (carries 5a1cb12 host-attach diagnostics)
sudo nextboot -e hw.dwc3.force_host=1
sudo reboot
# after boot:
sysctl hw.dwc3.force_host # expect 1
sysctl dev.rk818_pmu.0.rk818_voltage_mv # expect ≥ 3600 mV
sudo sysctl dev.fusb302.0.source_role=1
sleep 1
sysctl dev.fusb302.0.source_cc1 dev.fusb302.0.source_cc2 dev.fusb302.0.vbus_present
usbconfig
Expected post-source_role=1 state (Anker USB-C hub partner):
dev.fusb302.0.source_cc1: 1 (Ra — VCONN sense, full-feature C-to-C cable)
dev.fusb302.0.source_cc2: 2 (Rd — sink/UFP)
dev.fusb302.0.vbus_present: 1
dev.rk818_pmu.0.rk818_source_role: 1
dmesg:
fusb3020: source mode: polling as DFP, advertising default Rp on CC1/CC2
rk818_pmu0: rk818: type-C role -> source (5V→VBUS) (DCDC_EN=0xff)
fusb3020: source attach: CC1=Ra CC2=Rd, advertising default Rp
usbconfig:
ugen4.2: <USB2.0 Hub VIA Labs, Inc.> ← downstream hub IC
ugen4.3: <AX88179 Gigabit Ethernet ...>
ugen4.4: <NS1081 Norelsys>
ugen4.5: <KU-0833 Keyboard Chicony ...>
ugen4.6: <Anker USB-C Hub Device ...>
Falsifiers:
sysctl ... source_role=1returnsOperation not permitted→ battery is belowdev.fusb302.0.source_min_voltage_mv(3600 mV default). Plug into a wall charger or a hub that supplies VBUS-in before retrying.- VBUS doesn’t rise → rk818 boost_otg or otg_switch isn’t actually
being enabled. Read
dev.rk818_pmu.0.rk818_source_roleandhw.acpi.battery.charger_dump=1aftersource_role=1. - VBUS rises but no enumeration → DWC3 is still in peripheral mode.
Confirm
sysctl hw.dwc3.force_hostreports1and thatdmesg | grep "Configured for host mode"is present. - All downstream devices enumerate as USB 2.0 HIGH only → expected
today;
rk_typec_phydoes not yet swap SS+/SS- for orientation. - BC_LVL=0 watchdog fires during source role → the watchdog needs to be source-aware (it’s currently sink-only logic).
Source-mode draws real current. With nothing plugged into the wall side
of the hub, the phone is powering everything from the battery: a fresh
3731 mV reading dropped to 3595 mV inside roughly a minute of
source_role=1 while the AX88179, hub IC, and keyboard were enumerated.
For longer benches, feed the hub’s PD-in port from a wall charger
instead of running on battery.
Related
- fusb302: a USB-PD sink that works — bring-up arc.
- USB-C / PD verification — fresh-boot verification recipe.
- Cross-driver audit — fusb302 + rk818 section, prioritised fixes.
- Component: RK818 — accepts the negotiated current as input limit; future OTG path.
- Why the rk818 says “discharging” with PD plugged in — joint debug story.