Clockwork uConsole · Volume 6

Display, Keyboard, Audio, and the Kernel

The Clockwork patch chain, JD9365DA-H3 LCD driver, GD32 keyboard MCU, ALSA path, and the Linux-side glue that makes the hardware work

Contents

SectionTopic
1About this Volume
2The Clockwork Kernel Patch Set
· 2.1What the patches actually do
· 2.2Where the patches live (upstream tracking)
· 2.3Building a patched kernel from source
· 2.4When to build vs use a pre-built image
3The JD9365DA-H3 LCD Driver
· 3.1The panel — controller + glass
· 3.2DSI initialisation sequence
· 3.3The driver in the kernel tree
· 3.4The clockwork-uconsole-lcd device-tree overlay
· 3.5Pixel timing and the modeline
· 3.6Common LCD failure modes
4Display Stack: Framebuffer, DRM/KMS, Wayland/X11
· 4.1The legacy fbdev path
· 4.2DRM/KMS — the modern stack
· 4.3X11 on the uConsole
· 4.4Wayland on the uConsole
· 4.5Compositor-specific gotchas
5Backlight Control
· 5.1Two PWM sources, one knob
· 5.2The keyboard MCU’s role
· 5.3Linux interfaces (sysfs, brightnessctl)
· 5.4The “screen too dim” community gripe
6The Keyboard Subsystem
· 6.1GD32F103 firmware
· 6.2The USB HID descriptor
· 6.3Linux input layer mapping
· 6.4Custom keymaps via xkb
· 6.5Custom keymaps via Wayland
· 6.6The keyboard backlight LEDs
· 6.7Common keyboard issues matrix
7The Trackball
· 7.1Sensor and buttons
· 7.2The “janky trackball” community gripe
· 7.3Tuning sensitivity in xinput / Wayland
· 7.4Replacement trackball options
8The Audio Path (Linux-Side)
· 8.1ALSA card numbering on the uConsole
· 8.2The PWM-audio driver
· 8.3Sample rate, depth, and the realities of PWM-audio
· 8.4ALSA → PulseAudio → PipeWire stack
· 8.5The microphone path
· 8.6Common audio issues matrix
9Display Power Management
· 9.1DPMS modes
· 9.2The systemd-logind path
· 9.3Custom blank-and-suspend scripts
10Rockchip Kernel Differences (Radxa CM5)
· 10.1Different DSI controller
· 10.2Different ALSA setup
· 10.3Different keyboard PHY enumeration
· 10.4The DTS-porting checklist
11Canonical OS Image Kernels
· 11.1Rex’s Bookworm / Trixie images
· 11.2CrossPlatformDev CI builds
· 11.3Building your own from source
12Vol 12 Cheatsheet Updates
13Resources
14Footnotes
15Index

1. About this Volume

This volume is the Linux-side counterpart to Volume 2’s hardware-side story. Volume 2 told you what’s on the mainboard schematic — the JD9365DA-H3 LCD driver IC, the GD32F103 keyboard MCU, the OCP8178 audio amplifiers — and Volume 4 told you how the system gets to a running kernel. This volume picks up at the point where Linux is running and walks how the kernel knows about, controls, and exposes those peripherals to user-space.

It is the “why does my keyboard work, and how do I make it work differently” volume. It is also the “why does the screen look slightly off, and how do I fix it” volume. And it is the “what’s the audio quality story, really” volume — because the BCM2711 has no on-die audio codec, and the answer to the audio-quality question depends on knowing exactly what PWM-audio means.

The reader is assumed to be comfortable with Linux device trees, the input subsystem, ALSA terminology (cards, devices, mixers), and basic kernel-build practice. If “device tree overlay” is a foreign phrase, skim the Pi docs section Device Trees, overlays, and parameters1 before continuing.

A scope note: this volume covers only the Linux path. The kernel patches and the userland glue. Vol 2 has the analog and digital schematic side. Vol 11 has the power and thermal envelope. Vol 5 has the OS-image distinctions; this volume is about the kernel that those OS images all share.

2. The Clockwork Kernel Patch Set

The uConsole is hardware that mainline Linux doesn’t know about. The mainboard’s specific peripheral set — the JD9365DA-H3 panel on DSI0, the GD32 keyboard MCU on the GL850G’s USB hub, the OCP8178 amplifier behind the BCM2711’s PWM audio output, the AXP228 PMIC over a non-standard I²C bus — is not a configuration the upstream Pi kernel ships drivers for. The Clockwork patches are a small layer of code and device-tree changes that make these peripherals visible.

2.1 What the patches actually do

The patches2 are smaller and more targeted than you might expect. They cluster into five buckets:

Patch bucketWhat it changesWhy
Panel driverNew file drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.cDSI command sequence + modeline for the specific panel revision
Device-tree overlayNew file arch/arm/boot/dts/overlays/clockwork-uconsole-lcd-overlay.dtsWires the panel driver to DSI0, sets backlight GPIO, declares power rails
AXP228 quirksSmall additions to drivers/mfd/axp20x.c and drivers/power/supply/axp20x_battery.cHandles the slightly non-standard register layout of the AXP228 vs the AXP223 the upstream driver targets
HID quirk for the keyboardOne-line addition to drivers/hid/hid-quirks.cTells the kernel the keyboard’s report descriptor is “this specific device” — needed because the keyboard reports as a generic HID and the quirk gives it a stable name for udev rules
config.txt snippetsDefault values that get baked into the OS image’s /boot/firmware/config.txtEnables the panel overlay by default; sets dtparam=audio=on etc.

That’s the entirety of the patch set. The bulk is the panel driver — about 600 lines of C with the panel’s DSI initialisation sequence, modeline, and backlight handler. Everything else is configuration-shaped — small edits, mostly two- or three-line additions in existing files.

What the patches don’t do — and shouldn’t:

  • No kernel-API changes. The patches use only existing kernel interfaces (the DRM panel framework, the input subsystem, the I²C client API).
  • No userland changes. ALSA configs, X11 / Wayland configs, udev rules — these are layered on top by the OS distribution, not by the kernel patches.
  • No firmware blobs. The closed-source GPU firmware (start4.elf) is the same blob that ships with upstream Pi OS; the patches don’t touch it.
  • No closed-source code. Every patch is GPLv2 source.

2.2 Where the patches live (upstream tracking)

The patches are maintained by Clockwork and re-rebased to match each Pi-Foundation kernel release. The release tag in the Clockwork repo (v6.6.31-clockwork, v6.12.x-clockwork, etc.) corresponds to the Pi kernel release the patches target. As of mid-2026, the relevant tags:

TagTargets Pi kernelMaintained forNotes
v6.6.y-clockworkv6.6.x (LTS)Pi OS BookwormStable; mainline target for 2024-25
v6.12.y-clockworkv6.12.xPi OS TrixieCurrent as of 2026
v6.16.y-clockworkv6.16.xBleeding-edge community TrixieExperimental

Rex’s images (Vol 5 / Vol 11) bundle the relevant tag’s pre-built kernel. CrossPlatformDev’s CI builds rebuild from this tag automatically on every Pi-kernel release.

