Compare commits

..

2 Commits

Author SHA1 Message Date
2f081d3e19 fix: panel-cwu50 timing fix from Rex's kernel - native 720x1280 mode
Swap H/V display mode to native panel resolution (720x1280) instead of
rotated (1280x720). The DRM/KMS pipeline handles rotation via connector
orientation property. Setting wrong horizontal resolution caused DSI
controller to send extra pixels per line, resulting in horizontal
repetition.

Add DCS-based panel detection in init_sequence2 as supplemental check.

Based on ak-rex/ClockworkPi-linux rpi-6.12.y branch panel-cwu50.c.

Replaces old 0008 patches (DSI_INIT0, BURST removal) that didn't fix
the issue.
2026-06-13 20:19:19 -04:00
5aca97e057 Merge pull request 'feat: add uConsole CM5 host with Reticulum mesh network support' (#61) from feat/uconsole-cm5-v3 into master
Reviewed-on: #61
2026-06-06 13:19:19 +00:00
6 changed files with 240 additions and 342 deletions

102
flake.nix Executable file → Normal file
View File

@@ -2,34 +2,31 @@
description = "Gortium infra flake"; description = "Gortium infra flake";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=25.11"; nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
agenix = { agenix = {
url = "github:ryantm/agenix"; url = "github:ryantm/agenix";
inputs.darwin.follows = ""; inputs.darwin.follows = "";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
lix = { lix = {
url = "git+https://git.lix.systems/lix-project/lix?ref=main"; url = "git+https://git.lix.systems/lix-project/lix?ref=main";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nixpkgs-uconsole = { nixpkgs-uconsole.url = "github:NixOS/nixpkgs/nixos-25.11";
url = "github:nixos/nixpkgs/54170c54449ea4d6725efd30d719c5e505f1c10e";
};
nixos-uconsole = { nixos-uconsole = {
url = "github:nixos-uconsole/nixos-uconsole/v1.1.0"; url = "github:nixos-uconsole/nixos-uconsole/v1.1.0";
inputs.nixpkgs.follows = "nixpkgs-uconsole"; inputs.nixpkgs.follows = "nixpkgs-uconsole";
inputs.nixos-raspberrypi.follows = "nixos-raspberrypi";
}; };
nixos-raspberrypi = { nixos-raspberrypi = {
url = "github:nvmd/nixos-raspberrypi/v1.20260317.0"; url = "github:gortium/nixos-raspberrypi/cm5-cross-v1";
inputs.nixpkgs.follows = "nixos-uconsole/nixpkgs"; inputs.nixpkgs.follows = "nixpkgs-uconsole";
}; };
}; };
outputs = { self, nixpkgs, agenix, disko, lix, nixos-uconsole, nixos-raspberrypi, ... }@inputs: outputs = { self, nixpkgs, agenix, lix
, nixpkgs-uconsole, nixos-uconsole, nixos-raspberrypi
, ... }@inputs:
let let
system = "x86_64-linux"; system = "x86_64-linux";
keys = import ./lib/keys.nix; keys = import ./lib/keys.nix;
@@ -40,20 +37,16 @@
"/etc/ssh/ssh_host_ed25519_key" "/etc/ssh/ssh_host_ed25519_key"
"/root/.age/bootstrap.key" ]; "/root/.age/bootstrap.key" ];
}; };
overlays = [ agenix.overlays.default (import ./overlays/reticulum.nix) ]; overlays = [ agenix.overlays.default ];
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system overlays; inherit system overlays;
config.allowUnfree = true; config.allowUnfree = true;
config.permittedInsecurePackages = [ config.permittedInsecurePackages = [ "openclaw-2026.3.12" ];
"openclaw-2026.3.12"
];
}; };
devShell = import ./shells/nix_dev.nix { devShell = import ./shells/nix_dev.nix {
inherit pkgs system agenix; inherit pkgs system agenix;
}; };
in in {
{
nixosConfigurations = { nixosConfigurations = {
lazyworkhorse = nixpkgs.lib.nixosSystem { lazyworkhorse = nixpkgs.lib.nixosSystem {
specialArgs = { inherit system self keys paths inputs; }; specialArgs = { inherit system self keys paths inputs; };
@@ -62,9 +55,7 @@
nixpkgs.overlays = overlays; nixpkgs.overlays = overlays;
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
nixpkgs.config.rocmSupport = true; nixpkgs.config.rocmSupport = true;
nixpkgs.config.permittedInsecurePackages = [ nixpkgs.config.permittedInsecurePackages = [ "openclaw-2026.3.12" ];
"openclaw-2026.3.12"
];
nix.package = lix.packages.${system}.default; nix.package = lix.packages.${system}.default;
} }
agenix.nixosModules.default agenix.nixosModules.default
@@ -95,24 +86,71 @@
]; ];
}; };
uConsole = nixos-uconsole.lib.mkUConsoleSystem { uconsole-cm5 = nixpkgs-uconsole.lib.nixosSystem {
variant = "cm5"; system = "aarch64-linux";
specialArgs = { inherit self keys paths inputs nixos-raspberrypi; }; specialArgs = {
inherit self keys paths inputs;
nixos-raspberrypi = nixos-raspberrypi;
isCM4 = false;
};
modules = [ modules = [
{ {
nixpkgs.overlays = overlays; nixpkgs.buildPlatform = "x86_64-linux";
nixpkgs.hostPlatform = "aarch64-linux";
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
nixpkgs.config.permittedInsecurePackages = [ boot.loader.raspberry-pi.bootloader = "kernel";
"openclaw-2026.3.12"
];
} }
disko.nixosModules.disko nixos-raspberrypi.nixosModules.nixpkgs-rpi
./hosts/uConsole/configuration.nix # Fix display timings from Rex's kernel: native 720x1280 instead of
./hosts/uConsole/hardware-configuration.nix # rotated 1280x720, plus DCS-based panel detection.
./hosts/uConsole/disko-config.nix ({ lib, ... }: {
boot.kernelPatches = [{
name = "panel-cwu50-rex-timing-fix";
patch = ./patches/0008-panel-cwu50-rex-timing-fix.patch;
}];
})
nixos-raspberrypi.nixosModules.raspberry-pi-5.base
nixos-raspberrypi.lib.inject-overlays
nixos-raspberrypi.lib.inject-overlays-global
nixos-uconsole.nixosModules.uconsole-cm5
({ config, lib, pkgs, inputs, ... }: let
lix-cross = import inputs.nixpkgs-uconsole {
localSystem = { system = "x86_64-linux"; };
crossSystem = { system = "aarch64-linux"; };
overlays = [ inputs.lix.overlays.default ];
};
in { nix.package = lix-cross.lix; })
agenix.nixosModules.default
./hosts/uconsole-cm5/configuration.nix
./hosts/uconsole-cm5/hardware-configuration.nix
]; ];
}; };
}; };
devShells.${system}.default = devShell; devShells.${system}.default = devShell;
packages.${system} = {
uconsole-cm5-image = (nixos-raspberrypi.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = {
inherit self keys inputs;
nixos-raspberrypi = nixos-raspberrypi;
isCM4 = false;
};
modules = [
{
nixpkgs.buildPlatform = system;
nixpkgs.hostPlatform = "aarch64-linux";
}
nixos-raspberrypi.nixosModules.nixpkgs-rpi
nixos-raspberrypi.nixosModules.raspberry-pi-5.base
nixos-raspberrypi.lib.inject-overlays-global
nixos-raspberrypi.nixosModules.sd-image
nixos-uconsole.nixosModules.uconsole-cm5
agenix.nixosModules.default
./hosts/uconsole-cm5/configuration.nix
];
}).config.system.build.sdImage;
};
}; };
} }

