14 · status

Where we are now

Subsystem-by-subsystem: what's working, what's flaky, what's blocked. A snapshot, not a roadmap.

The first thirteen essays are war stories. This one is the table of contents to those wars, in the present tense. If you opened the project today and asked “what’s actually running,” this is the answer. Most of it links back to the essay where it was earned; small post-essay status deltas are called out inline.

gruvbox + on-screen keyboard
The PinePhone Pro running sway with the omfreebdy gruvbox theme. wvkbd-mobintl pinned at the bottom; swaybar with workspace + clock at the top. Captured live via `tools/phone-shots.sh` over USB-Ethernet. wvkbd-mobintl is themed by omfreebdy via theme-set-wvkbd — its keys recolor whenever the theme changes.

▸ phone · desktop · 2026-04-22

Overall

Working: boot, build pipeline, USB networking, DWC3 gadget, touchscreen, MIPI DSI display, GPU-accelerated Sway, OPP-aware Panfrost DVFS, visible glxgears around 1448 FPS in auto mode with clean close, WPA2 WiFi association + DHCP with SDIO running at 50 MHz, full Bluetooth pairing + AES-CCM encryption, A2DP audio out to a speaker, on-device audio out the loudspeaker (RT5640 + I2S0), volume buttons, power-button screen blanking in Sway, and USB-PD r3.0 sink negotiation (9 V / 3 A) actually charging the battery through the rk818.

Partial / flaky: WiFi long-idle and broader AP/auth coverage (the SDIO IRQ path, SDIO run clock, DHCP boot path, and multi-megabyte transfers now have receipts), Hyprland/WebGL/GPU stress coverage (glxgears and short glmark2 work on Panfrost; Firefox/glxtest can still leave Sway unkillable in a Panfrost BO wait, and the separate DRM modeset-lock wedge remains open), RT5640 earpiece/headset/speakerphone routing (loudspeaker playback and internal DMIC capture/playback work, but headset, HP detect, earpiece, and call routing are not done), modem voice/data (EG25-G enumerates and answers AT; SIM/data path not done), Bluetooth orchestration script (fragile, sleep-and-grep based), display first-frame flicker on resume, and thermal hardening (TSADC readout, soft GPU/CPU caps, and guarded code-domain comparator IRQs work with hardware QSEL; kernel-priority cpufreq is backed out and hardware TSHUT remains disarmed).

CPU frequency scaling: as of 13e94af rk_cru: per-PLL whitelist for RK3399 PLL register window the rk_cru per-PLL whitelist allows LPLL/BPLL writes through, so cpufreq_dt(4) works on both clusters and powerd -a adaptive is on by default. Display path PLLs (VPLL, CPLL) and DPLL stay frozen.

Blocked: no tier-two subsystem is blocked on unknown hardware behavior right now.

Not started: cameras, suspend/resume, and the low-leverage SoC accelerators that need larger framework ports first (RGA, crypto, Rockchip IOMMU, power domains). Magnetometer has crossed into partial: the driver can identify the populated chip and now has polled raw-axis sysctls awaiting bench truth.

By subsystem

Boot — ● working

Honeyguide image plus Megi’s U-Boot 2024.04 plus rk2aw on SPI, SD-first boot. Graphical boot menu on the EFI framebuffer, serial console at 1500000 baud on the headphone jack. From cold power to login prompt: about 30 seconds.

See essay 02 — Boot from SD.

Build pipeline — ● working

Cross-build aarch64 kernels on honor (FreeBSD 15.0-RELEASE amd64). Honeyguide overlay + our src/ overlay + patches/ directory layered by mise run patch. Full buildkernel ≈ 5 minutes; incremental ≈ 18 seconds. Must use stable/15 + clang (not main, not gcc14).

See essay 03 — Building from source.

USB networking — ● working