These patches have never been submitted upstream to the mainline Linux kernel. There are two reasons: (1) the JD9365DA-H3 panel driver is generic enough that it could be merged, but the panel timing data is specific to Clockwork’s choice of panel and would need a Clockwork-specific compatible string in the device tree; (2) the AXP228 quirks should arguably be merged into the upstream axp20x driver as a new compatible, but no one has done the rebase work. If you wanted to contribute upstream, the AXP228 driver patch is the highest-value target.

2.3 Building a patched kernel from source

Most users will never need this — the pre-built kernel images are excellent. But if you’re hacking on the panel driver or chasing a kernel bug, the build:

# On a build host (Linux x86_64 with cross-toolchain, or another Pi):
sudo apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev \
    crossbuild-essential-arm64 ccache

# Clone the patched kernel:
cd ~/src
git clone --branch v6.6.y-clockwork https://github.com/clockworkpi/RaspberryPi-linux-clockworkpi
cd RaspberryPi-linux-clockworkpi

# Set up cross-build environment:
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
export KERNEL=kernel8

# Get the uConsole-specific config:
make bcm2711_defconfig
# (or for CM5: make bcm2712_defconfig)
# Then merge in the uConsole-specific config diffs:
scripts/kconfig/merge_config.sh -m .config arch/arm64/configs/uconsole.config

# Build kernel + DTBs + modules:
make -j$(nproc) Image modules dtbs

# Install to a mounted target SD card / USB:
sudo mkdir -p /mnt/uconsole-boot /mnt/uconsole-rootfs
sudo mount /dev/sdX1 /mnt/uconsole-boot       # boot partition
sudo mount /dev/sdX2 /mnt/uconsole-rootfs     # rootfs

