Identity
| Part | Rockchip RK818 |
| Role | System PMIC — DCDC and LDO regulators, RTC, USB / DC charger, battery fuel gauge, 5 V OTG boost (boost_otg + otg_switch), SWITCH2 switched output |
| Bus / address | i2c0 addr 0x1c |
| GPIO / IRQ | PMIC IRQ on the RK3399 PMU domain; charger and battery interrupts surface through the same line |
| Datasheet | RK818 datasheet (community archive) |
| Pine64 wiki | PinePhone Pro hardware |
| Schematic | sheets 9–10 (PMIC, regulators, charger) |
Status — ● working
Regulators come up at boot via U-Boot’s PMIC defaults; the RTC, charger,
and battery driver all attach. Charging now works after 8da75ba rk818: clear OTG/SWITCH2 in DCDC_EN — battery now charges from PD
cleared DCDC_EN_REG[7] (OTG_EN) at init — the chip’s
own 5 V boost was back-driving VBUS while the PD source was sourcing it,
parking the charger FSM at “discharging” even with CHRG_EN set. The
input-current limit follows the FUSB302 PD contract ( ef6035e fusb302+rk818: apply PD-negotiated input current to the charger ).
SWITCH2_EN is deliberately separate from Type-C role handling after the
Linux parity cleanup in 4d190f3 rk818: decouple SWITCH2 from Type-C OTG .
The latest cleanup puts that handoff behind the local power_supply
coordinator instead of letting fusb302(4) include RK818 headers directly
( 6469f2e power: add charger coordination layer ), and the prepared-sink contract from
06348c6 fusb302: prepare sink path after TCPC attach + 9041600 rk818: hold SWITCH2 through TCPC probe + 544bd86 rk818: preserve prepared Type-C sink path
keeps DCDC_EN at 0x3f across the IRQ-driven enforce path so PD
Request TX succeeds on both CC orientations.
Verified live on 2026-05-03 (kernel #138+, head 544bd86 at the
relevant boot): with a 65 W PD wall on CC1, dev.fusb302.0.negotiated_voltage_mv: 9000,
dev.rk818_pmu.0.rk818_hw_input_current_limit_ma: 3000, USB_CTRL 0xcb,
DCDC_EN held at 0x3f across rk818[irq]: enforced sink mode (mask=0xc0, DCDC_EN 0xff->0x3f). Battery rate goes positive (charging) once the
input-limit is raised. PMIC IRQ migration ( d1b3a0b rk818: drive charger updates from PMIC IRQ ) means
charger state updates are event-driven rather than polled.
What’s missing: no suspend/resume integration; TSADC readout and soft caps
are now sane, but hardware thermal trips remain separate work and not
RK818-side. The noisy decoded rk818[poll] register dump
is gone from the normal path; decoded state is now on-demand via
hw.acpi.battery.charger_dump=1 or dev.rk818_pmu.0.rk818_debug=1. The
fuel-gauge sysctls now expose chip-reported state (see Parity verification
below), and the ACPI compatibility status bit is fixed (2 means charging).
Driver
- Our tree:
src/sys/dev/iicbus/pmic/rockchip/rk818_battery.c+rk818_battery.h— combined battery + charger driver. Registersrk818-chargerwithsrc/sys/dev/power_supply/power_supply.c, so Type-C drivers ask for input-current / source-role changes through a small class interface instead of calling RK818 symbols directly. - Linux mainline (split):
drivers/mfd/rk808.c(MFD core),drivers/regulator/rk808-regulator.c(regulators),drivers/power/supply/rk818_battery.c,drivers/power/supply/rk818_charger.c.
Linux splits this chip across MFD core + regulator + battery + charger.
We collapse it into a single battery + charger driver and let U-Boot
own the regulators, which is enough for boot but means we don’t have
runtime regulator control. The charger / fuel-gauge logic is loosely
modelled on the Linux drivers but the FSM interpretation in
rk818-charger-not-actually-charging
documents how much had to be relearned by hand because the chip’s actual
behaviour diverged from both Megi’s downstream driver and the public
datasheet.
The decoupling of OTG and SWITCH2 (cross-driver audit §fusb302) is
a prerequisite for any future source-role / DP-alt-mode work.
Open work
Expose battery state to userspace throughDriver-specific sysctls (hw.battery.*/acpiio(4)soacpiconf -iand OS battery indicators work.dev.rk8xx.0.{voltage_mv,current_ma,soc_pct,remaining_mah,full_mah,ts1_raw,status}) landed; bench verification below. Wiring into FreeBSD’sacpi_batteryprovider API would still be useful for stock tools (acpiconf -i 0,apm) but is not a clean fit for non-ACPI ARM and is deferred.- Convert
BAT_TS1_ADCraw to milli-degrees Celsius (NTC table lookup — see Linux’srk818_bat_get_ntc_resplusntc_table[]). Raw ADC is exposed for now. - Wire the PMIC IRQ for charger / fuel-gauge state changes — currently we refresh cached state from a quiet 1 Hz gauge thread and emit
POWER_SUPPLYdevctl notifications when the cached charger state changes. - Suspend/resume integration: PMIC sleep mode + low-power regulator transitions.
- Run-time regulator control (rk808-regulator equivalent) so consumers can mark rails always-on / boot-on at the kernel level rather than relying on U-Boot defaults.
Parity verification
The fuel-gauge readout refreshes once per second from rk818_battery_thread
and caches values for sysctl readers. Compare against a Linux PinePhone Pro
image’s /sys/class/power_supply/rk818-battery/* files (note: Linux
reports voltage/current in microvolts/microamps, FreeBSD here in mV/mA).
FreeBSD (dev.rk818_pmu.0.*) | Linux (/sys/class/power_supply/rk818-battery/*) | Expected |
|---|---|---|
rk818_voltage_mv | voltage_now / 1000 | 3000–4350 mV depending on SoC |
rk818_current_ma | current_now / 1000 | sign and magnitude move with charge/discharge state |
rk818_capacity_pct | capacity | 0–100 from the current voltage approximation |
rk818_online | USB online / present properties | 1 when SUP_STS.USB_EXS and SUP_STS.USB_EFF are both set |
rk818_status | status | charging / not-charging / full / fault strings |
rk818_input_current_limit_ma | input current limit | requested Type-C / PD limit |
rk818_hw_input_current_limit_ma | input current limit | RK818-supported value actually written to USB_CTRL[3:0] |
Falsifiers
- Voltage way wrong (0 or > 5000 mV): VBAT register order (H vs L) or
the calibration-K/B math is broken. Cross-check
BAT_VOL_REGH = 0xC4,BAT_VOL_REGL = 0xC5againstinclude/linux/mfd/rk808.hin mainline, and that VCALIB0/VCALIB1 reads return non-zero values around 3000/4200. - Current sign wrong (positive while discharging): the 12-bit
sign-extension at bit 11 (
if (val & 0x800) val -= 4096) is missing or inverted. Linux uses the same bit-11 test. soc_pctstuck at the same value for >10 minutes of bench charge: the chip’s coulomb counter wasn’t primed at boot. The driver only primes (GASCNT_CAL_REG3..0) when the DTmonitored-batterynode has a non-zerocharge-full-design-microamp-hours— confirm withbootverboselogs that the design capacity was read. If still stuck, Linux also writesRELAX_VOL1/2thresholds; we do not yet, because the gauge appears to learn well enough without that on the PPP. Add those writes ifsoc_pctconsistently disagrees withvoltage_mvextrapolation.remaining_mahis exactly 0 or wildly off (e.g. > 10000): the res_div is wrong. PPP uses a 10 mΩ shunt → res_div = 2 (Linux convention inverted from naïve reading;SAMPLE_RES_DIV2in Linux corresponds to the 10 mΩ case). If readings are 2× too small, changebattery_info.res_divto 1; if 2× too large, leave at 2 and check raw register values.full_mahis 0: either NEW_FCC has never been written (chip resets it to 0 on a hard battery yank) or the addresses are wrong. Linux falls back topdata->design_capacityin that case; we currently do not.
Related
- Why the rk818 says “discharging” with PD plugged in — the war story behind
8da75bark818: clear OTG/SWITCH2 in DCDC_EN — battery now charges from PD . - fusb302: a USB-PD sink that works — PD contract feeds the RK818 input limit.
- Cross-driver audit — fusb302 + rk818 section.
- USB-C / PD verification — recipe to confirm PD + charger state.
- Component: FUSB302.