if_cdce + usb_template_cdce configures the phone as a CDC-Ethernet device on the OTG port. Plug into a laptop, get an IP, SSH in. TX watchdog auto-recovery handles transient stalls. This is the bring-up lifeline — every kernel since has been deployed by ssh honor 'cat kernel' | ssh pinephone 'sudo tee /boot/kernel/kernel'.

See essay 04 — USB-Ethernet up.

DWC3 gadget — ● working

The Synopsys DWC3 USB controller in gadget mode, written largely from the chip programming guide because the in-tree FreeBSD driver was host-mode only. Inline EP0 handling (the usb_template callback timing made the kthread-deferred path too slow for control transfers).

See essay 05 — DWC3 gadget from scratch.

Touchscreen — ● working

Goodix GT917S on DTS &i2c3, 720×1440, 5-touch. Initially polling; now interrupt-driven, multitouch in X11 and Wayland. See essay 06.

Display — ● working

Full MIPI DSI pipeline: VOP → DW MIPI DSI → HX8394 panel, 720×1440 at 60 Hz. EFI framebuffer for boot, then DRM/KMS. X11 / Sway / Hyprland all run. Sway launches under dbus-run-session so mako has a session bus ( 5e67a6d sway: launch under dbus-run-session ); the swaybar status command is now a small i3bar JSON emitter for battery + clock ( 86c8e52 swaybar: status_command emits battery+clock i3bar JSON ). Hyprland 0.54 deprecated windowrulev2; the on-phone overlay has been migrated to the v3 block syntax ( 3d52952 hypr: migrate to 0.54 v3 windowrule syntax + phone.conf overlay ). Residual: occasional first-frame tearing on resume, intermittent green flash on mode change. See essay 07.

GPU — ◐ partial

Mali-T860 MP4 via Panfrost. Sway is no longer forced to pixman; glxinfo reports Mali-T860 (Panfrost), direct rendering, OpenGL ES 3.1, and Mesa 24.1.7. Kernel #77 from be91afa Refcount Panfrost file lifetime fixed the close-path panic by refcounting the per-file Panfrost/MMU lifetime. On 2026-05-05, a visible Sway/Xwayland run measured vblank_mode=0 glxgears -info at 1339 FPS average. Later that day kernel #175 completed a short full glmark2 --size 320x480 -b :duration=1 pass with score 882 and a 420x760 default pass with score 609. The longer run entered warm thermal policy but did not wedge: timeouts=0, resets=0, active_jobs=0 after exit, and cooldown restored GPU max-auto while CPU frequency returned to adaptive policy. The GPU clock path is OPP-aware: 59bde6a panfrost: add OPP-aware GPU DVFS added regulator-backed OPP transitions, d3b80a7 panfrost: avoid active-job DVFS transitions avoided active-job DVFS transitions, and 3bfc1d0 panfrost: avoid max clock for light auto work stopped light compositor submissions from forcing max-auto. Kernel #172 idles mostly at OPP1 (297 MHz / 825 mV) with occasional OPP0 (200 MHz / 825 mV) and ramps to OPP4 (594 MHz / 925 mV) under sustained GL load; visible Sway glxgears measured 1468 FPS in performance mode and 1448 FPS in auto mode with no panfrost faults. Normal Sway compositing is reliable once the patched FreeBSD-platform libdrm is installed; an unpatched package makes Mesa fail device enumeration and Sway fails before renderer creation. The new browser/WebGL warning is Firefox’s glxtest: on 2026-05-06 it left Sway unkillable in panfrost_ioctl_wait_bodma_resv_wait_timeout_rcu, survived SIGKILL, and blocked clean reboot after sshd stopped. The older DRM modeset-lock wedge originally captured under Hyprland load is still reproducible on #145: kldunload bwfm_sdio && kldload bwfm_sdio over ssh on 2026-05-04 produced the identical signature — drm_modeset_lock.c:268 + drm_atomic_helper.c:617/667/892 warnings, hw_done/flip_done timeouts on every plane/CRTC/connector — and took USB-Ethernet down with it. The trigger isn’t Hyprland-specific; it’s anything that yanks a busy DRM-adjacent code path while pages are in flight. See essay 08 and GPU debugging.