sudo cp arch/arm64/boot/Image /mnt/uconsole-boot/kernel8.img
sudo cp arch/arm64/boot/dts/broadcom/*.dtb /mnt/uconsole-boot/
sudo cp arch/arm64/boot/dts/overlays/*.dtb* /mnt/uconsole-boot/overlays/
sudo cp arch/arm64/boot/dts/overlays/README /mnt/uconsole-boot/overlays/

sudo make INSTALL_MOD_PATH=/mnt/uconsole-rootfs modules_install

# Check the resulting kernel version:
ls /mnt/uconsole-rootfs/lib/modules/
# (should show the new version, e.g., 6.6.31-v8+)

sudo umount /mnt/uconsole-boot /mnt/uconsole-rootfs

A clean build on a 12-core Linux box takes ~12 minutes; on the uConsole itself, ~75 minutes (and you really shouldn’t — see Vol 11 thermals).

2.4 When to build vs use a pre-built image

Use casePre-built imageBuild from source
Run the canonical OS
Apply a security patch released by Pi✅ (waits for Rex)✅ (immediate)
Custom panel driver work
Custom keyboard MCU firmware integration
Debug a kernel hang✅ (with dynamic-debug)
Add a new device-tree overlay(sometimes possible via dtoverlay= in config.txt)
Run on the latest Pi kernel before Rex rebases
Educational — understand the boot stack

For 95% of users, pre-built is correct. Building is for hardware hackers and kernel developers — not because it’s hard but because it’s a slow feedback loop on a battery-powered handheld.

3. The JD9365DA-H3 LCD Driver

This is the largest single piece of the patch set and the part that makes the most visible difference. The display works because of this driver.

3.1 The panel — controller + glass

The Clockwork uConsole’s display is a 5-inch IPS panel paired with a Jadard JD9365DA-H3 controller IC.3 The controller is a typical mid-2010s mobile-phone-style DSI IC: 4-lane MIPI DSI input, 1280×720 native resolution, 60 Hz refresh, RGB888 24-bit pixel format, and a backlight control via a separate PWM pin.

What’s notable is what isn’t on the controller:

  • No HDMI input. The driver is DSI-only.
  • No analog VGA. No surprise — modern mobile IC.
  • No on-die scaler. Whatever resolution the host sends, the panel displays. Non-native resolutions get scaled by the host’s GPU, not the panel.
  • No on-die touch controller. The uConsole’s panel is non-touch. (Some community mods bolt a USB capacitive touch controller in front; that’s a separate build.)

The panel’s IPS glass is the slow-but-good kind — wide viewing angles, accurate colour at off-axis, but slow pixel response time (~30 ms typical) compared to TN. For text, web, RetroArch, terminal use — fine. For action gaming — visible motion blur. This is intrinsic to the panel choice, not to the kernel driver.

3.2 DSI initialisation sequence

Powering on a DSI panel is not as simple as flipping a power switch. The controller IC needs a sequence of vendor-specific commands sent over the DSI command channel before it’s ready to display incoming pixel data. The JD9365DA-H3’s init sequence is approximately:

  1. Assert RESET low for ≥ 10 ms.
  2. Wait for power supplies to settle (3.3 V logic, 1.8 V signal).
  3. Release RESET.
  4. Wait 100 ms for internal PLL to lock.
  5. Enter LP (Low Power) mode on the DSI bus.
  6. Send a sequence of ~80 vendor-specific DCS Long Write commands (gamma curves, source-driver timing, panel-specific compensation values).
  7. Send DCS command 0x11 (Sleep Out).
  8. Wait 120 ms.
  9. Send DCS command 0x29 (Display On).
  10. Switch DSI bus to HS (High Speed) mode.
  11. Begin streaming pixel data.

The driver implements this as a function panel_init_seq(). The “magic” bytes — those 80 vendor-specific commands — come straight from the panel datasheet’s Recommended Initialisation Sequence section. They configure the panel’s source-driver bias, the gate-driver shift register timing, the gamma-correction curve, and the IR-drop compensation. Most of them are opaque without the datasheet’s register reference table.

A good rule of thumb: if your screen comes up but the colours are wrong (red/blue swapped, low contrast, banding), the init sequence is right but a panel-specific gamma/colour parameter is wrong. If the screen is just black, either the init sequence isn’t running (driver not loaded? device tree not picked up?) or the backlight isn’t on (different problem — see §5).

3.3 The driver in the kernel tree

The driver lives at drivers/gpu/drm/panel/panel-jadard-jd9365da-h3.c (the upstream-style filename; some kernel forks call it panel-clockworkpi-uconsole.c). Structurally it follows the standard Linux DRM panel-driver pattern:

struct jd9365da_panel {
    struct drm_panel base;
    struct mipi_dsi_device *dsi;
    struct backlight_device *backlight;
    struct regulator *power;
    struct gpio_desc *reset_gpio;
    bool prepared;
    bool enabled;
};

static const struct of_device_id jd9365da_of_match[] = {
    { .compatible = "clockworkpi,uconsole-lcd" },
    { .compatible = "jadard,jd9365da-h3" },
    {}
};

static struct mipi_dsi_driver jd9365da_driver = {
    .driver = {
        .name = "panel-jadard-jd9365da-h3",
        .of_match_table = jd9365da_of_match,
    },
    .probe = jd9365da_probe,
    .remove = jd9365da_remove,
    .shutdown = jd9365da_shutdown,
};
module_mipi_dsi_driver(jd9365da_driver);

Key methods (the four that the DRM framework calls):

MethodWhen it runsWhat it does
prepare()Power-on / wake from blankEnable power regulator; deassert RESET; run init sequence (DSI cmds)
enable()After prepare, before pixel streamingSend Display On DCS command; turn backlight on
disable()Before suspend / blankTurn backlight off; send Display Off DCS command
unprepare()Power-off / deep blankDisable power regulator; assert RESET; cut clocks

The DRM framework calls these in a specific order: prepare → enable → (rendering) → disable → unprepare. If your screen flickers on resume from suspend, suspect prepare() — it’s getting called but the init sequence isn’t completing.

3.4 The clockwork-uconsole-lcd device-tree overlay

The overlay tells the kernel:

  • “There is a mipi_dsi_device with compatible = 'clockworkpi,uconsole-lcd' on DSI0.”
  • “Its RESET line is wired to GPIO 5 (BCM numbering).”
  • “Its backlight PWM is on PWM0 channel.”
  • “Its power regulator is a fixed 3.3 V LDO from the AXP228’s DISP_3V3 rail.”

In .dts form:

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2711";

    fragment@0 {
        target = <&dsi0>;
        __overlay__ {
            status = "okay";

            panel: panel@0 {
                compatible = "clockworkpi,uconsole-lcd";
                reg = <0>;
                reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>;
                backlight = <&backlight>;
                power-supply = <&disp_3v3>;
                status = "okay";
            };
        };
    };

    fragment@1 {
        target-path = "/";
        __overlay__ {
            backlight: backlight {
                compatible = "pwm-backlight";
                pwms = <&pwm 0 1000000>;
                brightness-levels = <0 4 8 16 32 64 128 255>;
                default-brightness-level = <6>;
            };

            disp_3v3: disp_3v3 {
                compatible = "regulator-fixed";
                regulator-name = "DISP_3V3";
                regulator-min-microvolt = <3300000>;
                regulator-max-microvolt = <3300000>;
            };
        };
    };
};

The overlay is enabled by dtoverlay=clockwork-uconsole-lcd in config.txt (covered in Vol 4 §13.1). The overlay file is compiled to clockwork-uconsole-lcd.dtbo and stored in /boot/firmware/overlays/.

3.5 Pixel timing and the modeline

The panel’s modeline (the timing parameters telling the kernel exactly when to assert HSYNC, VSYNC, and how long each blanking interval lasts) is embedded in the panel driver. The values, from the datasheet:

ParameterValueNotes
Native resolution1280 × 720Fixed; panel is 5″ IPS
Native refresh60 HzConfigurable down to 30 Hz on lower-power profiles
Pixel clock74.25 MHz1280 × 720 × 60 × 1.075 (blanking)
Horizontal front porch110pixel clocks
Horizontal sync width40pixel clocks
Horizontal back porch220pixel clocks
Vertical front porch5lines
Vertical sync width5lines
Vertical back porch20lines
DSI lane count44-lane MIPI DSI
DSI bit rate per lane~445 MbpsFor 60 Hz operation

If you change the modeline (e.g., to drive 30 Hz to save power), you must change both the pixel clock and the lane bit rate proportionally. There is no scaling in the panel — it simply reads pixels at the rate the host streams them.

3.6 Common LCD failure modes

SymptomLikely causeFirst-line fix
Screen totally black, backlight offBacklight not enabled (PWM duty 0)brightnessctl set 50%. Or check dtparam=audio=on not breaking PWM.
Screen totally black, backlight on (faint glow)Panel init sequence not running`dmesg
Screen on but display garbled / wrong coloursDSI init sequence partially brokenCheck kernel logs for DSI errors. Re-power-cycle (cold boot, not reboot).
Screen flickers periodicallyDSI clock mismatch or PSR issueDisable kernel DRM PSR support: video=DSI-1:e:none in cmdline.txt
Half the screen rendered, half blackPanel driver thinks resolution is different than actualVerify modeline matches the panel datasheet
Vertical bands of staticRESET signal glitch on power-upCheck for RC delay on RESET line; if missing, slow the RESET deassertion in driver
Display on for ~3 seconds then blackBoot logo OK but kernel can’t take overDRM/KMS stack failed to bring up. Check dmesg.
Resume-from-suspend gives black screenprepare()/enable() ordering bug in panel driverOften fixed by upgrading kernel; or workaround systemd-suspend.service to skip display suspend
Tearing during full-screen videoNo vsync; KMS atomic commit not enabledUse a Wayland compositor (sway, wayfire) instead of X11; or xrandr --output DSI-1 --primary

4. Display Stack: Framebuffer, DRM/KMS, Wayland/X11

The kernel makes a screen exist; user-space puts pixels on it. There are several layers in user-space, and choosing the right one matters for performance and feature support.

4.1 The legacy fbdev path

Once upon a time, every Linux screen was a “framebuffer device” — /dev/fb0. The kernel exposed the screen as a memory-mapped region; you wrote bytes; pixels appeared. Tools like fbi (image viewer) and fbterm (terminal) used this directly.

On the uConsole, /dev/fb0 still exists for legacy compatibility, but it’s a compatibility shim over DRM/KMS — the kernel’s modern stack — not a primary path. Writing directly to /dev/fb0:

sudo cat /dev/zero > /dev/fb0      # blanks the screen
sudo cat /dev/urandom > /dev/fb0   # static

These work but bypass DRM’s modesetting, atomic commit, and synchronisation. Tearing is common; performance is limited; modern compositors don’t use this path.

When fbdev is fine: bare-metal embedded loadouts where you want a single full-screen application (a kiosk, a graphing-calculator app, a SDR spectrum display) with no compositor overhead. Use kmscube to verify DRM works, then fbi for static images, mpv for video.

When fbdev isn’t fine: anything wanting hardware-accelerated rendering, multiple windows, or playing nicely with systemd-logind.

4.2 DRM/KMS — the modern stack

The Direct Rendering Manager + Kernel Modesetting (DRM/KMS) is the modern path. The kernel exposes /dev/dri/card0 (the GPU device) and /dev/dri/renderD128 (the rendering node, for headless rendering). User-space talks to these via libdrm or the higher-level mesa graphics library.

DRM/KMS gives:

  • Atomic mode-setting — flip resolution / refresh / pixel format in one transaction; either the whole change applies or none of it does.
  • Vsync-aware page-flipping — no tearing.
  • Hardware planes — overlay a video plane atop a UI plane without copying pixels.
  • Multiple consumers — one process can render while another reads back; the GPU arbitrates.

Tools:

  • kmscube — minimal triangle demo. If it works, your DRM stack is fine.
  • modetest — query and exercise DRM modes from the command line.
  • drm_info — pretty-prints the DRM device’s capabilities.
sudo apt install drm-info modetest
sudo drm_info | head -40

On a working uConsole, drm_info lists “Connector DSI-1” with the JD9365DA-H3’s modeline.

4.3 X11 on the uConsole

X11 (specifically Xorg) runs on the uConsole. The default Pi OS desktop until Bookworm, X11 is well-supported and well-understood. On the uConsole’s stock kernel:

  • The Xorg server uses the modesetting driver (pure DRM/KMS-based).
  • Xorg starts via lightdm or gdm3 from systemd.
  • Window manager: mutter (GNOME default) or fluxbox, i3, anything that runs on Xorg.

X11’s strengths on the uConsole:

  • GTK2 / older GNOME / older Qt apps work. Ancient TUIs and bespoke graphing tools just work.
  • xinput for input device tuning — granular trackball / keyboard configuration.
  • Network transparencyssh -X works, you can run a GUI app from a remote box on your local screen.

X11’s weaknesses:

  • Tearing. Without compositor work, xterm scrolling tears visibly.
  • HiDPI is ad-hoc. You can scale, but it’s per-application.
  • High idle CPU. Xorg + a compositor + a window manager idles at ~3% CPU when nothing’s happening, vs ~0.5% on Wayland.

4.4 Wayland on the uConsole

Wayland is the default on Pi OS Bookworm and later. The compositor on Pi OS Bookworm is wayfire; on Bookworm-Lite it’s whatever you install (sway is popular).

Wayland’s strengths on the uConsole:

  • Vsync everywhere. No tearing.
  • HiDPI consistent. The compositor scales the whole desktop; apps follow.
  • Low idle CPU. ~0.5% with wayfire.
  • Better security model. Apps can’t read each other’s input or screen content.

Wayland’s weaknesses:

  • ssh -X doesn’t work in the X11 sense. Need to use VNC or waypipe.
  • Some apps don’t run — older GTK2, some Qt5 apps that hardcode X11 paths.
  • Input device tuning is per-compositor. The xinput of the X11 world is replaced by compositor-specific config files.

4.5 Compositor-specific gotchas

Per-compositor notes:

CompositoruConsole behaviour
wayfireDefault on Pi OS Bookworm. Works out of the box. Screen capture: wf-recorder.
swayPopular for power-users. Tile-only; no floating windows by default. Good battery life.
gnome-shellWorks but heavyweight. Idles at ~5% CPU. Not recommended for handheld use.
kwin_waylandPlasma’s compositor. Fully featured but heavy.
cosmic-compSystem76’s compositor. Still maturing in 2026.
westonReference compositor; minimal. Useful for testing the underlying stack.

For the uConsole as a handheld pen-test / SDR / writing device, sway or wayfire are the right answers. Heavy compositors (gnome-shell, kwin) are overkill for this hardware class.

5. Backlight Control

The screen has two PWM-driven brightness paths and they don’t always coordinate. Worth understanding before you “fix” the brightness.

5.1 Two PWM sources, one knob

The JD9365DA-H3 panel has its own internal PWM that controls the LED backlight intensity. The mainboard also has a PWM signal exported from the BCM2711’s PWM0 channel, routed through a level-shifter, that goes to the panel’s LEDPWM input.

So the actual brightness is: (host PWM duty) × (panel internal PWM duty) × (LED max current).

In normal operation, the panel-internal PWM is set to a fixed maximum (during the init sequence), so changing brightness means changing the host PWM duty. This is what brightnessctl does.

5.2 The keyboard MCU’s role

The keyboard backlight (the white LEDs under each key) is on a separate PWM, this one driven by the GD32F103 keyboard MCU. It is independent of the screen backlight and uses the keyboard MCU’s GPIO PWM peripheral. From the host side, the keyboard backlight is controllable via the GD32 firmware’s USB control endpoint — there’s a small command set (set brightness, get brightness, fade) — and Clockwork ships a userspace tool (uconsole-kbd-bl) that wraps these into shell-callable commands.

5.3 Linux interfaces (sysfs, brightnessctl)

Once the device-tree overlay is loaded, the screen backlight appears under /sys/class/backlight/:

$ ls /sys/class/backlight/
clockwork-uconsole-bl
$ cat /sys/class/backlight/clockwork-uconsole-bl/max_brightness
255
$ cat /sys/class/backlight/clockwork-uconsole-bl/brightness
128
$ echo 200 | sudo tee /sys/class/backlight/clockwork-uconsole-bl/brightness

For higher-level convenience, brightnessctl:

sudo apt install brightnessctl

brightnessctl get          # current brightness (raw value)
brightnessctl info         # all backlights + current/max
brightnessctl set 50%      # set to 50% of max
brightnessctl set +10%     # increase by 10%
brightnessctl set 5%-      # decrease by 5%

brightnessctl writes to the same sysfs interface but handles the “absolute” vs “relative” semantics cleanly. It’s the recommended tool.

For the keyboard backlight (if you have it):

# Older Clockwork tool:
sudo uconsole-kbd-bl --brightness 100      # 0-255

# Newer (kernel-level) interface, if Rex's image exposes it:
echo 100 | sudo tee /sys/class/leds/clockwork-uconsole-kbd::brightness

The keyboard backlight is not on /sys/class/backlight/ — it’s on /sys/class/leds/. Different kernel subsystem.

5.4 The “screen too dim” community gripe

Several community posts complain about the uConsole’s screen being too dim under bright sunlight. Some context:

  • The IPS panel’s max nominal brightness is ~300 nits.
  • That is normal for a budget IPS panel from this era.
  • Sunlight readability requires 700+ nits, which is in different-class panel territory.

If the screen seems unusually dim indoors (not just sun-washed), check:

  1. Software brightness: brightnessctl get — if it reports < max_brightness, raise it.
  2. Panel-internal PWM: if the panel’s own PWM has been set low by a malformed init sequence, you’ll see a low maximum even at host PWM 100%. Re-flash a known-good kernel.
  3. Backlight LED string degradation: after 5+ years, LED strings dim. This is hardware aging; replacement panel modules are available.

The keyboard-backlight version of this gripe is “the keyboard backlight is useless.” It is not very bright; this is by design (battery life trade-off). You can boost it via the uconsole-kbd-bl tool or by patching the GD32 firmware to allow higher PWM duty cycles (community mod, Vol 11).

6. The Keyboard Subsystem

The 74-key gamepad-style keyboard is one of the uConsole’s distinguishing features. Linux sees it as a USB HID keyboard. This chapter is the Linux-side reference.

6.1 GD32F103 firmware

The GD32F103R8T64 is an ARM Cortex-M3 @ 72 MHz with 64 KB Flash and 20 KB SRAM, in an LQFP-64 package. It runs Clockwork’s open-source firmware (Code/uconsole_keyboard/ in the Clockwork repo, MIT-licensed). The firmware does:

  1. Scan the 5×16 key matrix every 5 ms (200 Hz scan rate).
  2. Debounce key events (10 ms typical hold-down requirement).
  3. Generate USB HID report packets matching the standard 8-byte boot keyboard format + 8 bytes for modifier-keys-as-bitmap.
  4. Drive the backlight LED PWM from a brightness register.
  5. Receive USB control transfers for backlight commands.
  6. Implement a serial/console mode (an unused-by-default debug interface that exposes a tiny shell).

The keyboard board has its own LM1117S-3.3 LDO for the GD32’s 3.3V supply (Vol 2 §8). The USB connection is through the GL850G hub on the mainboard.

Re-flashing the GD32 firmware is possible via SWD (Single-Wire Debug) but requires a JTAG adapter and physically opening the case. Most users never re-flash.

6.2 The USB HID descriptor

The keyboard reports as USB VID 0x28e9 (GigaDevice generic) PID 0x0289 (or a Clockwork-assigned PID — varies by firmware revision). The HID descriptor declares:

  • One Report ID 1 — boot-style 8-byte keyboard (modifier byte + 6 keycodes + reserved).
  • One Report ID 2 — extended modifier keys (Function key, Ctrl-Function, Alt-Function chord codes).
  • Possibly: one Report ID 3 — Consumer Control (volume, brightness, media keys mapped from Function chords).

Linux’s usbhid driver picks this up automatically. You can inspect the device:

$ lsusb | grep -i clockwork
Bus 001 Device 003: ID 28e9:0289 GDMicroelectronics ClockworkPi uConsole Keyboard

$ udevadm info --query=all --name=/dev/input/event4 | head -20
P: /devices/platform/scb/fd500000.pcie/pci0000:00/0000:00:00.0/0000:01:00.0/usb1/1-1/1-1.4/1-1.4:1.0/0003:28E9:0289.0001/input/input4/event4
N: input/event4
S: input/by-id/usb-ClockworkPi_uConsole_Keyboard-event-kbd
S: input/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0-event-kbd
E: ID_BUS=usb
E: ID_INPUT=1
E: ID_INPUT_KEY=1
E: ID_INPUT_KEYBOARD=1
E: ID_VENDOR=ClockworkPi
E: ID_VENDOR_ID=28e9
E: ID_MODEL=uConsole_Keyboard
E: ID_MODEL_ID=0289

The 0003:28E9:0289 in the path is the HID-quirks-required identifier — 0003 is the HID protocol class, then VID:PID. The Clockwork patch adds an entry to hid-quirks.c so the kernel knows to apply specific quirks (mostly: don’t be confused by the F-key chord codes).

6.3 Linux input layer mapping

Linux exposes input devices as event nodes under /dev/input/event*. The keyboard appears as event4 (number varies). The kernel’s input layer translates HID-level keycodes to Linux’s evdev keycodes. Most keys map cleanly:

Physical keyHID code (hex)Linux evdev code
A0x04KEY_A (30)
Enter0x28KEY_ENTER (28)
LeftShift0xE1KEY_LEFTSHIFT (42)
Fn (Clockwork)0x65 (vendor)KEY_FN (464)
Esc0x29KEY_ESC (1)

The “Fn” key is the most non-standard part — it’s used to access the F-key row (F1-F12 are accessed as Fn+1, Fn+2, etc.) and the volume/brightness shortcuts. Linux’s input layer handles this via the KEY_FN keycode plus key-chord remapping in user-space.

6.4 Custom keymaps via xkb

Under X11, the X Keyboard Extension (XKB) is the canonical tool. The Clockwork keyboard is detected as a generic 105-key PC keyboard by default; you may want to remap.

A simple example — swap Caps Lock and Esc (the vim user’s first edit):

# Add to ~/.xinitrc or similar:
setxkbmap -option caps:swapescape

# Or system-wide via /etc/default/keyboard:
XKBMODEL="pc105"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS="caps:swapescape"

For richer customisation, xmodmap:

# Read current map:
xmodmap -pke > my_keymap.txt

# Edit my_keymap.txt to add custom mappings.

# Apply:
xmodmap my_keymap.txt

Or for full custom layouts, write an XKB rule fragment in /usr/share/X11/xkb/symbols/uconsole — out of scope for this volume but documented in the X.org wiki.

6.5 Custom keymaps via Wayland

Under Wayland, each compositor has its own keymap config, but they all use the same XKB syntax internally. For wayfire:

# ~/.config/wayfire.ini
[input]
xkb_layout = us
xkb_options = caps:swapescape

For sway:

# ~/.config/sway/config
input "type:keyboard" {
    xkb_layout us
    xkb_options caps:swapescape
}

# To find your keyboard's identifier:
swaymsg -t get_inputs | jq '.[] | select(.type=="keyboard")'

6.6 The keyboard backlight LEDs

The white LEDs under the keys are PWM-driven by the GD32 MCU. From Linux:

  • Older interface: uconsole-kbd-bl --brightness N (where N is 0-255).
  • Newer kernel-level interface (Rex’s images): /sys/class/leds/clockwork-uconsole-kbd::brightness.

Brightness levels:

SettingBacklight perception
0Off
64Just visible in a dark room
128Comfortable indoor
192Bright for night use
255Maximum (still not very bright)

The community gripe “keyboard backlight is too dim” is real and stems from a battery-life-vs-brightness trade-off in the firmware. To boost it requires SWD-flashing the GD32 with custom firmware (the source is open).

6.7 Common keyboard issues matrix

SymptomLikely causeFix
Specific key requires hard press (e.g., T)Production variation in switch quality, or hidden key-cap obstructionFirst: clean (compressed air, IPA on a Q-tip). If persistent: replace keyboard PCB.
Multiple keys fire from one pressDebounce issue or dirty switchClean. If persistent: GD32 firmware has a tunable debounce — re-flash if you can.
Stuck modifier (Shift won’t release)Firmware bug or stuck switchsetkeycodes reset; or re-flash GD32 firmware
Fn chord doesn’t workQuirk not appliedVerify HID quirks loaded: cat /sys/kernel/debug/hid/0003:28E9:*/rdesc
Keyboard not detected at allUSB hub or LDO failurelsusb — if no Clockwork device, hardware fault on keyboard PCB
Backlight doesn’t respondGD32 firmware version too old, or uconsole-kbd-bl not installedUpdate firmware; install tool
Symbols on different keys than printedXKB layout mismatchsetxkbmap us (or whatever your physical layout is)
Keyboard works in console but not in XX11 input device hot-plugged after Xorg startxinput list; if missing, restart Xorg

