Google Nexus 7 (2013) Asus flo Qualcomm APQ8064 Snapdragon S4 Pro · Krait 300 armv7 RO-OS · linux-ro-os-qcom-apq8064
Nexus 7 (2013) · P4 · core I/O

The little reading tablet,
awake, and lit at last.

Google's compact 7-inch reader from the Android 4.3 Jelly Bean era — the device the Holo design language was built to show off. Stock booted a 3.4 board-file kernel with no device tree at all. On 2026-07-22 it booted RO-OS to userspace for the first time: lk2nd replaces the stock bootloader's ATAGS handoff and passes a real DTB in r2. Since then the panel came alive on the glass, Wi-Fi and Bluetooth both associate, audio plays over A2DP, the charger holds a real mid-SoC band, and all four felt-parity rows run — and the panel now survives a blank — on a tablet whose microphones still sit behind a coprocessor mainline has never spoken to.

Kernel
7.1.0 · lk2nd + extlinux
Panel
MDP4 · 1200×1920 IPS
SoC
APQ8064 · Adreno 320
Vs. Android
3.4 (no DT) → 7.1 mainline
70%hw parity
16 of 23 subsystems confirmed — panel lit and survives a blank, into core I/O

Hardware

Every subsystem, and exactly where it stands. Nothing here is padded.

WorkingIn progressBlocked / portingNot started

Boot / kernel

WORKING

Boots 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.

lk2nd 23.1 · extlinux

Access / shell

WORKING

SSH 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.

172.16.42.1 · cdc_ncm

Display / panel

LIT ON GLASS

The 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.

jdi,lt070me05000 · 59.81 Hz

Backlight

WORKING

4700000.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.

ro-backlight

Touchscreen

WORKING

Elan 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.

ekth3500 · event2

GPU

RENDERS, NEVER IDLES

Adreno 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.

a3xx · freedreno

Compositor

WORKING

sway + 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.

sway · GLES2

Wi-Fi

WORKING

Associates, 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.

wcn36xx · WCN3660

Bluetooth

WORKING

hci0 on the same riva. Discovers real devices with live RSSI; BlueZ 5.87 advertises A2DP Source/Sink + AVRCP.

BlueZ 5.87

Audio out

WORKING — VIA A2DP

Onboard 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.

A2DP · PipeWire

Audio in / mic

HARD

Same 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.

WCD9310

Battery + charger

WORKING

smb347 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.

bq27541 + smb345

Storage

DMA RESTORED

The 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.

mmci · 43.1 MB/s

CPU frequency

WORKING

All 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.

384–918 MHz · schedutil

Haptics

WORKING

pm8xxx_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.

feltd · FF_RUMBLE

Felt parity

4 / 4 ROWS

touch-boost (lifts both the CPU floor and the Adreno devfreq floor on finger-down), feltd, dt2w and now the audio-cue path all running.

touch-boost · feltd · dt2w

Thermal

WORKING

Five zones: the bq27541 pack sensor plus cpu0–cpu3, reading 50–54 °C under load. (cpu0-thermal reports −71 °C — bogus, needs calibration.)

5 zones

Clocks

WORKING

All controllers bound: gcc-msm8960 (qcom,gcc-apq8064), mmcc-msm8960, lcc-msm8960 and five kpss-xcc. SSBI + PM8921 up — the power key arrives through it.

gcc/mmcc/lcc

Sensors

BUS REACHABLE, SILENT

The 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.

gsbi2 i2c

Suspend

SEE NOTE

The 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.

usb-gadget-no-suspend

Camera

NOT STARTED

The 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.

5MP + 1.2MP

Modem

N/A

WiFi-only SKU; no cellular hardware on this variant.

—

RO shell

P6 FRONTIER

Gated 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.

A 12-year-old 3950 mAh cell has spent its life trickled to 100% — batteryd exists to stop that.
📦

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.

lk2nd also raises the fastboot buffer from ~340 MB to 768 MiB, so the rootfs flashes in one shot instead of piecewise dd through TWRP.
⇄

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.

A modern touch-UX layer the 2013 firmware never had — no hardware nav buttons to depend on.
◱

Double-tap-to-wake

Fleet-standard wake-from-black, planned once the touchscreen driver is identified post-boot.

A fleet-wide rung applied uniformly, not device-specific work.
7.0

A living kernel

A msm8974-mainline-based kernel actively maintained, replacing a frozen 3.4 board-file build with no device tree at all.

Stock flo predates even the device-tree era — this is a bigger leap than most devices in the fleet.

Road to RO-OS

Every device climbs the same ladder. Phase = the highest rung fully reached.

Current phase
P4 — Core I/O (panel lit)
Target track
Track 3 — RO Lite · GL-only, no Waydroid, snappy via touch-boost
% model
P0≈5% · P1≈15% · P2≈30% · P3≈45% · P4≈60% · P5≈75% · P6≈90% · P7=100%
✓
P0 — Boots~5%

kernel + initramfs + rootfs mount; reaches a login/console internally

✓
P1 — Access~15%

a shell — serial UART, USB-net, or WiFi+SSH

