The night started on the USB-PD CC1 hang from the previous handoff and ended with a fresh rk_power_domain driver and a recalibrated rk_tsadc. Nine driver-side commits, one rolled-back experiment, four partial → working promotions in the components matrix, and one one-line rc.conf fix.
USB-PD on CC1 finally negotiates 9 V / 3 A. The handoff from earlier in the day had 036fecc fusb302: keep sink Rd on both CC pins deployed but PD was still failing because the RK818 PMIC IRQ enforcement path was re-arming SWITCH2_EN immediately after fusb302(4) cleared it via prepare_typec_sink. 544bd86 rk818: preserve prepared Type-C sink path added a rk818_typec_sink_prepared flag so the IRQ-driven enforce path uses the wider mask (OTG | SWITCH2) once prepare has run, locking DCDC_EN at 0x3f for the rest of the boot. Live capture from CC1 attach:
rk818_pmu0: rk818[typec-sink]: prepared Type-C sink path (DCDC_EN 0x7f->0x3f)
rk818_pmu0: rk818[irq]: enforced sink mode (mask=0xc0, DCDC_EN 0xff->0x3f)
fusb3020: negotiated PD Rev3 (partner=3, SW1=0x45 C3=0x05)
fusb3020: Source_Capabilities: 4 PDOs (5 V/2.4 A, 9 V/3 A, 15 V/3 A, 20 V/2.25 A)
fusb3020: Sending Request for PDO 2 (RDO=0x2104b12c)
fusb3020: TX_SUCCESS in 7 ms
fusb3020: PD negotiated: 9000 mV / 3000 mA
rk818_pmu0: rk818: USB input current limit set to 3000 mA (idx 11, USB_CTRL=0xcb)Both CC orientations now negotiate. Essay 16 grew a second WarStory (“CC1 silence and the prepared-sink contract”) covering the full bring-up arc — power-supply coordination layer, PMIC IRQ migration, sink-prepare contract, and the IRQ enforce mask preservation.
Ran mise run bench:phone --with-actuators to see what survived the night’s deploys. The results split cleanly:
| Step | Status | Note |
|---|---|---|
vibrator-rumble | ● working | new gpio_vibrator(4) path; you felt the 5 s rumble |
sensors | ● working | post-fixes; see below |
sgm3140-status + -pulse | ● working | three 250 ms strobes verified by eye |
modem-status | ● working | after mise run modem:power:phone |
spi-flash | ○ blocked | rk_spi RDID skew, see below |
| WiFi fallback matrix | ◐ partial | known SDIO IRQ wedge at 8 MiB host→phone |
Three concrete fixes landed from this pass:
- sgm3140 overlay didn’t reach
/dev/led/flash//dev/led/torchbecausesys/modules/dtb/rockchip/Makefilenever listedsgm3140.dtsoin itsDTSO=set.b7ec39bdtb: build sgm3140 overlay as part of the rockchip dtb modules patches the Makefile sobuildkernelemitssgm3140.dtbo. Phone’s/boot/loader.conffdt_overlaysline was extended frombcm-hostwaketobcm-hostwake,sgm3140. After redeploy:/dev/led/flash,/dev/led/torch, and the bench’s flash + torch + strobe pulses all work end to end. - bwfm runtime knobs were missing because
mise run build-kerneldoesn’t rebuild.komodules andbwfm_sdio.kowas stale.mise run build-module bwfm_sdio+ manual deploy gave usdev.bwfm_sdio.0.poll_fallback_hzandirq_watchdog_hzlive. The fallback matrix can now run; it does run; it shows the same “host SDIO IRQ stays flat under sustained transfer” pattern that essay 15 already calls out. - The phone was running an Apr-22 dtb. The compass node, the
&tsadc { status = "okay" }re-enable, and several other recent dts changes only existed in the May-02 build. Deploying the fresh dtb unblocked rk_tsadc attach and letmagnetometer(4)even reachdevice_attach.
The AF8133J ack’d over i2c only after deasserting its active-low reset GPIO (<&gpio1 RK_PA1 GPIO_ACTIVE_LOW>) — verified by hand with gpioctl before writing any driver code. The driver had no reset handling at all.
31c69f6 magnetometer: deassert reset-gpio before first I2C read added the gpio_pin_get_by_ofw_property("reset-gpios") lookup + a 10 ms low/high pulse, but the polarity was backwards: gpio_pin_set_active(true) drives an ACTIVE_LOW pin low (asserted = in reset). My initial pulse ended in the asserted state, so the chip stayed held in reset. 3b6cb7b magnetometer: fix reset-gpio polarity in attach pulse swapped the calls. After:
magnetometer0: af8133j detected (reg[0x01]=0x00 reg[0x00]=0x5e)
$ sysctl dev.magnetometer.0
dev.magnetometer.0.x_raw: -1553
dev.magnetometer.0.y_raw: -211
dev.magnetometer.0.z_raw: 992
dev.magnetometer.0.chip_variant: af8133jPromoted to ● working in the components matrix.
The TSADC reads about 30 °C low at idle on the PinePhone Pro (raw code ~466 maps to −3 °C via the rk3399_code_table, but the chip is clearly warmer). The original driver had a TODO to “wire a tsadc-trim cell from rk_efuse”; investigation tonight confirmed Linux’s RK3399 thermal driver applies no per-chip trim and the standard RK3399 efuse binding has no such cell. So we made the offset a writable + tunable knob instead.
7613729 rk_tsadc: make trim_offset_mc writable + tunable changed dev.rk_tsadc.0.trim_offset_mc from CTLFLAG_RD to CTLFLAG_RW and added a TUNABLE_INT_FETCH("hw.rk_tsadc.trim_offset_mc") so the loader can set it. Phone’s /boot/loader.conf now has hw.rk_tsadc.trim_offset_mc=-30000. Idle temps:
$ sysctl dev.rk_tsadc.0.cpu_temp dev.rk_tsadc.0.gpu_temp
dev.rk_tsadc.0.cpu_temp: 33750 # 33.75 °C, was 5.55 °C uncalibrated
dev.rk_tsadc.0.gpu_temp: 29444 # 29.44 °C, was 0.62 °C uncalibratedBoth looked sensible at the time. Later correction, 2026-05-05:
the root cause was not trim. Linux’s RK3399 path reports
1024 - tsadc_q; our driver had been interpreting the native q value
directly. ede7969 rk_tsadc: derive RK3399 q-select code in software fixed that transform in software, and
the phone now runs with trim_offset_mc=0. Keep this entry as the false
trail that led to the right comparison.
Audited the matrix against tonight’s evidence and promoted four more rows. Each had a working driver doing the kernel-side job; “partial” was actually about userspace policy that lives outside the driver. Fresh notes on each per-component page spell out what specifically remains for nice integration:
- Vibrator — driver works; needs a
feedbackd-shaped daemon to map semantic events (“notification”, “ringtone”) toFF_RUMBLEparameters. - MPU-6500 — driver works with IRQ-driven cache (
irq_count=7516089after 18 min uptime); the userlandphone-orientationsysctl poller ships and drives sway/Hyprland rotation. In-kernelEV_ABSwould be cleaner long-term. - STK3311 — driver works with thresholded near/far IRQ; userland
phone-proximityscript ships (commented-out in compositor configs). Needs ALS curve fit for backlight scaling. - RK3399 eFuse — driver reads all five leakage cells + cpu_id + wafer_info live. The TSADC trim consumer turned out not to exist; CPU/GPU DVFS would be the next plausible consumer if FreeBSD ever grows Rockchip cpufreq glue.
Matrix totals after the sweep: ● working 32 / ◐ partial 7 / ○ blocked 0 / · not started 6.
Also softened essays 14 and 15 + component-rk-vop so the modeset-lock and GPU-stress wedges are flagged as unconfirmed on #141+ rather than asserted as known-broken — neither has reproduced in casual use since the rk_vop follow-up arc landed ( 21b88a8 rk_vop: ack only fired interrupts; restore wait_for_vblanks , 36d4794 rk_vop: latch page-flip events until FS_INTR , 3f72b0e rk_vop: roll back unproven vblank latch ).
Investigated why mx25l(4) reads JEDEC RDID as 25 70 18 instead of GD25LQ128’s expected c8 60 18. The capacity byte (0x18) is correct; the leading two bytes are wrong but stable across reboots and clocks. Eliminated:
- Clock speed (10 MHz vs 1 MHz: same bytes)
- CPHA / clock phase (mode 0 vs mode 1: same bytes, with
spibuscorrectly reportingmode 1) - Stale
mx25lJEDEC table (gd25lq128is already in our patch as0xc8 0x6018)
Diff against Linux’s drivers/spi/spi-rockchip.c showed FreeBSD’s rk_spi never writes CTRLR1 (the per-transfer RX frame count register). Linux writes CTRLR1 = (frame_count - 1) before every transfer. CTRLR1 left at default 0 means “expect 1 RX frame”, which fits the symptom: only the last RX byte arrives intact.
f0768d4 rk_spi: write CTRLR1 frame count before each transfer tried adding the CTRLR1 write inside rk_spi_xfer_buf bracketed by rk_spi_enable_chip(0) / rk_spi_enable_chip(1). Patch built, deployed — and the kernel hung silently after the loader’s “Autoboot in 1 seconds” menu (CRU hack means kernel boot output never reaches serial, only the EFI framebuffer). Reverted in 18eeb81 Revert "rk_spi: write CTRLR1 frame count before each transfer" .
Likely failure mode: enable_chip(0) mid-transfer, after set_cs(true) had already been called, left the controller in a bad state. Linux’s order is disable → write CTRLR0 + CTRLR1 + BAUDR → enable → assert CS → fill TX FIFO; our wrapper-style insertion happened between “assert CS” and “fill TX FIFO”. The corrected next-session plan is to refactor rk_spi_transfer to do the CTRLR1 write before enable_chip + CS-assert, matching Linux’s order. The known-bad kernel is preserved as /boot/kernel/kernel.bad-rk_spi on the SD for forensics.
GD25LQ128 stays ◐ partial until that refactor lands.
First-cut port of drivers/pmdomain/rockchip/pm-domains.c for the RK3399. FreeBSD has no power-domain framework at all — no pwr_domain_if.m, no consumer parsing in simplebus, no peer driver expects power-domains = <&power N> to do anything. Rather than build the whole framework tonight, the new driver takes a sysctl-first shape: it implements the actual register sequencing (bus-idle handshake before power-off, power-on before idle release, mirroring Linux’s rockchip_pmu_set_idle_request() + rockchip_do_pmu_set_power_domain()) and exposes every one of the 27 RK3399 domains under dev.rk_power_domain.0.<name>.{power,idle}_state (RW) plus _status (read-only mirror).
Verified live with a safe round-trip on the RGA domain (no driver, no consumer, has full pwr+idle handshake):
$ sysctl dev.rk_power_domain.0.rga.power_state_status
dev.rk_power_domain.0.rga.power_state_status: 1 # boot state: on
$ sudo sysctl dev.rk_power_domain.0.rga.power_state=0
dev.rk_power_domain.0.rga.power_state: 1 -> 0
$ sysctl dev.rk_power_domain.0.rga.{power,idle}_state_status
dev.rk_power_domain.0.rga.power_state_status: 0 # off, idle latched
dev.rk_power_domain.0.rga.idle_state_status: 1
$ sudo sysctl dev.rk_power_domain.0.rga.power_state=1
dev.rk_power_domain.0.rga.power_state: 0 -> 1
$ sysctl dev.rk_power_domain.0.rga.{power,idle}_state_status
dev.rk_power_domain.0.rga.power_state_status: 1 # back on, un-idled
dev.rk_power_domain.0.rga.idle_state_status: 0All 27 domains start pwr=1, idle=0 — U-Boot really does leave everything on.
The driver is in src/sys/arm64/rockchip/rk_power_domain.c ( 227e58b rk_power_domain: sysctl-only RK3399 PMU power-domain controller + Makefile fixups 42dbab2 files.arm64: fix hunk count after rk_power_domain addition + 84175f0 rk_power_domain: use OF_decode_addr instead of fdtbus_bs_tag ). Promoted from · not started to ● working in the matrix; the per-component page spells out the four-step plan to plug it into a real framework so peer drivers can request domains via DT.
WiFi DHCP race fix: the previous handoff and tonight both hit a state where wlan0 associated to the AP (UP, RUNNING, ssid Ether, bssid …, AES-CCM ucast) but had no inet line. Root cause is the ifconfig_wlan0="WPA SYNCDHCP" + background_dhclient_wlan0="NO" combo: bwfm SDIO completes WPA association asynchronously, after rc.d/bwfm_sdio has created the wlan0 interface, and SYNCDHCP times out (~30 s) before WPA finishes.
4e6ffe4 overlay/rc.conf: switch wlan0 to background DHCP switches overlay/etc/rc.conf to WPA DHCP + background_dhclient_wlan0="YES". Live tonight. Boot doesn’t wait on DHCP; the lease lands a few seconds after login. ue0’s static inet stays SYNCDHCP-style (it boots ready synchronously).
Remaining gaps — prioritized
What’s left, ranked by bang-for-buck given where we are now:
Near-term, bounded (1–2 sessions each)
rk_spiCTRLR1 refactor. Move theCTRLR1 = len-1write to beforeenable_chip+ CS-assert inrk_spi_transfer, matching Linux’s order. Should fix the JEDEC RDID skew and unblock/dev/flash0. Then rewritetools/debug-spi-flash-phone.shto useddinstead of the nonexistentflashctl(8). Detail inproject_spi_flash_rdid_skew.mdmemory note.- Re-verify modeset-lock + GPU-stress on
#144. Runscripts/wedge-reproand either close the WarStory in essay 8 or capture a current trace. Site already says “needs re-verification” — actually doing it gets us out of “unconfirmed” purgatory.
Medium scope (2–4 sessions)
- WiFi SDIO IRQ enablement. Essay 15 has the full plan: track
sdio_intr_enabledin DWMMC softc, clearCLKENA_LPwhile SDIO IRQ is armed, issue update-clock command afterCLKENAchange. The transfer matrix is the falsifier. Highest user-facing impact of anything left; current state is “associates and gets a lease, butbwfm_irq=0for the entire transfer and 8 MiB host→phone wedges.” - Power-domain framework. Define
pwr_domain_if.m(modeled onclk_if.m/hwreset_if.m), teachsimplebusto parsepower-domains = <&power N>, add request-counting, then have tonight’srk_power_domainbecome a real consumer. Reusable across the entire Rockchip family. Lays the groundwork for… - Suspend-to-RAM. Needs the framework above + CPU cluster power-down + DDR self-refresh + RK818 PMIC suspend. Real workstream but starts unlocking actual phone-as-phone use.
Bigger / interesting
- Rockchip IOMMU. Needs an ARM platform IOMMU framework first (FreeBSD has none —
iommu(9)today is x86 + POWER). Multi-session, real architectural work, reusable upstream. - USB-C DisplayPort alt-mode. VDM (DiscoverIdentity / DiscoverSVIDs / EnterMode for SVID
0xff01) infusb302(4)+tcphy0_dpwired into a DRM bridge. Linux drives DP-Alt out of the PPP’s USB-C port today — silicon supports it, FreeBSD just doesn’t. Multi-session. - OTG / host mode. Re-implement source-role state machine in
fusb302(4)(was scaffolded ine0ad0adfusb302 + rk818: source-role / OTG path scaffold , rolled back in6cb29abusb-c: roll back post-wednesday role scaffold ) + DWC3 device→host mode-switch. Blocked on FreeBSD’s lack ofextconframework; Linux uses extcon notifications to drive the DWC3 mode swap.
Userspace polish (small but proves the kernel work)
- MPU-6500 in-kernel
EV_ABSemit instead of the userland sysctl poller; lets compositors react without a 4 Hz polling loop. - STK3311 ALS curve for
backlight(8)scaling against ambient light. - Vibrator
feedbackd-analogue daemon mapping semantic events toFF_RUMBLEeffect parameters. - eFuse
kern.cpu_iduserland exposure sodmidecode-style tools see the chip serial without root MMIO access.
Done as of this session — for the record
- USB-PD on CC1 (
544bd86rk818: preserve prepared Type-C sink path , full arc in essay 16’s second WarStory) sgm3140overlay actually built + loaded (b7ec39bdtb: build sgm3140 overlay as part of the rockchip dtb modules )bwfm_sdio.korebuilt with the new sysctls- Base dtb refresh (compass + tsadc + others)
magnetometerreset-gpio + polarity (31c69f6magnetometer: deassert reset-gpio before first I2C read +3b6cb7bmagnetometer: fix reset-gpio polarity in attach pulse )rk_tsadctrim workaround (7613729rk_tsadc: make trim_offset_mc writable + tunable , later superseded by the 2026-05-05 q-select fix)- Components matrix sweep: vibrator + MPU-6500 + STK3311 + eFuse promoted (
b0b8400site: promote 4 fully-working drivers, document userspace gaps ) rk_power_domainsysctl-only first cut (227e58brk_power_domain: sysctl-only RK3399 PMU power-domain controller + fixups)- WiFi DHCP race fix (
4e6ffe4overlay/rc.conf: switch wlan0 to background DHCP ) - Site cleanup of stale modeset-lock + GPU-stress claims (
30c0220site: mark modeset-lock + GPU-stress wedges as unconfirmed on #141 ) - Site DP-Alt claim corrected (
08f4629site: correct essay 16 DP-Alt claim — silicon supports it, FreeBSD just doesn't yet )