7. The Trackball

The right side of the keyboard has a small mechanical trackball with two buttons (left- and right-click). It’s mounted on the keyboard PCB and shares the GD32’s USB endpoint.

7.1 Sensor and buttons

The trackball is an optical-mouse-style sensor (typically a PixArt PAW32xx or similar) reading the rotation of a small ball. The GD32 firmware reads the X/Y motion deltas from the sensor at ~125 Hz (8 ms intervals) and packages them into a HID Mouse report alongside the keyboard scancodes.

Linux sees the trackball as a separate input device from the keyboard, even though they share the same physical USB endpoint. From xinput list:

⎜   ↳ ClockworkPi uConsole Keyboard      id=10  [slave  pointer  (2)]
⎜   ↳ ClockworkPi uConsole Keyboard      id=11  [slave  keyboard (3)]

The mouse-class (id=10) has the trackball; the keyboard-class (id=11) has the keys.

7.2 The “janky trackball” community gripe

The trackball’s reputation in the community is “janky.” Specifics:

  • Pickier than expected. The optical sensor needs the ball to be clean and the ball-cup to be free of dust. Pocket lint accumulates fast.
  • Tracking inconsistency. Slow motions sometimes don’t register; fast motions over-shoot.
  • Mechanical wear. The ball-cup’s plastic deforms slightly over heavy use; community reports say after a year of daily use, replacement is recommended.