Bluetooth — firmware load — ● working

BCM4345C5 in the AzureWave AP6255 module, HCI over UART2 at 1500000 baud with hardware flow control. From-scratch netgraph H4 framing node (ng_h4frame), patchram uploader, UART RX trigger fix. See essay 09.

Bluetooth — HOST_WAKE — ◐ partial

GPIO HOST_WAKE armed as a real interrupt; pairing flows reliable since. The optional ng_h4frame tickler is still on by default — empirically redundant but not proven safe to disable. See essay 10.

Bluetooth — SSP and Murata — ● working

SSP with Just Works, AES-CCM confirmed (Encryption_Change enabled=0x02). Required swapping the BCM4345C0 patchram from the SC-disabled Raspberry Pi build to a Murata 1MW build. The blob is committed but the build pipeline doesn’t refuse the wrong build. See essay 11.

Bluetooth — A2DP — ● working

mpg123 → /dev/dsp → virtual_oss → SBC → AVDTP → L2CAP → ACL → speaker. Two netgraph patches, one userland patch, one orchestration script. Fragile but plays. See essay 12.

On-device audio — ◐ partial

RT5640 over I2S0 plays through the loudspeaker. The final blocker was a TLV control polarity bug: RT5640_DAC1_DIG_VOL = 0x0000 is -65.62 dB, not 0 dB. Three weeks of correct fixes (codec analog rails always-on, HPO_MIXER unmute, full Linux-mainline HP power-on sequence, OUT_L3/R3_MIXER addressing, SPO routing) were all real and necessary, all hidden behind one upstream attenuation. Loudspeaker is the HPO path through an external simple-audio-amplifier on PB3; the SPO path is wired to the earpiece. The simple_amplifier(4) driver now asserts the enable GPIO on PCMTRIG_START and drops it on _STOP ( 3b23d2f audio: drive simple amplifier enable GPIO + be6f2f1 audio: include GPIO definitions for simple amp ), so speakers come up on every boot rather than relying on a drifted-high GPIO. cat /tmp/sine.raw > /dev/dsp0 produces audible sound. See essay 13.

Internal microphone capture now has a confirmed bench receipt. The live phone exposes pcm0: <rt5640-sound> (play/rec), and kernel #182 fixed the first teardown class by stopping RK I2S RX before RT5640 ADC power-down: a 1 MiB capture closes cleanly and leaves I2S_XFER=0. Kernels #183 through #186 falsified the obvious RT5640-only suspects: Linux’s GP2 DMIC clock mux, IN1P-vs-GPIO3 data pin selection, divider index 4, dmic_edge=3, postmarketOS’ ADC Capture Volume = 80, and DMIC/ADC/I2S ASRC were not sufficient while FreeBSD still started only RX. Kernel #187 matched Linux’s Rockchip I2S trigger rule by starting TX and RX together for capture; kernel #188 removed sleeping RT5640 I2C from the PCM trigger lock; kernel #189 moved FIFO clears to the final STOP path so playback, capture, and a full-duplex monitor all close cleanly. The bench catch: PinePhone Pro privacy switch #3 gates the onboard microphones, and with it off the DMIC path returns floating/static-looking samples. With that switch enabled, the first intelligible internal-mic setting was dmic_clk_div=3, dmic_edge=3, adc_dig_volume=32, producing DMIC=0xb860 and ADC_DIG_VOL=0x2020. Kernel #190 rebooted with that default, captured 962,560 bytes from /dev/dsp0, played it back through the loudspeaker, and returned with I2S_XFER=0, I2S_CLR=0, and both FIFO clear-timeout counters at zero. Headset mic, earpiece, headphone-jack detect, and modem call routing are still open. Browser media exposed a separate userland routing trap on 2026-05-06: a YouTube run left PulseAudio’s OSS thread pegged while Sway and Panfrost were still responsive. The live image now prevents PulseAudio autospawn and asks Firefox’s cubeb layer for OSS directly; the follow-up run avoided PulseAudio and kept Panfrost clean, but saturated Firefox/media CPU hard enough to heat the phone.

