Blog / Post

Fedora on the AYANEO Pocket DS: mainline Linux on a dual-screen handheld

Mainline Linux on the AYANEO Pocket DS (QCS8550): Fedora aarch64 bring-up, taming the dual touchscreens, and a SteamOS-style image that boots to Big Picture.

I make Linux boot on hardware that was never supposed to run it. The current victim: the AYANEO Pocket DS a clamshell, dual-screen handheld built on the Qualcomm QCS8550 (the embedded cousin of the Snapdragon 8 Gen 2) that ships with Android. It now runs Fedora aarch64 on a mainline-first kernel, and as of this summer it has a SteamOS-style image that boots straight into Big Picture.

This is not an Android container, not a chroot, not a vendor kernel with a distro stapled on. It’s Fedora on a kernel tracking mainline, with the device-specific drivers either patched in and clearly labelled, or on their way upstream.

What actually works

  • Both screens, both touchscreens the top OLED and the lower panel, each with its own touch controller, correctly rotated and routed.
  • GPU acceleration Adreno 740, clocked to 1.0 GHz with a custom OPP table, on a patched Mesa (freedreno). This plus suspend/resume are the crown jewels; a regression there is a headline regression, and they’re protected accordingly.
  • Suspend/resume. Yes, really, on a Qualcomm handheld.
  • WiFi and Bluetooth (WCN7850), UFS + microSD, USB-C with DisplayPort alt-mode, stereo speakers, fan control across the SoC’s thermal zones, and the gamepad (the sticks/buttons arrive via an internal USB controller plus an MCU handled through InputPlumber mappings).

What doesn’t: the fingerprint sensor. That’s the honest, complete list of casualties right now.

Credit up front: the SM8550 device groundwork from ROCKNIX made this port possible. My job was turning that into a proper distro: real Fedora RPMs, real images, real CI no quirk-script pile.

The bug of the port: a touchscreen that froze the desktop

If you only read one section, read this one.

Touching the top screen would freeze the whole desktop. Kernel log: geni_i2c: Bus arbitration lost, clock line undriveable, then a timeout resetting the controller’s state machine, then the touch driver giving up.

The top touchscreen (a Focaltech FT5426, mainline edt_ft5x06 driver) sits on an i2c bus that runs through the hinge. Its pin configuration is identical to the bottom screen’s perfectly-healthy bus so this isn’t a config problem, it’s physics: the hinge traces make the bus electrically marginal. It’s clean at idle and only falls over under the burst of traffic a touch generates. I ruled out clock speed (going slower was actually worse), regulators, and pin drive strength before accepting that the hardware is what it is.

So the fix is a driver patch that treats the bus like the flaky link it is:

  1. Shorter reads. The driver’s defaults for this chip fetch 10 touch points a 63-byte read on every interrupt, which is exactly the kind of long transaction that wedged the bus. Capped to 2 points, it’s a 15-byte read. Short transactions survive.
  2. Retries. The stock driver retried zero times; one transient error dropped the whole touch packet. Now: 4 retries with backoff.
  3. Recovery only when it’s real. The chip’s reset line is only toggled after a genuine freeze (>1 s with no successful read), rate-limited so ordinary bus stutter doesn’t cost you a 300 ms hitch.
  4. Sanity checks. Frames with impossible touch counts or out-of-range coordinates get dropped, which also cured the cursor occasionally teleporting past the touch point.

Result: from “touching the screen crashes the session” to ~98% smooth. It ships in the project kernel today, clearly labelled as the workaround it is.

Two more war stories, briefly

Bluetooth shipped with no MAC address. bluetoothctl reported no controller at all, even though firmware load and HCI init completed fine. The WCN7850 reports an all-zero BD_ADDR, and the Bluetooth core deliberately refuses to register an address-less controller correct behavior, hostile symptom. The fix is a small boot service that programs a stable, locally-administered MAC derived from each device’s /etc/machine-id (per-device, so units don’t collide). It has to retry through the controller’s ~5-second early-boot setup window, and then Bluetooth just works.

A firmware update could silently kill WiFi. The kernel is built without firmware decompression, and Fedora ships the ath12k WiFi blobs xz-compressed so the port carries uncompressed copies, and a routine linux-firmware update could clobber them. Worse, the failure was delayed: the running chip keeps firmware in RAM, so WiFi died only on the next radio reset. The blobs now also live under /lib/firmware/updates/, which the kernel searches first and which no distro package touches. Landmine defused permanently.

There’s also a fun audio one the speakers were whisper-quiet because the smart amp’s analog PA was set to 12/17 in the ALSA UCM profile, and an override couldn’t win due to UCM’s include-ordering rules and an on-screen keyboard that lives on the bottom screen and types into the top one, which took XWayland, uinput injection, an InputPlumber ignore rule, a one-line C patch to Onboard, and a KWin “never take focus” rule to pull off. Dual-screen Linux keeps you humble.

The SteamOS-style image

The part r/SBCGaming actually cares about: there’s now a steamos image profile alongside the KDE, KDE Mobile, GNOME, and Phosh ones.

  • Boots directly into Steam Big Picture inside a gamescope session (the mmogr gamescope fork with Flip-DS dual-screen support).
  • Native ARM64 Steam and Proton 11 ARM64 (Sildur’s build) are preseeded into the image first boot doesn’t download a gigabyte before showing you anything.
  • Full KDE Plasma 6 desktop is one session-switch away (steamos-session-select-style Gaming/Desktop toggle, with a “Return to Gaming Mode” launcher).

Honest status: beta. An early tester’s first-boot report turned into a four-bug fix chain (a filesystem-resize binary that was never actually shipped, a partition-grow step that silently aborted so downloads hit disk-full, a stale package pipeline, and a Steam bootstrap pinned to an old client snapshot forcing a broken self-update). All four are fixed at the root as of early July. Remaining known issues: the bottom screen is blank in Gaming Mode for now (the dual-screen companion tooling is KWin-based and only runs in desktop mode), and there’s one unverified report of a slow first boot I’m still chasing. I do think it is stable given that I am daily driving it myself.

Every profile rebuilds automatically in CI every week, so images never go stale against Fedora updates.

Upstream, because that’s the point

The COPR kernel is the delivery vehicle, not the destination. Driver series for the top panel’s power tree the SGM3804 charge-pump regulator and SY7758 backlight have been submitted already by Neil Armstrong, and the panel driver for the display controller is co-authored with Teguh Sobirin and my friend Aaron Kling, since the same DDIC ships in the AYN Odin 2 Portal, Odin 3, and Thor. Fixing it once, upstream, means every one of those devices benefits that’s the whole model.

drm/panel: st7703: Add AYANEO Pocket DS Lower Panel Support – Has been merged to https://gitlab.freedesktop.org /drm/misc/kernel.git

Input: edt-ft5x06- ignore contacts with anout-of-range slot id – has been applied

Links

  • Docs, flashing guide, images: https://fedora-pocketds.azka.li/
  • Source (kernel, packages, image builder): https://gitlab.com/linux-pocketds
  • Packages: Fedora COPR linux4switch/pocketds

Fair warning before you flash: the boot chain replaces the Android bootloader path with a signed loader, and the docs are the source of truth read them first, this is still an enthusiast port.

Enjoyed this? This work is patron-funded. Patrons get early builds, devlogs, and a vote on which device I attack next — everything I make stays free and open.