The factory tracking sensitivity is fairly low. xinput-tunable in software (next §). For mechanical mitigation, regular cleaning (~weekly, with a Q-tip and isopropyl alcohol) keeps it usable. Replacement trackball assemblies are sold by Clockwork and several aftermarket vendors (Talking Sasquach mentions community 3D-print upgrades).

7.3 Tuning sensitivity in xinput / Wayland

Under X11:

# List the trackball:
xinput list | grep -i clockwork

# Find the device id (e.g., 10):
xinput list-props 10

# Set acceleration profile (0=flat, 1=adaptive):
xinput set-prop 10 "libinput Accel Profile Enabled" 1, 0  # adaptive

# Set acceleration speed (-1.0 to 1.0):
xinput set-prop 10 "libinput Accel Speed" 0.5

Save in ~/.xinitrc or a script that runs on session start.

Under Wayland (wayfire):

# ~/.config/wayfire.ini
[input]
mouse_accel_profile = adaptive
mouse_accel_speed = 0.5

Under sway:

# ~/.config/sway/config
input "type:pointer" {
    accel_profile adaptive
    pointer_accel 0.5
}

7.4 Replacement trackball options

Three paths if the stock trackball is unsatisfactory:

  1. Clean and re-grease. The cheap fix. Disassemble, clean ball + cup with IPA, apply a tiny dab of light oil. Buys 6-12 months.
  2. Aftermarket trackball. Several Tindie sellers offer drop-in replacement trackball assemblies with better optical sensors. Cost: $15-30. Talking Sasquach (Vol 7 community notes) calls some of these worthwhile.
  3. External pointing device. Use a USB mouse or trackpad via the USB-A port. Loses the integrated form factor but gains a real pointing device. Best for desk use; impractical for handheld.