WiFi — ◐ partial

Native bwfm(4) driver ported from OpenBSD. SDIO bus methods work, chip enumeration works, firmware downloads and verifies, and the driver uses the PPP-specific brcmfmac43455-sdio.pine64,pinephone-pro.txt NVRAM on hardware. The two important late fixes were loading the Broadcom CLM blob (brcmfmac43455-sdio.clm_blob) and tagging synthetic scan frames with the firmware’s real RX channel before handing them to FreeBSD net80211.

That moved WiFi from “driver bring-up but no discovery” to real scan results on hardware. The next stack of fixes moved it again: firmware join events mark the synthetic net80211 node associated, bwfm(4) advertises 802.3 encapsulation for FullMAC data frames, and bwfm_connect() passes the selected RSN IE to firmware with the Broadcom wpaie iovar. 871c39d Mark firmware-associated bwfm nodes 066702e Use 802.3 transmit encapsulation for bwfm 2862f34 Pass WPA IE to bwfm firmware 9e5446a Gate bwfm diagnostics

On the phone, wpa_supplicant completes the WPA2 four-way handshake, installs PTK/GTK, and DHCP binds wlan0 to 192.168.1.142. Verified live on 2026-04-28:

wlan0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        ether <hidden>
        inet 192.168.1.142 netmask 0xffffff00 broadcast 192.168.1.255
        media: IEEE 802.11 Wireless Ethernet OFDM/54Mbps mode 11g
        status: associated
        ssid <hidden> channel 6 (2437 MHz 11g) bssid <hidden>
        country US authmode WPA2/802.11i privacy ON deftxkey 2
        AES-CCM 2:128-bit AES-CCM 3:128-bit txpower 30 bmiss 7 scanvalid 60
        wme roaming MANUAL
        parent interface: bwfm_sdio0

mise run wifi:boot:phone sets the normal rc.conf path (wlans_bwfm_sdio0=wlan0, ifconfig_wlan0="WPA SYNCDHCP"), removes the old USB defaultrouter so WiFi is the normal internet path, backs up /etc/rc.conf, waits for wpa_state=COMPLETED, and only starts DHCP manually if rc races the FullMAC association. A 2026-05-06 reboot after reinstalling that rc path came back with wlan0 at 192.168.1.142, the default route on wlan0, power_save=1, pm=2, mpc=1, and irqmode=irq-active-watchdog.

The eMMC migration on 2026-05-04 surfaced a latent bug in the bwfm_sdio_attach path: the IOR.fn2-ready poll was capped at 500 ms, but on a cold boot from eMMC the chip’s fn2 fabric routinely takes longer. Symptom: fn2 I/O ready timeout, IOR=0x02 — PROCEEDING anyway, then every later fn2 transaction failing. debug.bwfm_dump_state=1 showed clk=AVAIL fn2=0 mb=host=0x00040002DEVREADY=1 and SDPCM proto v4 in the upper word, meaning the firmware was actually up and on protocol; the host was just giving up on the function-2 enable handshake too early. 4f93cc4 bwfm: extend fn2-enable timeout to 5 s, log mbdata on timeout bumps the loop bound to 5 s (matching Linux’s brcmf_sdio_bus_init) and prints mailbox state on timeout. Post-fix, association + DHCP + ping-out all work on the first attempt from cold boot.

