Hardware
Every subsystem, and exactly where it stands. Nothing here is padded.
Boot / kernel
WORKINGBoots to userspace on 7.1.0-ro-os-qcom-apq8064 via lk2nd + extlinux (DTB in r2). The decade-old Google-logo hang is solved — the stock FLO-04.08 ATAGS handoff never delivered a DTB, and lk2nd replaces it.
Access / shell
WORKINGSSH over USB. Host gets 172.16.42.2/24 by DHCP from the device over cdc_ncm; ping 2.1 ms; sshd listening. Also over Wi-Fi on the LAN now that it associates.
Display / panel
LIT ON GLASSThe panel lit on 7.1-r3. It was two independent gates, not one: an off-grid DSI pixel clock (155493 kHz is unreachable on this PLL; 155000 is grid-exact) and the panel bridge sending DCS before the DSI host was powered (prepare_prev_first). Fixing only the first was confirmed dark on hardware — both had to fall together.
Backlight
WORKING4700000.dsi.0, the panel's own DCS backlight, writable by the session user via the video group. The old ETIMEDOUT was a symptom of the dark panel, not a separate fault, and cleared the moment the panel lit.
Touchscreen
WORKINGElan ekth3500 on i2c-1 enumerates as Elan Touchscreen on /dev/input/event2 — it moved from event1 when the vibrator started binding and took input0. It advertises INPUT_PROP_DIRECT, which is how touch-boost and dt2w find it without name matching.
GPU
RENDERS, NEVER IDLESAdreno 320 accelerates (freedreno, GL 3.1) but cannot survive a power-rail collapse: the first render after restore wedges the command processor (rptr frozen, gpu hw init failed). A udev rule pins power/control=on to prevent it, so the GPU sits at 450 MHz forever — devfreq logs zero transitions — and the CPUs idle around 58 °C. Root-caused to core-memory retention, which downstream keeps for the 3D block alone.
Compositor
WORKINGsway + SXMO session fully up — swaybg, swaybar, conky, Xwayland, lisgd. A grim screenshot pulled over SSH shows a complete 1200×1920 desktop, proving the whole render path.
Wi-Fi
WORKINGAssociates, DHCP, passes traffic — ~55 Mbit/s sustained (~76 % of a 72.2 Mbit/s PHY, zero errors). Three gates fell in order: firmware (the iris XO is 48 MHz, not 19.2), scanning (software-scan fallback), and band decoding — the driver had been reading every 2.4 GHz frame as 5 GHz.
Bluetooth
WORKINGhci0 on the same riva. Discovers real devices with live RSSI; BlueZ 5.87 advertises A2DP Source/Sink + AVRCP.
Audio out
WORKING — VIA A2DPOnboard audio is absent upstream on this SoC (no sound node, no wcd9310, no legacy SLIMbus), so Bluetooth A2DP was always the only realistic path out. Proven with a portable speaker: both channels routed in PipeWire over a live transport, and pairing survives reboot. Clears P4 audio-out and the audio half of felt parity at once.
Audio in / mic
HARDSame SLIMbus / WCD9310 gap as onboard audio-out. A2DP does not help here — capture needs the codec brought up, which is a port, not a missing DT node.
Battery + charger
WORKINGsmb347 binds once monitored-battery lands — the old error -2 was -ENOENT, which the driver does not tolerate. batteryd moved Tier D → Tier A and holds a real 75–80 % band, verified actuating (4340000 → 4000000 µV) and failing open on stop. SoH 93.9 %, 25 cycles.
Storage
DMA RESTOREDThe eMMC PIO workaround turned out to be obsolete: DMA survived 348 s of mixed I/O where the original oopsed at 46 s. Sequential reads 24.1 → 43.1 MB/s, and 0.78 of a core handed back — in PIO the CPU copied every byte, costing ~30 % of the machine.
CPU frequency
WORKINGAll four Krait cores share one cpufreq-dt policy and scale 384–918 MHz on schedutil — the DT was missing every OPP, fixed by generating a Krait OPP table from the downstream PVS tables. Capped at 918 MHz until the qcom,saw2 CPU regulator has a driver — that is the clock the bootloader already set, so everything at or below it has enough voltage.
Haptics
WORKINGpm8xxx_vib_ffmemless — a full FF_RUMBLE device, driven and confirmed at the PMIC register (VIB_DRV 0x00 → 0xe0 → 0x00, with weak/strong scaling). The first device in the fleet where feltd moves a real motor. The old ‘nothing binds’ note looked for an LED node; mainline registers an input FF device instead.
Felt parity
4 / 4 ROWStouch-boost (lifts both the CPU floor and the Adreno devfreq floor on finger-down), feltd, dt2w and now the audio-cue path all running.
Thermal
WORKINGFive zones: the bq27541 pack sensor plus cpu0–cpu3, reading 50–54 °C under load. (cpu0-thermal reports −71 °C — bogus, needs calibration.)
Clocks
WORKINGAll controllers bound: gcc-msm8960 (qcom,gcc-apq8064), mmcc-msm8960, lcc-msm8960 and five kpss-xcc. SSBI + PM8921 up — the power key arrives through it.
Sensors
BUS REACHABLE, SILENTThe parts are known — an MPU-6050, an AK8963 and an AL3320 ALS — identified from flo's own shipped DSPS firmware, which names their driver files. They sit on GSBI2 i2c, which the AP can reach: an earlier note here said “not on any Linux-visible bus” and that was wrong — the bus had simply shipped status="disabled" in the device tree, so the scans that found nothing were scanning the wrong buses. It is enabled now and nothing has answered yet. Stock never powered these from the AP at all: the coprocessor voted for its own rails directly through the RPM, below the kernel's regulator framework, so the supply is invisible in the board files.
Suspend
SEE NOTEThe device does suspend — and used to look dead afterwards, because the USB gadget was not a wakeup source. Setting power/wakeup=enabled fixed it: 24 min continuous, zero loss, where it previously died at ~15 min.
Camera
NOT STARTEDThe hardware exists — 5 MP rear + 1.2 MP front (an earlier front-only note was a Nexus 7 2012 fact). Nothing works yet: mainline camss does not cover this SoC and the dtsi has zero camera nodes.
Modem
N/AWiFi-only SKU; no cellular hardware on this variant.
RO shell
P6 FRONTIERGated on the RO port itself now, not on this device — the felt-parity stack beneath it is already running.
Beyond Android
What's designed in for this device once it boots — parity ceiling raised, not just met.
Battery intelligence
batteryd caps charge at a mid-SoC band and tracks State-of-Health, shipping fleet-wide; flo is on the oldest-packs-first list once booting unlocks it.
16 MiB, sidestepped
That hard 16 MiB boot budget once forced xz-9e ramdisk surgery to make a boot.img fit. lk2nd retires the problem: the 16 MiB partition now holds only the 246 KiB bootloader, and the kernel lives on a roomy ext2 partition.
Gesture navigation
Swipe-based nav with haptic commit and paired audio cues, planned for Track 3 — core physics, no blur, snappy via touch-boost on the Krait 300.
Double-tap-to-wake
Fleet-standard wake-from-black, planned once the touchscreen driver is identified post-boot.
A living kernel
A msm8974-mainline-based kernel actively maintained, replacing a frozen 3.4 board-file build with no device tree at all.
Road to RO-OS
Every device climbs the same ladder. Phase = the highest rung fully reached.
kernel + initramfs + rootfs mount; reaches a login/console internally
a shell — serial UART, USB-net, or WiFi+SSH
panel lights via KMS, backlight, touchscreen events
WiFi associates; Bluetooth enumerates
audio out (A2DP), battery/charge + batteryd Tier A, thermal throttling on CPU and GPU; motion sensors reachable on GSBI2 but not yet answering
GL acceleration + a Wayland compositor runs
the RO shell runs — double-tap-wake, rotation, gestures
cameras, suspend/resume, vibration
Live sprint
What's on the bench right now.
Keep the GPU alive across a power-rail collapse
The appended-DTB route was proven dead on hardware — the kernel wrote nothing to ramoops, so it died before setup_arch. lk2nd 23.1 boots /extlinux/extlinux.conf from an ext2 partition and passes the DTB in r2. Proven, not inferred: /proc/cmdline came back byte-for-byte as the config written onto cache.
There was no cpufreq policy and BogoMIPS read 13.50 — the DT was missing every OPP. A Krait OPP table generated from the downstream PVS tables now scales all four cores 384–918 MHz on schedutil. Capped at 918 MHz pending a qcom,saw2 CPU-regulator driver (no voltage scaling yet).
It was two independent gates, not one: an off-grid DSI pixel clock (155493 kHz is unreachable on this PLL; 155000 is grid-exact) and the panel bridge sending DCS before the DSI host was powered (prepare_prev_first). Fixing only the first was confirmed dark on hardware — both had to fall together.
wcn36xx WiFi (WCN3660) associates and sustains ~55 Mbit/s; BlueZ 5.87 enumerates; and with no onboard codec upstream, Bluetooth A2DP is the audio-out path — proven end to end in PipeWire, pairing surviving reboot. That clears the P3 and P4 core-I/O rungs.
From r33 to r52 a DPMS blank killed the panel until a reboot, and nine explanations were measured and killed: engine-busy, DMA-busy, ULPS, the video engine, LP-vs-HS, the escape clock, the lvs7 rail. Every one read byte-identical at a working boot and a failing wake — which was the clue, not the dead end. They were all downstream of a PHY that was never programmed at all.
The DSI PHY’s registers live inside the DSI host’s register block, and that window is fed by the host’s clocks — not by the PHY’s own iface, the only clock in its pm_clk list. The bridge programs the PHY before it powers the host, so if the host has runtime-suspended since the last blank both windows are dead and every write is silently discarded. Boot works only because the host is still awake from probe — which is exactly why this presented as “boots lit, dies on the first blank”.
Proved by A/B/A on one boot with one variable: pinned, the PHY reset drives, the register window reads live, and a DCS read returns 255 — a read needs the panel to drive data back, so it is end-to-end proof rather than a hopeful log line. Unpinned, the same cycle gives -110 and a dark screen. Re-pinning recovers an already-wedged panel with no reboot.
Adreno 320 accelerates under freedreno, but the first render after a power-rail collapse wedges the command processor (rptr frozen, gpu hw init failed). A udev rule pins power/control=on to keep it up, so it sits at 450 MHz forever — devfreq logs zero transitions. Root-caused to core-memory retention, which downstream keeps for the 3D block alone.
Audio-in and the headset jack really do sit behind SLIMbus/WCD9310 — a driver port, not device-tree wiring. The motion sensors are a different story, and the earlier claim that they were equally unreachable was wrong: they hang off GSBI2 i2c, which the AP can drive, and which had merely shipped disabled in the device tree. It is enabled now, the controller probes, and the bus has still not returned a single ACK. Stock never powered these parts from the AP — the coprocessor voted its own rails straight through the RPM — so the open question is which supply to bring up, and the answer is in a firmware blob rather than a datasheet.
SoC block diagram
The programmable blocks inside the APQ8064, and the chips that hang off each one. For the board-level view — every bus and address, with the missing blocks drawn as holes — see the system schematic.
camss.c begins at msm8916,
and the dtsi has no camera nodes at all), the video codec (no
VENUS symbol exists for this generation), DSPS and
LPASS/SLIMbus. The CPU, GPU, display, radio, storage and
both i2c buses all work. The two dashed rows are different in kind: the
DSPS Hexagon core owns every motion and light sensor and has no
remoteproc or SMGR driver upstream, and LPASS/SLIMbus owns the audio
codec with no apq8064 variant in sound/soc/qcom and no SLIMbus
controller for this generation. Both are ports, not device-tree work, and
everything behind them is dark for the same reason. Two smaller gaps sit on a
working bus: the ANX7808 SlimPort bridge answers nothing because its
power GPIO is never driven, and BCM2079x NFC has no mainline driver at
all. The SAW2 row is why the CPU stops at 918 MHz rather than 1512 —
no mainline driver for the CPU rail, so the OPP core does no voltage scaling
and only the clock the bootloader already set is safe.
Inside the SoC — the 11-second window
Why an unchanged kernel stopped reaching the screen, and what fixed it.
power/control=on — but the GPU
idles out 66 ms after probe (DRM_MSM_INACTIVE_PERIOD),
at 2.2 s, while the rootfs udev daemon does not start until 11.94 s. The rail
was therefore down for exactly 11 298 ms, and this a320 does not
survive the restore: the next render froze the command processor
(rptr stuck while wptr advanced) and recovery failed
with gpu hw init failed: -22. No userspace fix can win that race
— the collapse happens half a second before /init even runs. r26
moves the pin into adreno_gpu_init(), ahead of the first idle,
and the whole failure disappears. Note the kernel's GPU code, the DT node and
Mesa were identical across the break; only when the pin landed
mattered.