8. The Audio Path (Linux-Side)

The audio path’s hardware story (BCM2711 PWM-audio → AS4729 routing → OCP8178 amp → speakers) is in Vol 2 §7. This chapter is the Linux-side: how the kernel exposes audio, what ALSA looks like, what the realistic quality story is.

8.1 ALSA card numbering on the uConsole

ALSA enumerates audio devices as “cards.” On a uConsole running Pi OS, you typically see:

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]
  Subdevices: 7/7
  Subdevice #0: subdevice #0
  ...
card 1: vc4hdmi0 [vc4-hdmi-0], device 0: MAI PCM i2s-hifi-0 [MAI PCM i2s-hifi-0]
  Subdevices: 1/1

Card 0 is the PWM-audio path — the BCM2711’s “headphone” output, which is what the uConsole’s speakers are connected to. Despite being labelled “Headphones,” this is the path to the OCP8178 amps and the speakers. There is no separate “Speaker” card — audio in / out goes via card 0.

Card 1 is the HDMI audio path (only when HDMI is connected). You can ignore this for most uConsole loadouts.

If a USB audio device is plugged in, it gets a new card number (typically card 2 or 3).

8.2 The PWM-audio driver

The bcm2835-audio driver in the Linux kernel implements PWM-audio. The driver:

  1. Takes incoming PCM samples from ALSA.
  2. Runs them through a delta-sigma modulator (in software for low rates, GPU-assisted for higher rates).
  3. Outputs the modulated bitstream as a PWM duty cycle on a GPIO pin.
  4. The GPIO is filtered through the AS4729 → OCP8178 → speaker chain (Vol 2 §7).

This is a simple, clever hack from 2012’s Pi 1 era that has aged into “the Pi audio path.” It works. The quality is bounded by the PWM resolution and the analog filter — see §8.3.

8.3 Sample rate, depth, and the realities of PWM-audio

Quoted spec on the Pi PWM-audio path:

ParameterValue
Sample rate44.1 kHz / 48 kHz
Bit depth11 bits effective
Dynamic range~50 dB SNR
Frequency response50 Hz – 18 kHz (at -3 dB)

Reality (what you actually hear):

  • Speech, podcasts, soft music: fine. Listenable.
  • Bass-heavy music: thin. The speaker enclosures are small and the bass has little air to move.
  • High-fidelity music (chamber music, jazz horns, etc.): listenable but not satisfying. The 50 dB SNR is well below CD-quality (96 dB).
  • Synth music from chiptune-era games: surprisingly good. PWM-audio aliasing isn’t a problem for square-wave-heavy material.

Practical implication: if audio quality matters for your loadout (music production, ham-radio digital-mode listening), use a USB DAC instead of the on-device speakers. A $30 SaPlace or $80 Schiit Modi will sound enormously better than the built-in path.

For most loadouts (terminal beep, alarm sounds, occasional notification), the built-in path is fine.

8.4 ALSA → PulseAudio → PipeWire stack

Linux audio stacks layered on ALSA:

LayerRoleStatus on Pi OS
ALSAKernel-level audio driver + libraryAlways present
PulseAudioUserland audio server, mixer, app routingDefault on Pi OS Bookworm and earlier
PipeWireModern replacement for PulseAudioDefault on Pi OS Trixie and later
JACKPro-audio low-latency serverOptional; for music production

PulseAudio and PipeWire both expose ALSA cards as “sinks” and “sources” with per-app volume control, mixer routing, automatic device-switching. PipeWire additionally supports JACK clients, so a PipeWire-equipped uConsole can run pro-audio apps.

The transition from PulseAudio to PipeWire is largely painless — pavucontrol (the GUI mixer) works on both via a compatibility shim. For most users, “your audio works” is the same on either.

For a music-production loadout (Vol 1 §6 community archetype):

  • PipeWire + qjackctl for low-latency audio routing.
  • LMMS, SunVox, Cardinal, etc. as JACK clients via PipeWire’s compatibility layer.
  • Community report: PipeWire’s latency on the BCM2711 is ~5-10 ms with quantum=512, which is acceptable for non-real-time work and tolerable for tracker-style live sequencing. Real-time monitoring (singing into a mic and hearing it in headphones) is not realistic without a USB audio interface.

8.5 The microphone path

The uConsole has a small electret microphone on the keyboard board, routed through the AS4729’s analog input to the BCM2711’s PWM-audio input (which uses a delta-sigma converter in reverse for input). The microphone is exposed as ALSA card 0 device 0 capture:

arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: Headphones [bcm2835 Headphones], device 0: bcm2835 Headphones [bcm2835 Headphones]

# Test recording:
arecord -D plughw:0,0 -f cd -d 5 test.wav
aplay test.wav

Quality is mediocre — the microphone is unidirectional and far from your mouth, and the analog signal chain isn’t optimised for microphone-class input. Use cases that work: voice memos, simple voice commands. Use cases that don’t: video conferencing without an external mic, voice recording for content creation.

For real microphone work, a USB headset or a USB lavalier is mandatory.

8.6 Common audio issues matrix