The next structural issue was throughput, not host IRQ delivery. On 2026-05-05, kernel #160 proved that Rockchip DWMMC slot 0 reports SDIO function interrupts on bit 24 (sdio_mask=0x01000000), not the generic DWMMC bit 16. c312250 dwmmc: use Rockchip SDIO interrupt bit switches the host mask, and 9703c32 bwfm: defer SDIO IRQ tasking during attach defers IRQ-driven bwfm_sdio task scheduling until the attach/deferred-init path is ready. The bounded 2 MiB transfer receipt at logs/wifi-transfer/20260505-095119-sdio-bit24-irq-active.log completed both directions with irqmode=irq-active-watchdog, bwfm_irq=201..515, task_irq=180..417, cmd53_err=0, rx_err=0, tx_err=0, and no packet errors.

That fixed the battery-hostile “100 Hz polling is the only way packets move” condition. 2458d80 bwfm: expose firmware power save control then made firmware power policy explicit: dev.bwfm_sdio.0.power_save=1 applies FAST_PS + MPC (pm=2 mpc=1), and setting it to 0 applies CAM + MPC-off (pm=0 mpc=0) for throughput tests.

Neither fix solved throughput because the SDIO card clock itself was still stuck at probe speed: hw.clock.clk_sdio.frequency=800000. cf62d1b dwmmc: raise RK3399 SDIO CIU clock for high-speed traffic made the RK3399 DWMMC clock update path keep sc->bus_hz in sync with the CIU clock, and 8c61d6d sdiob: enable SDIO run clock by default makes the SDIO bridge request a 25 MHz post-discovery run clock by default. On the 2026-05-06 post-reboot receipt, debug.sdiob_run_clock_hz=25000000, clk_sdio and clk_sdio_c both reported 50000000, debug.bwfm_state stayed irq-active-watchdog, and a 256 KiB host-to-phone HTTP fetch forced over wlan0 measured 2336 kBps with cmd53_err=0, rx_err=0, tx_err=0. The follow-up larger-transfer pass kept the same error profile: 8 MiB host-to-phone HTTP over wlan0 measured 1504 kBps, 32 MiB measured 2294 kBps, and an 8 MiB phone-to-host nc upload completed in 7.55s, all with cmd53_err=0, rx_err=0, tx_err=0, and rx_glom_errors=0.

Modem — ◐ partial

Quectel EG25-G now has a repeatable power path: mise run modem:power:phone asserts the 4G regulators, drives the PinePhone Pro reset/PWRKEY sequence, waits for USB enumeration, then captures modem status. The phone sees VID:PID 2c7c:0125, u3g0 creates /dev/cuaU0.0 through /dev/cuaU0.4, and /dev/cuaU0.2 plus /dev/cuaU0.3 answer AT/ATI with firmware EG25GGBR07A08M2G. Current blocker is higher-level: AT+CPIN? returns +CME ERROR: 10, so SIM/mechanical detection comes before cellular data.

USB-PD / charger — ● working

