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 Rp — STATUS0[1:0] per the FUSB302 datasheet:
BC_LVL | Source Rp | Means |
|---|---|---|
0 | < 200 mV (no Rp) | nothing connected on this line, or the wrong CC line |
1 | 200–660 mV | source advertising USB default (500 mA / 5 V) |
2 | 660–1230 mV | source advertising 1.5 A |
3 | > 1230 mV | source 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:
type=0x02— PD message type.0x02isRequest(Data);0x01isSource_Capabilities;0x03isAccept;0x04isReject;0x06isPS_RDY. See USB-PD r3.1 §6.2.1.1.4.ndo=1— number of data objects. ARequestcarries one RDO; aSource_Capabilitiescarries one PDO per advertised supply.id=2—MessageIDfield, low 3 bits, increments per message in the partner direction. Useful for matching a TX against the GoodCRC the source sends back.hdr=0x1042— the 16-bit PD header. Bits decode as type (0x02), port data role (sink), SPECREV (Rev3,0b10), port power role (sink),MessageID, number of data objects, and extended bit.ee8daadfusb302: dynamic SPECREV, programmed MEASURE/POWER, rx_msgid tracking made SPECREV dynamic — the value here should match what the partner advertised back, downgrading to Rev2 only if the source is Rev2.fifo(15)— number of bytes pushed to the chip TX FIFO. For a Request: 1 bytePACKSYM | byte_count, 2 bytes header LSB-first, 4 bytes RDO, 4 bytes JAM_CRC (chip computes), plus framing tokens (SOP1 SOP1 SOP1 SOP2 PACKSYM TXOFF JAM_CRC EOP TXON).fifo: 12 16 13 14 15 16 …— raw bytes written to register0x43. The first five (12 12 12 13) are the SOP-prefixed packet start tokens;86isPACKSYM | 0x06(six payload bytes follow);42 10is the header LSB-first (0x1042);2c b1 04 21is the RDO0x2104b12c;ff 14areEOPandTXON.TX regs:the second line shows the chip state before and after the FIFO push and afterCONTROL0.TX_START(c3809d5fusb302: actually trigger transmission with CONTROL0.TX_START ).SW1=0x45confirms the active CC line is selected for measurement and TX.ST0=0x80hasVBUSOKset;ST1=0x28hasRX_EMPTYset.TXSTART_err=0 -> TX_SENT in 4 ms— the chip raisedTX_SENT(inINT_A,0x01) 4 ms afterTX_START. Other outcomes:RETRY_FAIL(no GoodCRC after retries),HARD_RESET(partner sent hard reset),COLLISION(BMC line active when we tried to start). With045efa2fusb302: event-driven TX completion + per-TX retry program + COLLISION unmask the policy engine handles each as a discrete event rather than assumingTX_SENTonTX_START.
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:
- No cable detected —
vbus_present=0,orientation=0. The chip never sawRp. Usually a kill-switch, dead cable, or the source isn’t assertingRp(some power banks do power-good gating on wake). - Cable detected but
BC_LVL=1— the source is a USB-default 5 V / 500 mA non-PD port. There is no PD to negotiate. Use a different source. - Cable detected,
BC_LVL=3, but noSource_Capabilities— the source is PD-capable but isn’t initiating negotiation. Trydev.fusb302.0.reattach=1to force the source to re-issueSource_Capabilities. Source_Capabilitiesdecoded, Request transmits, noAccept— usually a SPECREV mismatch (now defaulted to Rev3, downgrade-on-Rev2 ina8b584dfusb302: default GoodCRC to Rev3 (downgrade only on Rev2 partner) ) or a TX completion event we lost. CheckTX regs: → TX_SENTlines in the serial log.
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.