SymptomLikely causeFix
No sound at alldtparam=audio=on missing or audio chip not initialisedCheck config.txt; reboot. Check `dmesg
Sound at very low volumeALSA mixer or PulseAudio mixer set lowalsamixer, pavucontrol — raise master.
Crackling / clippingPulseAudio buffer too small, or excessive system loadAdjust default-sample-rate in /etc/pulse/daemon.conf; or move to PipeWire.
Sound only on HDMI, not built-inWrong default cardpavucontrol Output Devices → set Headphones as default. Or pacmd set-default-sink.
Hum / buzzGround loop or charger PSU noiseTry battery-only operation. If clean, charger PSU is the culprit.
Stutter when CPU loadedAudio thread starved; PWM-audio is CPU-drivenUse chrt -f -p 50 $(pidof pulseaudio) to raise priority.
Microphone records silenceALSA capture muted (default)alsamixer → F4 (capture view) → unmute mic.
USB audio device not switched toPulseAudio not auto-switching on hot-plugpacmd set-default-sink to the USB device; or enable module-switch-on-connect.

9. Display Power Management

A handheld with 2-4 hour battery life lives or dies by good DPMS (Display Power Management Signaling). This chapter is what’s available and how to use it.

9.1 DPMS modes

Standard X11 / DRM DPMS modes:

ModeBacklightPixel data sent?Power savedWake-up time
OnYesYes0%0 ms
StandbyYesNo~5%<1 ms
SuspendNoNo~30%~50 ms
Off (DPMS)NoNo~35%~200 ms

Trigger via X11:

xset dpms force off       # Power off display
xset dpms force on        # Power back on
xset s 60                 # Screen blank after 60 seconds idle
xset dpms 600 900 1200    # Standby/Suspend/Off after these many seconds

Under Wayland, the compositor controls DPMS. For wayfire:

[output:DSI-1]
mode = 1280x720@60

For sway:

swaymsg "output DSI-1 dpms off"
swaymsg "output DSI-1 dpms on"

9.2 The systemd-logind path

Modern Linux uses systemd-logind to coordinate power states. It listens for lid-close, power button, and idle events and triggers actions defined in /etc/systemd/logind.conf:

[Login]
HandlePowerKey=suspend       # Press power button → suspend
HandleLidSwitch=suspend       # Lid close → suspend (no lid on uConsole, but…)
IdleAction=suspend
IdleActionSec=30min

The uConsole has no lid switch and the power button is wired to the AXP228 directly (Vol 2 §3.5), so HandlePowerKey= is what controls the soft-power-off behaviour. Setting it to ignore lets you handle the power button in your own script (useful for “press to dim, hold to suspend, very-long-hold to power off” custom logic).

9.3 Custom blank-and-suspend scripts

A simple “blank screen + reduce CPU + sleep” script:

#!/bin/bash
# /usr/local/bin/uconsole-low-power.sh

# Dim screen
brightnessctl set 5%

# Lower CPU governor
echo powersave | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

# Disable WiFi
sudo nmcli radio wifi off

# Disable Bluetooth
sudo rfkill block bluetooth

Bind to a Fn-chord on the keyboard via xbindkeys (X11) or your compositor’s keybindings (Wayland). Battery life with this active is roughly 6+ hours — more than double the typical “idle, screen on” runtime.

For deeper sleep (suspend-to-RAM), systemctl suspend. But: the AXP228’s idle current in suspend is ~60 mA — non-negligible. For overnight storage, a real shutdown (systemctl poweroff) is more battery-friendly.

10. Rockchip Kernel Differences (Radxa CM5)

If you’ve installed a Radxa CM5, the kernel story changes. Most user-visible behaviour is the same; the implementation underneath is different.

10.1 Different DSI controller

The RK3588S2 has a Rockchip-designed DSI controller, not the Broadcom one. The JD9365DA-H3 panel driver still works (the panel is the same — only the controller is different) but the device-tree entries change:

&dsi0 {
    status = "okay";
    rockchip,lane-rate = <445>;

    panel: panel@0 {
        compatible = "clockworkpi,uconsole-lcd";
        reg = <0>;
        // ... same as before
    };
};

The &dsi0 reference is to a different node in the device tree (Rockchip’s, not Broadcom’s), and the rockchip,lane-rate property is Rockchip-specific. If you’ve ported the panel driver from Pi OS to Armbian, you make these adjustments.

10.2 Different ALSA setup

The RK3588S2 has an on-die I²S controller. The uConsole’s mainboard wires the OCP8178/AS4729 audio chain to the I²S bus instead of using PWM-audio. ALSA card numbering shifts:

  • Card 0: rk3588s2-i2s (the audio path; hardware sends bit-stream over I²S, OCP8178 decodes it).
  • Card 1: HDMI audio (when connected).
  • USB cards as before.

Audio quality is significantly better than PWM-audio — full 16-bit / 48 kHz nominal, 80+ dB SNR. The Radxa-CM5 path is the audio-quality upgrade path for users who care.

10.3 Different keyboard PHY enumeration

Same keyboard hardware (GD32F103), same USB HID protocol. But the GL850G hub is on a different USB host root on the Rockchip side, so the device path changes:

# Pi side path:
/devices/platform/scb/fd500000.pcie/.../usb1/1-1/1-1.4/...

# Rockchip side path:
/devices/platform/usb@fc800000/...

udev rules that match by path will need updating; rules matching by VID:PID work unchanged.

10.4 The DTS-porting checklist

If you’re authoring a Radxa-CM5 uConsole image:

  1. Panel driver: copy the JD9365DA-H3 driver source from the Pi clockwork-kernel tree.
  2. Panel device tree: rewrite the overlay for Rockchip DSI references (§10.1).
  3. Audio device tree: wire the RK3588S2’s I²S controller to the AS4729/OCP8178 chain.
  4. Backlight: Rockchip’s PWM controller is at a different MMIO address; update the pwms = <&pwm 0 ...> reference.
  5. Power tree: the AXP228 is still on the mainboard, but the I²C controller it sits on is different (different bus number).
  6. Boot config: make sure U-Boot loads rk3588s2-uconsole.dtb (the right DTB filename).

This is non-trivial work — typically a weekend for a kernel developer who’s done it before, longer for a first-timer. The community is starting to build this up in the Armbian Radxa CM5 patches.

11. Canonical OS Image Kernels

This chapter is the practical “where does my kernel come from” reference.

11.1 Rex’s Bookworm / Trixie images

Maintained by community member “Rex” on the ClockworkPi forum:

  • Bookworm 6.6.y for uConsole/DevTerm: https://forum.clockworkpi.com/t/bookworm-6-6-y-for-the-uconsole-and-devterm/13235
  • Trixie 6.12.y for uConsole/DevTerm: https://forum.clockworkpi.com/t/trixie-6-12-y-for-the-uconsole-and-devterm/19457

Both are full Pi-OS-derived images with Clockwork patches pre-applied. The kernel is rebuilt periodically when Rex pulls in new Pi-Foundation kernel updates. If you want “the canonical CM5 path on the uConsole,” these are the images to use.

11.2 CrossPlatformDev CI builds

https://github.com/crossplatformdev/uConsole-Image-Builder/releases — automated CI builds of Jammy/Bookworm/Trixie for CM3/CM4/CM5 with multiple desktop environments. Useful if you want a specific DE pre-installed (KDE Plasma, XFCE, MATE, etc.). Built on top of the Clockwork kernel patches, so the panel and keyboard work out of the box.

11.3 Building your own from source

If neither of the above gives you what you want — say, you want a custom XFCE-Lite image with a hand-tuned kernel config and minimal package set — the path:

  1. Start from a base image (Pi OS Lite or Debian Bookworm minimal).
  2. Apply the Clockwork patches per §2.3 of this volume.
  3. Customise package set with apt.
  4. Add your custom config files.
  5. dd the result to an SD/eMMC.

pi-gen (Pi-Foundation’s image-build tool) is the standard scaffolding for this — https://github.com/RPi-Distro/pi-gen. Allows scriptable image creation.

Building a full image takes ~45 minutes on a fast workstation. The Clockwork-patches step is the only uConsole-specific bit; everything else is standard Debian/Pi image construction.

12. Vol 12 Cheatsheet Updates

The following one-pagers go into Vol 12:

  • §6 (Display): brightnessctl get/set, DPMS toggles (xset dpms, swaymsg output dpms), modeline reference.
  • §8 (Keyboard): xkb and wayfire/sway keymap snippets, the common keyboard issues matrix from §6.7.
  • §9 (Audio): alsamixer, pavucontrol, the audio issues matrix from §8.6, the microphone test command (arecord -D plughw:0,0 -f cd -d 5 test.wav).
  • §10 (Trackball): tuning commands for xinput / wayfire / sway.
  • §13 (Display power management): the DPMS table from §9.1, the low-power-mode script from §9.3.
  • §15 (Error → fix matrix): the LCD failure modes from §3.6, the keyboard issues from §6.7, the audio issues from §8.6.

13. Resources

SourceURL
Clockwork uConsole repo (kernel patches)https://github.com/clockworkpi/uConsole
Pi clockwork-patched kernelhttps://github.com/clockworkpi/RaspberryPi-linux-clockworkpi
Pi device-tree documentationhttps://www.raspberrypi.com/documentation/computers/configuration.html#device-trees-overlays-and-parameters
Linux DRM/KMS overviewhttps://docs.kernel.org/gpu/drm-uapi.html
Wayland (wayfire)https://github.com/WayfireWM/wayfire
Wayland (sway)https://swaywm.org/
Pi OS image build (pi-gen)https://github.com/RPi-Distro/pi-gen
Rex’s Bookworm uConsole image (forum)https://forum.clockworkpi.com/t/bookworm-6-6-y-for-the-uconsole-and-devterm/13235
Rex’s Trixie uConsole image (forum)https://forum.clockworkpi.com/t/trixie-6-12-y-for-the-uconsole-and-devterm/19457
CrossPlatformDev image builderhttps://github.com/crossplatformdev/uConsole-Image-Builder/releases
Armbian Radxa CM5 pagehttps://www.armbian.com/radxa-cm5/
brightnessctlhttps://github.com/Hummer12007/brightnessctl
xkb documentationhttps://wiki.archlinux.org/title/X_keyboard_extension
GD32F103 datasheet (GigaDevice)https://www.gigadevice.com/products/microcontrollers/gd32-arm-cortex-m3/
JD9365DA-H3 datasheet02-inputs/datasheets/JD9365DA-H3_DS_V0.01_20200819.pdf (also 03-outputs/datasheets/)
ALSA documentationhttps://www.alsa-project.org/wiki/Main_Page
PipeWire documentationhttps://pipewire.org/

14. Footnotes

(Footnotes are listed inline above; this section is a placeholder anchor for the index.)

15. Index

A — ALSA — §8 (full chapter). Audio issues matrix — §8.6. AXP228 patches — §2.1.

B — Backlight (screen) — §5. Backlight (keyboard) — §6.6. bcm2835-audio driver — §8.2. brightnessctl — §5.3. Buttons (trackball) — §7.1.

C — cmdline.txt (cross-ref Vol 4) — §3.6. Compositor — §4.5. Compatible string (panel) — §3.3. clockwork-uconsole-lcd overlay — §3.4. Cross-platformdev CI — §11.2.

D — DCS commands — §3.2. Debounce — §6.7. Device tree overlay — §3.4, §10. DPMS — §9.1. DRM/KMS — §4.2. DSI controller (Pi) — §3.3. DSI controller (Rockchip) — §10.1. dtoverlay= — §3.4 (cross-ref Vol 4). DLDOs — Vol 2 cross-ref.

E — enable() (DRM) — §3.3. Event nodes (/dev/input/event*) — §6.3.

F — fbdev — §4.1. fbi — §4.1. Framebuffer — §4.1. Function key (Fn) — §6.3, §6.7.

G — Gamma curve — §3.2. GD32F103 — §6.1. GL850G — §6.2 (cross-ref Vol 2 §9). GNOME — §4.5.

H — HID descriptor — §6.2. HID quirks — §2.1, §6.2.

I — init sequence (panel) — §3.2. Input layer (Linux) — §6.3. IPS panel — §3.1. I²S (Radxa) — §10.2.

J — JACK — §8.4. JD9365DA-H3 — §3 (full chapter).

K — Kernel build — §2.3. Kernel patches — §2 (full chapter). Keyboard — §6 (full chapter). Keyboard backlight — §6.6.

L — LM1117S-3.3 — §6.1 (cross-ref Vol 2 §8). LED PWM — §5.1. Lid switch (none) — §9.2.

M — Microphone — §8.5. Modeline — §3.5.

N — Native resolution — §3.5.

O — OCP8178 — Vol 2 cross-ref, §8.2. Open-source firmware (GD32) — §6.1. Overlay — §3.4.

P — Panel driver — §3.3. PCF85063A (cross-ref Vol 7) — §3.6 (recovery context). PinePhone — N/A. PipeWire — §8.4. PulseAudio — §8.4. PWM-audio — §8.2, §8.3. PWM-backlight — §5.

Q — qjackctl — §8.4.

R — RESET (panel) — §3.2. Rex’s images — §11.1. Rockchip — §10 (full chapter). RP2040 — N/A. RTC (cross-ref Vol 4) — §9.

S — Sample rate — §8.3. Schiit Modi — §8.3. SDXC — N/A. setxkbmap — §6.4. SNR — §8.3. Sound issues — §8.6. sway — §4.5, §6.5. systemd-logind — §9.2.

T — T-key (community gripe) — §6.7. Trackball — §7 (full chapter). TrustZone — N/A this volume.

U — uconsole-kbd-bl — §5.3, §6.6. USB DAC — §8.3. USB HID — §6.2.

V — vc4hdmi0 (HDMI audio) — §8.1. Vsync — §3.6.

W — wayfire — §4.4, §6.5. Wayland — §4.4, §6.5.

X — X11 — §4.3. XKB — §6.4. xinput — §7.3.

Y, Z — None.

Footnotes

  1. Raspberry Pi device-tree documentation: https://www.raspberrypi.com/documentation/computers/configuration.html#device-trees-overlays-and-parameters. Lists the overlay system and the syntax for dtparam= and dtoverlay= in config.txt.

  2. The patches live in the Clockwork uConsole repo: https://github.com/clockworkpi/uConsole. The relevant subdirectories are Code/uconsole_kernel/ (kernel module sources and DTS overlays) and Code/uconsole_keyboard/ (GD32 firmware sources). The patches are released under GPLv2 (matching the upstream Linux kernel licence).

  3. JD9365DA-H3 datasheet: 02-inputs/datasheets/JD9365DA-H3_DS_V0.01_20200819.pdf. User guide: 02-inputs/datasheets/JD9365DA-H3_User_Guide_Preliminary_V0.00_20200827.pdf. Both shipped with the Clockwork uConsole repo at https://github.com/clockworkpi/uConsole. Note the version stamp — these are early datasheet revisions; some details may differ from the panel that actually ships, but they’re representative.

  4. GD32F103 datasheet from GigaDevice: https://www.gigadevice.com/products/microcontrollers/gd32-arm-cortex-m3/. The R8T6 SKU has 64 KB flash and 20 KB RAM — adequate for a USB HID keyboard with backlight control.