125
hosts/uConsole/configuration.nix Executable file → Normal file
View File

@@ -1,19 +1,5 @@
{ config, lib, pkgs, paths, self, keys, ... }: { config, lib, pkgs, paths, self, keys, ... }:
let
# Backlight fallback for CM5 display quirk
# The kernel driver usually handles this, but some boots need a kick
backlightFixScript = pkgs.writeShellScript "backlight-fix" ''
# Try sysfs backlight control
for bl in /sys/class/backlight/*/brightness; do
if [ -f "$bl" ]; then
max=$(cat "$(dirname "$bl")/max_brightness" 2>/dev/null || echo 100)
echo "$max" > "$bl" 2>/dev/null || true
fi
done
'';
in
{ {
# Basic Host Info # Basic Host Info
networking.hostName = "uConsole"; networking.hostName = "uConsole";
@@ -21,37 +7,19 @@ in
i18n.defaultLocale = "en_CA.UTF-8"; i18n.defaultLocale = "en_CA.UTF-8";
# System State # System State
system.stateVersion = "25.11"; system.stateVersion = "25.05";
# Boot & Hardware (migrated to kernel bootloader per nixos-raspberrypi deprecation notice) # Boot & Hardware (migrated to kernel bootloader per nixos-raspberrypi deprecation notice)
boot.loader.raspberry-pi.bootloader = "kernel"; boot.loader.raspberry-pi.bootloader = "kernel";
# kernel managed by nixos-raspberrypi module — don't override, patches are version-specific # kernel managed by nixos-raspberrypi module — don't override, patches are version-specific
# boot.kernelPackages = pkgs.linuxPackages_latest; # boot.kernelPackages = pkgs.linuxPackages_latest;
# Kernel parameters matching nixos-uconsole CM5 module
# console=tty1 is critical — without it, console output goes to ttyAMA0 not fb0
boot.kernelParams = [
"8250.nr_uarts=1"
"console=tty1"
];
# Enable Mesa GPU drivers — REQUIRED for VC4 display pipeline to initialize
hardware.graphics.enable = true;
# Console font sized for the 5" 720x1280 display (from nixos-uconsole base module)
console = {
earlySetup = true;
font = "ter-v24n";
packages = with pkgs; [ terminus_font ];
};
# Networking # Networking
networking.networkmanager.enable = true; networking.networkmanager.enable = true;
services.openssh = { services.openssh = {
enable = true; enable = true;
# TODO: lock down after first deployment settings.PermitRootLogin = lib.mkForce "prohibit-password";
settings.PermitRootLogin = lib.mkForce "yes"; settings.PasswordAuthentication = lib.mkForce false;
settings.PasswordAuthentication = lib.mkForce true;
}; };
# User # User
@@ -88,7 +56,6 @@ in
htop htop
tmux tmux
neovim neovim
libgpiod # GPIO control (for internal USB hub, AIO modules)
# ===== HAM Radio ===== # ===== HAM Radio =====
js8call js8call
@@ -108,8 +75,7 @@ in
soapysdr-with-plugins # SoapySDR + hardware support plugins soapysdr-with-plugins # SoapySDR + hardware support plugins
# ===== Mesh / LoRa ===== # ===== Mesh / LoRa =====
# meshtastic not available in nixpkgs 25.11 stable; install manually: meshtastic # Python CLI for Meshtastic devices
# nix shell nixpkgs#meshtastic -c meshtastic
reticulumStack # Reticulum Network Stack (rnsd, rnsh, rncp, rnx, rnpath, etc.) reticulumStack # Reticulum Network Stack (rnsd, rnsh, rncp, rnx, rnpath, etc.)
lxmf # LXMF messaging protocol lxmf # LXMF messaging protocol
nomadnet # Nomad Network client nomadnet # Nomad Network client
@@ -173,33 +139,22 @@ in
}; };
# ============================================================ # ============================================================
# Kernel modules for SDR, radio, and WiFi # Kernel modules for SDR and radio
# ============================================================ # ============================================================
boot.kernelModules = [ boot.kernelModules = [
"mt7921u" # MediaTek MT7921 USB WiFi (uConsole AC1200)
"88x2bu" # Realtek 8812/8821BU USB WiFi (common adapter) "88x2bu" # Realtek 8812/8821BU USB WiFi (common adapter)
"rtl8xxxu" # RTL8188/8192/8723 USB WiFi "rtl8xxxu" # RTL8188/8192/8723 USB WiFi
"rtl2832_sdr" # RTL-SDR kernel module "rtl2832_sdr" # RTL-SDR kernel module
"dvb_usb_rtl28xxu" # RTL-SDR DVB-T "dvb_usb_rtl28xxu" # RTL-SDR DVB-T
# Display drivers — loaded AFTER RP1 PCIe southbridge init (~12s)
# NOTHING in initrd — ALL RP1 hardware is behind PCIe
"panel_cwu50" # uConsole DSI panel driver
"vc4" # VideoCore 4 KMS GPU driver
"rp1_dsi" # RP1 DSI bridge driver
]; ];
boot.blacklistedKernelModules = [ ]; boot.blacklistedKernelModules = [ ];
# Rien dans initrd pour le display — tout RP1 est derrière PCIe
boot.initrd.kernelModules = lib.mkForce [ ];
# ============================================================ # ============================================================
# Extra udev rules for SDR and HAM radio devices # Extra udev rules for SDR and HAM radio devices
# ============================================================ # ============================================================
services.udev.packages = with pkgs; [ rtl-sdr ]; services.udev.packages = with pkgs; [ rtl-sdr ];
# ============================================================ # ============================================================
# Enable IPv6 for Reticulum mesh # Enable IPv6 for Reticulum mesh
# ============================================================ # ============================================================
@@ -213,74 +168,4 @@ in
# Reticulum uses its own encryption and doesn't need open ports # Reticulum uses its own encryption and doesn't need open ports
# for basic mesh operations (peer-to-peer discovery). # for basic mesh operations (peer-to-peer discovery).
# For TCP interfaces, open additional ports as needed. # For TCP interfaces, open additional ports as needed.
# ============================================================
# Hyprland Wayland compositor (manual start)
# No SDDM — boot to console, user starts Hyprland with command
# Display modules (vc4/panel_cwu50) load late after RP1 PCIe init
# ============================================================
programs.hyprland = {
enable = true;
xwayland.enable = true;
};
# SDDM disabled — was blocking boot when display isn't ready
# services.displayManager.sddm = {
# enable = true;
# wayland.enable = true;
# };
# ============================================================
# CM5 Config.txt Fix: use [pi5] section (not [cm5])
# Rex's images use [pi5], the CM5 firmware may not detect [cm5]
# ============================================================
# Merge nixos-uconsole GPIO config with our [pi5] overrides
# GPIO 10/11 are from nixos-uconsole configtxt.nix (audio amplifier)
# [pi5] section fixes the CM5 detection issue — firmware matches [pi5] not [cm5]
hardware.raspberry-pi.extra-config = ''
[all]
gpio=10=ip,np
gpio=11=op,dh
[pi5]
dtparam=pciex1=off
dtoverlay=clockworkpi-uconsole-cm5
dtoverlay=dwc2,dr_mode=host
dtoverlay=vc4-kms-v3d-pi5,cma-384
dtparam=nohdmi1=off
'';
# ============================================================
# CM5 Display Backlight Fix
# The kernel driver initializes backlight, but some boots fail.
# This service kicks it after boot as a reliable fallback.
# ============================================================
systemd.services.cm5-backlight-fix = {
description = "CM5 Display Backlight Fix";
after = [ "multi-user.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${backlightFixScript}";
};
};
# ============================================================
# Internal USB Hub Enable (GPIO 23) — DISABLED
# This service freeze the CM5 because gpioset 0 23=1 writes
# to the wrong GPIO chip (BCM2712 native, not RP1).
# Enable manually after boot once the correct chip is confirmed:
# gpioset 0 23=1 # on chip 0 (BCM2712, CORE_VOLT or critical)
# gpioset 512 23=1 # on chip 512 (RP1, likely correct)
# ============================================================
# systemd.services.enable-gpio23-usb-hub = {
# description = "Enable Internal USB Hub (GPIO 23)";
# before = [ "network.target" ];
# wantedBy = [ "multi-user.target" ];
# serviceConfig = {
# Type = "oneshot";
# RemainAfterExit = true;
# ExecStart = "${pkgs.libgpiod}/bin/gpioset 0 23=1";
# ExecStop = "${pkgs.libgpiod}/bin/gpioset 0 23=0";
# };
# };
} }

View File

@@ -1,46 +0,0 @@
{ lib, ... }:
{
disko.devices.disk.main = {
type = "disk";
device = "/dev/mmcblk0";
content = {
type = "gpt";
partitions = {
boot = {
name = "FIRMWARE";
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot/firmware";
mountOptions = [
"fmask=0022"
"dmask=0022"
];
};
};
root = {
name = "NIXOS_UCM5";
size = "30G";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/";
mountOptions = [ "noatime" ];
};
};
home = {
name = "NIXOS_HOME";
size = "100%";
content = {
type = "filesystem";
format = "ext4";
mountpoint = "/home";
mountOptions = [ "noatime" ];
};
};
};
};
};
}

View File

@@ -9,28 +9,15 @@
boot.initrd.kernelModules = [ ]; boot.initrd.kernelModules = [ ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
# Filesystems for NixOS install. # uConsole CM5 uses NVMe or eMMC for boot storage
# mkForce overrides disko's auto-generated paths so we can use # The uconsole-cm5 module sets up /boot/firmware and default /
# filesystem labels (by-label) which work with loop device installs. # Override device label here if using different storage
# Disko will set its own paths when nixos-anywhere is used. fileSystems."/" = lib.mkDefault {
fileSystems."/" = lib.mkForce {
device = "/dev/disk/by-label/NIXOS_UCM5"; device = "/dev/disk/by-label/NIXOS_UCM5";
fsType = "ext4"; fsType = "ext4";
options = [ "noatime" ]; options = [ "noatime" ];
}; };
fileSystems."/boot/firmware" = lib.mkForce {
device = "/dev/disk/by-label/FIRMWARE";
fsType = "vfat";
options = [ "fmask=0022" "dmask=0022" ];
};
fileSystems."/home" = lib.mkForce {
device = "/dev/disk/by-label/NIXOS_HOME";
fsType = "ext4";
options = [ "noatime" ];
};
swapDevices = [ ]; swapDevices = [ ];
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux"; nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";

13
overlays/reticulum.nix Executable file → Normal file
View File

@@ -1,8 +1,7 @@
final: prev: let final: prev: let
python3 = final.python3; python3 = final.python3;
pyPkgs = python3.pkgs; pyPkgs = python3.pkgs;
in in {
{
reticulumStack = python3.pkgs.buildPythonApplication rec { reticulumStack = python3.pkgs.buildPythonApplication rec {
pname = "reticulum"; pname = "reticulum";
version = "1.2.9"; version = "1.2.9";
@@ -12,12 +11,6 @@ in
inherit version; inherit version;
sha256 = "554814231c237b9caacf8df669312e57dd7d3f84b6d4810125087d1a79a75d75"; sha256 = "554814231c237b9caacf8df669312e57dd7d3f84b6d4810125087d1a79a75d75";
}; };
patchPhase = ''
# Fix license_files syntax: ("LICENSE") is a string not tuple
# Newer setuptools iterates over it char by char, fails on 'S'
substituteInPlace setup.py \
--replace-fail 'license_files = ("LICENSE")' 'license_files = ("LICENSE",)'
'';
propagatedBuildInputs = with pyPkgs; [ cryptography pyserial ]; propagatedBuildInputs = with pyPkgs; [ cryptography pyserial ];
doCheck = false; doCheck = false;
pythonImportsCheck = [ "RNS" ]; pythonImportsCheck = [ "RNS" ];
@@ -86,7 +79,3 @@ in
}; };
}; };
} }
# meshtastic may not exist in all nixpkgs versions (e.g. not in 25.11)
// prev.lib.optionalAttrs (prev ? meshtastic) {
inherit (prev) meshtastic;
}

View File

@@ -0,0 +1,45 @@
--- a/drivers/gpu/drm/panel/panel-cwu50.c
+++ b/drivers/gpu/drm/panel/panel-cwu50.c
@@ -27,12 +27,12 @@ static const struct drm_display_mode default_mode = {
.clock = 61020,
- .hdisplay = 1280,
- .hsync_start = 1280 + 8,
- .hsync_end = 1280 + 8 + 2,
- .htotal = 1280 + 8 + 2 + 16,
- .vdisplay = 720,
- .vsync_start = 720 + 30,
- .vsync_end = 720 + 30 + 15,
- .vtotal = 720 + 30 + 15 + 15,
+ .hdisplay = 720,
+ .hsync_start = 720 + 30,
+ .hsync_end = 720 + 30 + 15,
+ .htotal = 720 + 30 + 15 + 15,
+ .vdisplay = 1280,
+ .vsync_start = 1280 + 8,
+ .vsync_end = 1280 + 8 + 2,
+ .vtotal = 1280 + 8 + 2 + 16,
};
static inline struct cwu50 *panel_to_cwu50(struct drm_panel *panel)
@@ -586,7 +586,8 @@ static int cwu50_init_sequence2(struct cwu50 *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
- int err;
+ int err;
+ u8 buf[4];
dcs_write_seq(0xE0,0x00);
@@ -633,6 +634,12 @@ static int cwu50_init_sequence2(struct cwu50 *ctx)
dcs_write_seq(0x11);// SLPOUT
msleep (200);
+ dcs_write_seq(0xE0,0x00);
+ mipi_dsi_dcs_read(dsi, 0x04, buf, 3);
+
+ if(buf[0] == 0x39) ctx->is_new_panel = 1;
+
+ dcs_write_seq(0xE0,0x00);
dcs_write_seq(0x29);// DSiPON
msleep (100);