✓
P2 — Display + Touch~30%

panel lights via KMS, backlight, touchscreen events

✓
P3 — Connectivity~45%

WiFi associates; Bluetooth enumerates

●
P4 — Core I/O~60%current rung

audio out (A2DP), battery/charge + batteryd Tier A, thermal throttling on CPU and GPU; motion sensors reachable on GSBI2 but not yet answering

P5
P5 — GPU + Wayland~75%

GL acceleration + a Wayland compositor runs

P6
P6 — RO-OS shell~90%

the RO shell runs — double-tap-wake, rotation, gestures

P7
P7 — Full parity~100%

cameras, suspend/resume, vibration

Live sprint

What's on the bench right now.

On the bench

Keep the GPU alive across a power-rail collapse

✓
First boot + lk2nd handoff

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.

2026-07-22 · first boot, SSH over USB
✓
CPU cores unparked

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).

2026-07-22 · cpufreq-dt · KRAITCC + QCOM_HFPLL
✓
Panel lit on glass

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.

2026-07-27 · 7.1-r3 · panel lit, 59.81 Hz
✓
Connectivity + Bluetooth audio

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.

2026-08-04 · P3 + P4 core I/O
✓
r53 — the panel survives a blank, and the reason was never in the DSI host

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.

2026-08-23 · r53 · confirmed on the glass — not flo-specific
5
GPU renders but won't idle

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.

now · a3xx bound, devfreq pinned

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.

PROGRAMMABLE BLOCKS — APQ8064 (Snapdragon S4 Pro, 28 nm) CHIPS ON THE BOARD CPU — 4× Krait 300 384–918 MHz · KRAITCC + QCOM_HFPLL · schedutil SAW2 / SPM — CPU rail no mainline regulator driver → the 918 MHz cap Adreno 320 (a3xx) · gfx3d_gdsc GL 3.1 / GLES 3.0 · runtime PM forbidden since r26 MDP4 display controller v4.4 · one CRTC · fb0 msmdrmfb JDI LT070ME05000 panel 1200×1920 IPS · 59.81 Hz · lit on the glass DSI host (4700000.dsi) grid-exact 155 MHz pixel clock · prepare_prev_first ANX7808 SlimPort (DSI→HDMI) i2c-0 @0x39 — silent, power GPIO never driven CAMSS / CCI — camera pipeline NOT in mainline: camss.c starts at msm8916; dtsi has zero camera nodes 5 MP rear + 1.2 MP front hardware exists, no driver path Venus / vidc — video codec no VENUS symbol at all; APQ8064 predates mainline Venus HW H.264 encode / decode software decode only Riva / WCNSS — Hexagon QDSP6 PIL-loaded firmware · SMD transport WCN3660 Wi-Fi + BT · iris XO 48 MHz · GNSS not built DSPS — Hexagon sensor DSP no remoteproc, no SMGR in mainline accel · gyro · magnetometer · ALS and therefore auto-rotate + auto-brightness LPASS + SLIMbus — audio no apq8064 LPASS variant · no SLIMbus controller WCD9310 codec speakers · microphones · headset jack SDCC1 (mmci) — storage PIO — the BAM DMA path oopses under load eMMC 16 GB p23 /boot · p30 rootfs 12.43 GiB GSBI1 / GSBI5 — i2c 2 of 6 buses wired; GPIO16/17 clash with ttyMSM1 bq27541 · SMB345 · Elan ekth3500 + BCM2079x NFC — no mainline driver SSBI — PMIC link single-wire PM8921 PMIC pwrkey · rtc@11d · vibrator@4a · regulators USB ChipIdea (ci_hdrc) + SMMU peripheral; OTG built but untested USB gadget cdc_ncm 172.16.42.1 · ssh GCC · MMCC · RPM · TSENS · QFPROM 5 thermal zones · speed bin 14 / PVS 3 on this unit
Every remaining gap on this tablet is a programmable block mainline cannot drive — not a missing wire. Four subsystems are dark and all four are driver ports: camera (mainline 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.
Working on hardware Reachable, not wired Needs a driver port – – – unreachable from mainline

Inside the SoC — the 11-second window

Why an unchanged kernel stopped reaching the screen, and what fixed it.

BEFORE — r22…r25 · THE RACE IS LOST AFTER — r26 · THE PIN MOVES INTO THE DRIVER 2.10s 2.21s 2.71s 11.94s 13.4s 36.8s RAIL DOWN — runtime_suspended_time = 11298 ms GPU probes suspends 66 ms idle initramfs /init already too late rootfs udevd udev pins control=on rail restored, CP already lost sway's 1st render CP wedged · hw init −22 panel black, backlight on RAIL NEVER COLLAPSES — runtime_suspended_time = 0 2.10s 2.64s 36.8s GPU probes pm_runtime_forbid() in adreno_gpu_init() — patch 0016 sway renders 1200×1920 on the glass
The mitigation was always correct and always ~11 seconds too late. A udev rule pinned the Adreno's 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.
Rail up / working Rail down / wedged Mitigation fires too late