Appendix · recipe

USB-C / PD verification

How to confirm fusb302 negotiated a PD contract on a fresh boot, how to wake a stuck source, and how to read the chip's diagnostic lines.

The PinePhone Pro’s USB-C port is driven by an ON Semi FUSB302 controller on DTS &i2c4 at 7-bit address 0x22 (FreeBSD logs shifted addr 0x44 on iicbus3), with fusb302(4) running the sink policy engine and rk818_charger(4) accepting the negotiated current as an input limit. Bring-up is in essay 16. This page is the reference for verifying it works, recovering when it doesn’t, and decoding the chip’s serial diagnostics.

1. Verify PD on a fresh boot

After the phone has booted and a PD source is plugged in, four sysctls under dev.fusb302.0.* carry the contract state:

ssh pinephone 'sysctl dev.fusb302.0'

A working contract shows:

dev.fusb302.0.vbus_present: 1
dev.fusb302.0.orientation: 1                # 1 = CC1 (normal), 2 = CC2 (flipped)
dev.fusb302.0.negotiated_voltage_mv: 9000
dev.fusb302.0.negotiated_current_ma: 3000
dev.fusb302.0.max_voltage_mv: 9000          # configurable cap; PE picks highest PDO ≤ this

If vbus_present=1 and orientation is non-zero but negotiated_voltage_mv=0, negotiation either hasn’t completed yet (give it a couple of seconds — tNoResponse is 5 s) or has wedged. Skip to section 2.

The rk818 side picks up the negotiated current on the same Transition_Sink → Ready edge ( ef6035e fusb302+rk818: apply PD-negotiated input current to the charger ). Confirm via the kernel’s diagnostic line in dmesg:

rk818: USB input current limit set to 3000 mA (idx 11, USB_CTRL=0x4b)

The full live capture from a fresh-boot negotiation looks like:

Cable detected: CC1 (normal), VBUS=yes, CC1_lvl=3
negotiated PD Rev3 (partner=3, SW1=0x45 C3=0x05)
Source_Capabilities: 4 PDOs
  PDO 1: Fixed 5000 mV / 2400 mA
  PDO 2: Fixed 9000 mV / 3000 mA
  PDO 3: Fixed 15000 mV / 3000 mA
  PDO 4: Fixed 20000 mV / 2250 mA
Sending Request for PDO 2 (RDO=0x2104b12c)
PE: Select_Capability -> Transition_Sink
PD negotiated: 9000 mV / 3000 mA
rk818: USB input current limit set to 3000 mA (idx 11, USB_CTRL=0x4b)
PE: Transition_Sink -> Ready

Capture it during a deliberate plug-in:

mise run serial:capture -- pd-fresh-boot
# … plug the PD source in
grep -E 'fusb302|rk818|PE:|PDO|Source_Capabilities|negotiated' \
  logs/serial/*-pd-fresh-boot.log

2. Wake a stuck source with reattach

When negotiation gets wedged — vbus_present=1, cable still in, but no contract — a real source is often stuck in Type-C attached state and won’t re-issue Source_Capabilities of its own accord. Unplugging and replugging works but is invasive. The driver exposes a soft equivalent:

ssh pinephone 'sudo sysctl dev.fusb302.0.reattach=1'

This drops the Rd pull-down on the active CC line for 200 ms, then re-runs chip init ( 94b42f7 fusb302: re-detect orientation on BC_LVL drop, add reattach sysctl + ce438be fusb302: reattach now drops Rd for 200ms before re-init ). The source sees a Type-C detached → attached cycle and re-issues Source_Capabilities from scratch. Watch the serial log for the recovery:

reattach: dropping Rd for 200ms then re-init
Cable detected: CC1 (normal), VBUS=yes, CC1_lvl=3
Source_Capabilities: …
PE: Transition_Sink -> Ready
PD negotiated: …

If reattach doesn’t recover it either, the chip itself may be in a wedged state — try the hard handle (kldunload + kldload of the driver, or a phone reboot) and capture the chip register dump (section 4) before doing so.

3. BC_LVL values in chip: dump output

The Cable detected: … line carries the BC_LVL reading on the active CC line as CC1_lvl=… or CC2_lvl=…. BC_LVL is the chip’s measurement of how strongly the source is pulling RpSTATUS0[1:0] per the FUSB302 datasheet:

BC_LVLSource RpMeans
0< 200 mV (no Rp)nothing connected on this line, or the wrong CC line
1200–660 mVsource advertising USB default (500 mA / 5 V)
2660–1230 mVsource advertising 1.5 A
3> 1230 mVsource advertising 3.0 A — required for PD r3.0 negotiation at 9 V+

CC1_lvl=3 on a connected PD source is what you want to see: the source is willing to drive ≥ 3 A and is therefore willing to enter PD negotiation with high-power PDOs. CC1_lvl=1 means you’re plugged into a USB-C cable on a non-PD port (a 5 V/500 mA dumb wall wart) — the chip will detect the cable but Source_Capabilities will never arrive.

The driver also watches for BC_LVL=0 on the active line for more than 500 ms — that’s the cable-pull-out signal and triggers an orientation re-detection (fusb302(4) resets to PE_DISABLED and re-runs the discovery path).

4. Reading the TX[type=…] fifo(15): … diagnostic line

When the policy engine queues a PD message, the driver dumps the TX FIFO contents and the surrounding chip state. A typical line on a successful Request looks like:

TX[type=0x02 ndo=1 id=2 hdr=0x1042] fifo(15): 12 16 13 14 15 16 86 42 10 2c b1 04 21 ff 14
TX regs: pre SW1=0x45 ST0=0x80 ST1=0x28 | fifo_err=0 post-FIFO ST0=0x80 ST1=0x28 | TXSTART_err=0 -> TX_SENT in 4 ms (INT=0x40 INTA=0x01)

Decode:

If TXSTART_err is non-zero, the I2C write to CONTROL0.TX_START itself failed — usually because the bus is wedged or the chip is unresponsive. If the outcome is RETRY_FAIL, the partner isn’t acknowledging — check the SPECREV in the header, and check that BC_LVL=3 on the active CC line.

5. When negotiation never starts

The states that prevent the policy engine from leaving PE_DISABLED:

6. Confirming the rk818 followed the contract

The PD contract sets the rk818’s input current limit, not the battery-side charge current. To confirm the input limit changed:

ssh pinephone 'dmesg | grep -E "rk818.*input current"'

Expected output after a 9 V / 3 A negotiation:

rk818: USB input current limit set to 3000 mA (idx 11, USB_CTRL=0x4b)

idx 11 is the rk818’s USB_CTRL.ILIM_SEL value for 3000 mA per the RK818 datasheet table; USB_CTRL=0x4b is the full register write, with bit 7 USB_ILIM_EN set.

The battery-side charge current (CHRG_CTRL_REG1.ICHRG) is still at the rk818 default — bumping it on PD negotiation is a separate near-term follow-up (see essay 15). Until that lands, a successful PD contract gives the phone room on the input rail to support sustained load (Hyprland + GPU + pkg install no longer brown the PMIC out) but doesn’t actually charge the battery faster than baseline.