fusb302(4) negotiates PD r3.0 end-to-end on a fresh boot, and the rk818 actually charges the battery from the negotiated contract. Live on 2026-04-28 against a 4-PDO source: cable detected on CC1, Source_Capabilities decoded (5 V/2.4 A, 9 V/3 A, 15 V/3 A, 20 V/2.25 A), Request transmitted for PDO 2 (RDO 0x2104b12c), Select_Capability → Transition_Sink → Ready, dev.fusb302.0.negotiated_voltage_mv = 9000, negotiated_current_ma = 3000. The rk818 charger sets its input current limit to 3000 mA on the same transition ( ef6035e fusb302+rk818: apply PD-negotiated input current to the charger ); SUP_STS reads chg=CC-CV, usb_eff=1 and hw.acpi.battery.rate is positive (~+242 mA on the bench). What made the negotiation reliable across reboots was four changes: PD timers on callout(9) ( 12a63fe fusb302: PD timers via callout instead of state-age polling ), dynamic SPECREV defaulting to Rev3 with downgrade-on-Rev2 ( ee8daad fusb302: dynamic SPECREV, programmed MEASURE/POWER, rx_msgid tracking + a8b584d fusb302: default GoodCRC to Rev3 (downgrade only on Rev2 partner) ), IRQ-driven TX completion ( 045efa2 fusb302: event-driven TX completion + per-TX retry program + COLLISION unmask ), and a reattach sysctl that drops Rd for 200 ms to wake a stuck source ( 94b42f7 fusb302: re-detect orientation on BC_LVL drop, add reattach sysctl + ce438be fusb302: reattach now drops Rd for 200ms before re-init ). What made the rk818 actually commit to charging was clearing DCDC_EN[7] (OTG_EN) at init, so the chip’s own 5 V boost stops fighting the PD source on VBUS ( 8da75ba rk818: clear OTG/SWITCH2 in DCDC_EN — battery now charges from PD ); the later Linux parity cleanup decoupled unrelated SWITCH2_EN from that Type-C policy ( 4d190f3 rk818: decouple SWITCH2 from Type-C OTG ). See essay 16, appendix: rk818 not actually charging, and appendix: USB-C / PD verification.

Sensors — ◐ partial

Volume buttons now have a local Rockchip rk_saradc + rk_adc_keys path that polls saradc channel 1 and emits KEY_VOLUMEUP / KEY_VOLUMEDOWN on hardware. Live capture on the phone showed idle around 1.79 V, volume-up around 0-21 mV, and volume-down around 0.30-0.63 V, with the expected evdev keycodes.

The first I2C sensors are also attached now. stk3311(4) exposes proximity/light sysctls under dev.stk3311.0.*, and mpu6500(4) exposes raw accel/gyro/temperature sysctls under dev.mpu6500.0.*. On hardware, STK3311 reports chip ID 0x12, proximity raw samples around 78-80, and near=0 on the bench; MPU-6500 reports WHO_AM_I=0x70 with a plausible flat-device Z axis around -15700. 963fc6c Add PinePhone sensor and LED tasks

Still missing from the sensor cluster:

The side power button now toggles the active Sway output through omfreebdy’s screen-toggle, bound to XF86PowerOff after live libinput debug-events confirmed the GPIO key as KEY_POWER (116).

The notification LED nodes are present too (/dev/led/led-red, led-green, led-blue). The kernel already exposes them through gpioled; omfreebdy now has phone-led and a narrow root helper so userland can blink a color without handing a desktop menu full root access.

Camera — · not started

Sony IMX258 rear, OV8858 front. No drivers. Phase 4+.

PineTab2 (RK3566) — ◐ partial

Phase 3 has moved from pure research into first-boot implementation. The tree now imports the mainline PineTab2 v0.1/v2.0 DTS files, adds a FreeBSD first-boot wrapper, and carries a PINETAB2 kernel config. The wrapper now biases hard toward a quiet first boot: DSI/panel, cameras, audio, PCIe, GPU, and the internal BES2600 SDIO path are disabled until the board has proved serial, micro-HDMI, storage, USB, RK817, Goodix, SC7A20, SARADC buttons, and GPIO switch visibility.

What changed since the start of the year

Six months ago this project had a Honeyguide image that booted to a login prompt and USB-Ethernet that mostly worked. Today it has GPU-accelerated Hyprland on the panel, touch-driven Wayland, native WPA2 WiFi, A2DP music playback to a Bluetooth speaker, audible sound through the phone’s own loudspeaker, and a kernel tree we can rebuild and redeploy in under a minute. That’s tier-one (essay 1’s framing) and most of tier-two. Tier three — calls, SMS, ringtones routed through the in-codec path — is the next arc.

The honest balance: the things that work, work reasonably well. The things that don’t work, don’t work for understood reasons we haven’t fixed yet. There are no “we have no idea what’s going on” subsystems left. Everything else is a concrete next step away from green.