Identity
| Part | RK3399 eFuse block |
| Role | Read-only NVRAM exposing CPU ID, A72/A53 leakage calibration, TSADC trim values, and (optionally) secure-boot key hashes |
| Bus / address | MMIO 0xff690000 (window 0x80) |
| GPIO / IRQ | none |
| Datasheet | RK3399 TRM Part 1 chapter 22 (eFuse) |
| Pine64 wiki | PinePhone Pro main page |
| Schematic | on-SoC (no external schematic) |
Status — ● working
A minimal rk_efuse driver is in tree at
src/sys/arm64/rockchip/rk_efuse.c. It matches
rockchip,rk3399-efuse, allocates the MMIO window, runs the
RK3399 strobe sequence (LOAD | PGENB | STROBSFTSEL | RSB, then
per-word STROBE with the byte address shifted into bits
[25:16]), caches the first 128 bytes of fuse contents, and
exposes the useful cells via sysctl:
dev.rk_efuse.0.cpu_id— 16-byte hex string (offset 0x07, len 0x10)dev.rk_efuse.0.cpub_leakage— A72 cluster leakage class (0x17)dev.rk_efuse.0.cpul_leakage— A53 cluster leakage class (0x1a)dev.rk_efuse.0.gpu_leakage— Mali-T860 leakage class (0x18)dev.rk_efuse.0.center_leakage— center power-domain class (0x19)dev.rk_efuse.0.logic_leakage— logic power-domain class (0x1b)dev.rk_efuse.0.wafer_info— wafer info byte (0x1c)
Verified live on 2026-05-03: this PinePhone Pro returns
cpu_id=5442524d37322e303000000000061000, cpub_leakage=29,
cpul_leakage=17, gpu_leakage=23, center_leakage=27,
logic_leakage=21, wafer_info=8. All cells read non-zero, the
strobe sequence works, the cached buffer survives.
What remains for userspace integration
The driver does what it’s supposed to do; the question is what other drivers should consume the values it exposes:
- TSADC trim was the obvious candidate. After diving in
2026-05-03 we confirmed RK3399 has no eFuse-resident TSADC trim
cell — Linux’s RK3399 thermal driver applies no trim either.
The later 2026-05-05 fix showed the real TSADC problem was the missing
1024 - tsadc_qtransform, not a trim cell. The manualhw.rk_tsadc.trim_offset_mcknob remains available, but the phone now runs with trim0(see component-rk-tsadc). This integration is closed, not pending. - CPU/GPU DVFS is the next plausible consumer: leakage cells
feed per-die voltage tables in Linux’s
rockchip_cpufreq. FreeBSD has nocpufreq(4)Rockchip glue today, so the cells are exposed but unused. - Userland exposure as
kern.cpu_idwould letdmidecode-style tools and asset trackers see the chip serial without root-only MMIO access. Tiny, hasn’t been written.
Driver
- Our tree:
src/sys/arm64/rockchip/rk_efuse.c. Wired intofiles.arm64.patchasoptional fdt rk_efuse clkand added to the PINEPHONE_PRO kernel config. - Linux mainline:
drivers/nvmem/rockchip-efuse.c— short reference (~300 lines). - FreeBSD upstream: none — there is no
nvmemframework, so this driver only exposes sysctls. A futurenvmem-shaped consumer interface would be a natural follow-up if/when TSADC, cpufreq, and the secure-boot stack all want to read.
The Linux driver is a textbook nvmem provider: read fuses, hand out cells via the nvmem framework. The FreeBSD port reads the whole 128-byte window into a softc-resident buffer at attach and serves cell-shaped sysctls, which is enough for kernel-side and userland calibration consumers.
Parity verification
A working attach should print on dmesg:
rk_efuse0: <Rockchip RK3399 eFuse> mem 0xff690000-0xff69007f on simplebus0
rk_efuse0: fuses loaded (cpub=N cpul=N gpu=N wafer=N)
…with small (typically 0–31) integers in place of the Ns.
Then from userland:
# sysctl dev.rk_efuse.0.cpu_id
dev.rk_efuse.0.cpu_id: 1f7c4b... (16 hex bytes, 32 chars total)
# sysctl dev.rk_efuse.0
dev.rk_efuse.0.wafer_info: 12
dev.rk_efuse.0.logic_leakage: 7
dev.rk_efuse.0.center_leakage: 6
dev.rk_efuse.0.gpu_leakage: 9
dev.rk_efuse.0.cpul_leakage: 5
dev.rk_efuse.0.cpub_leakage: 8
dev.rk_efuse.0.cpu_id: ...
mise run debug:sensors:phone includes the same eFuse fields in its
combined PinePhone Pro sensor receipt and checks that cpu_id is a
32-character hex string.
Cross-check the same fields from a Linux PinePhone Pro image:
# /sys/bus/nvmem/devices/rockchip-efuse0/nvmem | xxd | head
00000000: ... ... ... ...
The byte at offset 0x07 (and the 16 bytes following) should
match dev.rk_efuse.0.cpu_id. Bytes 0x17–0x1c should match the
leakage / wafer-info sysctls.
Falsifiers
- All-zero or all-0xff readout → eFuse wasn’t loaded. Either
pclk_efuse(PCLK_EFUSE1024NS) wasn’t enabled, or the RSB/PGENB init wasn’t applied before the strobe loop. Check thatclk_get_by_ofw_name(dev, 0, "pclk_efuse", ...)succeeded and the gate is open in U-Boot’s CRU dump. - Readout varies across reboots on the same chip → we’re indexing the wrong register block (eFuse contents are write-once at fab and immutable). Suspect endianness or the address-shift math on the strobe word.
- Readout matches between two physically distinct PPP boards → strobe sequence is wrong and we’re reading a constant-pattern register. Confirm
RK3399_LOAD | RK3399_PGENB | RK3399_STROBSFTSEL | RK3399_RSBis the initial CTRL value, thenSTROBE | (addr << 16)is OR-ed in per word.
Open work
- Wire
cpub_leakage/cpul_leakageinto future Rockchip DVFS policy if FreeBSD grows per-die voltage curves. - If/when a second consumer arrives (secure boot, OTP key store), revisit the sysctl-only shape and consider a minimal
nvmem-style framework so consumers don’t all reach intodev.rk_efuse.0.*by name.
Related
- Cross-driver audit 2026-04-30 — Rockchip driver gaps.
- What’s next — quick-wins backlog.