Skip to content

Add armbian-tm16xx: front-panel display support for fd628/tm1628 (spi-gpio) boxes#3566

Open
B00StER wants to merge 1 commit into
ophub:mainfrom
B00StER:add-armbian-tm16xx
Open

Add armbian-tm16xx: front-panel display support for fd628/tm1628 (spi-gpio) boxes#3566
B00StER wants to merge 1 commit into
ophub:mainfrom
B00StER:add-armbian-tm16xx

Conversation

@B00StER

@B00StER B00StER commented Jun 22, 2026

Copy link
Copy Markdown

Add armbian-tm16xx: front-panel display support for fd628/tm1628 (spi-gpio) boxes

Problem

On Amlogic boxes whose device tree describes the front panel via the
tm1628/fd628 spi-gpio binding (e.g. X96 Max / S905X2,
meson-g12a-x96-max.dtb), the panel stays dark out of the box:

  • the DTB instantiates the SPI device (spi0.0, compatible
    fdhisi,fd628/titanmec,tm1628) but the kernel ships no driver that binds
    to it (no CONFIG_TM16XX/FD628, no module);
  • the existing armbian-openvfd path can't help: its modprobe-parameter device
    creation doesn't fire on current (6.x) kernels (so /sys/class/leds/openvfd
    never appears and it prints a misleading SUCCESS), and it targets the same
    GPIOs the DTB's spi-gpio node already claims.

Full diagnosis (evidence + reproduction) is in the companion issue #3567.

Change

Adds armbian-tm16xx (sibling of armbian-openvfd) under
build-armbian/armbian-files/platform-files/amlogic/rootfs/usr/sbin/.

It builds and DKMS-installs Jeff Lessard's maintained out-of-tree driver
jefflessard/tm16xx-display,
which binds to the existing DTB node with no device-tree changes, then
enables the upstream clock/status display-service.

armbian-tm16xx install   # build + DKMS-install + enable at boot
armbian-tm16xx status
armbian-tm16xx remove

Details handled by the script:

  • installs git/dkms/build-essential; checks kernel headers are present;
  • stages a self-contained DKMS package (/usr/src/tm16xx-1.0) — keypad omitted,
    and a trimmed Kbuild so the build doesn't pull in the other auxdisplay drivers
    the ophub kernel config enables;
  • installs modules to …/updates/ (so the bundled exporting line-display
    overrides the kernel's stub) and depmod;
  • force-loads tm16xx_spi via /etc/modules-load.d/ (the device's spi:fd628
    modalias doesn't match the module's spi:tm1628, so coldplug won't auto-load;
    it binds via OF-compatible once loaded);
  • enables display-service for the clock + status icons.

Because it's DKMS with AUTOINSTALL=yes, it rebuilds on kernel changes.

Testing

Verified on a Vontar X96 Max (S905X2), ophub Armbian, kernel 6.18.33-ophub:

  • spi0.0 driver → tm16xx-spi; /sys/class/leds/display + icon LEDs present;
  • display.service active, panel shows the clock with blinking colon;
  • survives reboots (modules auto-load + service auto-starts);
  • DKMS rebuild verified (dkms status → installed).

Notes / alternatives for maintainers

  • The arguably cleaner long-term fix is to enable a tm1628/fd628 driver in
    the ophub kernel (ophub/kernel, e.g. carry this driver + CONFIG_TM16XX=m)
    — the DTB is already correct. This PR is the self-contained image-side option
    that needs no kernel rebuild and works on existing installs.
  • Caveat: ophub kernels are swapped via armbian-update (not dpkg), so DKMS's
    autoinstall hook may not fire on a kernel change; users may need
    dkms autoinstall (with the new kernel's headers installed). Could be wired
    into armbian-update in a follow-up.
  • Likely benefits other S905X2/X3 boxes using the same fd628/spi-gpio binding.
  • Separately, a tiny fix to armbian-openvfd to detect a missing
    /sys/class/leds/openvfd (instead of printing SUCCESS) would avoid the
    misleading output.

…oxes

Boxes whose device tree binds the front panel via the tm1628/fd628
spi-gpio binding (e.g. X96 Max / S905X2) have a dark panel: the DTB
instantiates the SPI device but the kernel ships no driver for it, and
armbian-openvfd's module-parameter path can't create a device on 6.x
kernels (and targets the same GPIOs the spi-gpio node already claims).

armbian-tm16xx (sibling of armbian-openvfd) builds and DKMS-installs
jefflessard/tm16xx-display, which binds to the existing DTB node with no
device-tree changes, then enables the upstream display-service. Provides
install/status/remove. Verified on X96 Max (S905X2), kernel 6.18.33-ophub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ophub

ophub commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Hello, and thank you for sharing your work.

Since I don’t have a compatible device, I can’t verify whether the script works correctly. If you have the hardware, could you please help with the following?

  1. Share screenshots of the complete process of running the script on the device.
  2. Take photos of the device’s LED display before compiling/installing the driver and after running the script, so I can compare the results.

I read your description as well as the driver’s repository documentation, but I still couldn’t find an explanation of whether this driver is mutually exclusive with openvfd, or if the two can coexist.

You also mentioned that the driver can be added directly to the kernel source tree, allowing it to be built into the kernel for easier use. Have you tested this with the 6.18.y kernel? If you’ve already verified that it works, could you submit a PR with the kernel changes? I’ll test it on my side.

Mainline kernel source:
https://github.com/ophub/linux-6.18.y

@B00StER

B00StER commented Jun 28, 2026

Copy link
Copy Markdown
Author

Thanks for taking a look, and for the clear questions. Answers below.

1 and 2 (install process, before/after photos). This is on a Vontar X96 Max (S905X2) running your Armbian image, kernel 6.18.33-ophub. I'll attach the before (dark panel) and after (clock) photos in a follow-up comment here. The full annotated run is below: default dark state, armbian-openvfd reporting SUCCESS while creating no device, DKMS build and install, panel lit, and persistence across a reboot.

Full install run (x96-repro.log)

############################################################
#  X96 Max front-panel reproduction   2026-06-28T03:18:51Z
############################################################
board : Shenzhen Amediatech Technology Co., Ltd X96 Max
kernel: 6.18.33-ophub
image : ?  soc=s905x2  kernel=6.18.33

############################################################
#  STAGE 0 - DEFAULT STATE: panel is DARK (this matches the 'before' photo)
############################################################

>>> The device tree DOES create the panel device, but no kernel driver binds to it.
$ cat /sys/bus/spi/devices/spi0.0/of_node/compatible
fdhisi,fd628 titanmec,tm1628 
$ readlink /sys/bus/spi/devices/spi0.0/driver
(no driver bound)
$ ls /sys/class/leds | grep -c display
0
$ lsmod | grep -c tm16xx
0

>>> ophub's bundled armbian-openvfd does NOT fix it here: it prints SUCCESS but creates no device, panel stays dark.
$ sudo armbian-openvfd 11
[�[95m STEPS �[0m] Enabling LED screen display...
[�[94m INFO �[0m] Using LED Profiles: /usr/share/openvfd/conf/x96max.conf
[�[94m INFO �[0m] turn led usb on ... 
/usr/sbin/armbian-openvfd: line 114: /sys/class/leds/openvfd/led_on: No such file or directory
[�[94m INFO �[0m] turn led apps on ... 
/usr/sbin/armbian-openvfd: line 114: /sys/class/leds/openvfd/led_on: No such file or directory
[�[94m INFO �[0m] turn led setup on ... 
/usr/sbin/armbian-openvfd: line 114: /sys/class/leds/openvfd/led_on: No such file or directory
[�[94m INFO �[0m] turn led sd on ... 
/usr/sbin/armbian-openvfd: line 114: /sys/class/leds/openvfd/led_on: No such file or directory
[�[94m INFO �[0m] turn led hdmi on ... 
/usr/sbin/armbian-openvfd: line 114: /sys/class/leds/openvfd/led_on: No such file or directory
[�[94m INFO �[0m] turn led cvbs on ... 
/usr/sbin/armbian-openvfd: line 114: /sys/class/leds/openvfd/led_on: No such file or directory
[�[92m SUCCESS �[0m] LED display enabled!
$ ls -d /sys/class/leds/openvfd
Open device failed.
: No such file or directory
(/sys/class/leds/openvfd does not exist - openvfd did not bind)
$ sudo armbian-openvfd 0
[�[95m STEPS �[0m] Disabling LED screen display...
[�[92m SUCCESS �[0m] LED display disabled!

############################################################
#  STAGE 1 - install build prerequisites + confirm kernel headers
############################################################
$ sudo apt-get update -qq
$ sudo apt-get install -y git dkms build-essential
Reading package lists...
Building dependency tree...
Reading state information...
git is already the newest version (1:2.53.0-1ubuntu1).
dkms is already the newest version (3.2.2-1ubuntu1).
Solving dependencies...
The following additional packages will be installed:
  libcrypt-dev
The following NEW packages will be installed:
  libcrypt-dev
The following packages will be upgraded:
  build-essential
1 upgraded, 1 newly installed, 0 to remove and 7 not upgraded.
Need to get 128 kB of archives.
After this operation, 389 kB of additional disk space will be used.
Get:1 http://ports.ubuntu.com resolute/main arm64 libcrypt-dev arm64 1:4.5.1-1 [123 kB]
Get:2 http://ports.ubuntu.com resolute-updates/main arm64 build-essential arm64 12.12ubuntu2.26.04.1 [5,040 B]
debconf: unable to initialize frontend: Dialog
debconf: (Dialog frontend will not work on a dumb terminal, an emacs shell buffer, or without a controlling terminal.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
debconf: unable to initialize frontend: Teletype
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Noninteractive
Fetched 128 kB in 1s (159 kB/s)
Selecting previously unselected package libcrypt-dev:arm64.
(Reading database… 
(Reading database… 5%
(Reading database… 10%
(Reading database… 15%
(Reading database… 20%
(Reading database… 25%
(Reading database… 30%
(Reading database… 35%
(Reading database… 40%
(Reading database… 45%
(Reading database… 50%
(Reading database… 55%
(Reading database… 60%
(Reading database… 65%
(Reading database… 70%
(Reading database… 75%
(Reading database… 80%
(Reading database… 85%
(Reading database… 90%
(Reading database… 95%
(Reading database… 100%
(Reading database… 38403 files and directories currently installed.)
Preparing to unpack …/libcrypt-dev_1%3a4.5.1-1_arm64.deb…
Unpacking libcrypt-dev:arm64 (1:4.5.1-1)…
Preparing to unpack …/build-essential_12.12ubuntu2.26.04.1_arm64.deb…
Unpacking build-essential (12.12ubuntu2.26.04.1) over (12.12ubuntu2)…
Setting up libcrypt-dev:arm64 (1:4.5.1-1)…
Setting up build-essential (12.12ubuntu2.26.04.1)…
Processing triggers for man-db (2.13.1-1build1)…
$ headers for running kernel:
/usr/src/linux-headers-6.18.33-ophub
/lib/modules/6.18.33-ophub/build/Makefile

############################################################
#  STAGE 2 - fetch the driver (jefflessard/tm16xx-display) + this recipe
############################################################
Cloning into 'tm16xx-display'...
Cloning into 'x96max-armbian-frontpanel'...

############################################################
#  STAGE 3 - stage the DKMS source tree at /usr/src/tm16xx-1.0
############################################################

>>> Driver sources + ophub-tailored Makefile/dkms.conf; keypad sub-module omitted.
$ sudo rm -rf /usr/src/tm16xx-1.0
$ sudo mkdir -p /usr/src/tm16xx-1.0/include
$ sudo cp line-display.c line-display.h tm16xx.h tm16xx_compat.h tm16xx_core.c tm16xx_spi.c tm16xx_i2c.c /usr/src/tm16xx-1.0/
$ sudo cp -r /home/claude/tm16xx-display/include/. /usr/src/tm16xx-1.0/include/
$ sudo cp /home/claude/x96max-armbian-frontpanel/dkms/Makefile /usr/src/tm16xx-1.0/Makefile
$ sudo cp /home/claude/x96max-armbian-frontpanel/dkms/dkms.conf /usr/src/tm16xx-1.0/dkms.conf

############################################################
#  STAGE 4 - build + install the module via DKMS
############################################################
$ sudo dkms add -m tm16xx -v 1.0
Creating symlink /var/lib/dkms/tm16xx/1.0/source -> /usr/src/tm16xx-1.0
$ sudo dkms build -m tm16xx -v 1.0
The kernel is built without module signing facility, modules won't be signed

Building module(s)....... done.
$ sudo dkms install -m tm16xx -v 1.0 --force
Found pre-existing /lib/modules/6.18.33-ophub/kernel/drivers/auxdisplay/line-display.ko, archiving for uninstallation
Installing /lib/modules/6.18.33-ophub/updates/dkms/line-display.ko
Installing /lib/modules/6.18.33-ophub/updates/dkms/tm16xx.ko
Installing /lib/modules/6.18.33-ophub/updates/dkms/tm16xx_spi.ko
Installing /lib/modules/6.18.33-ophub/updates/dkms/tm16xx_i2c.ko
Running depmod.... done.
$ sudo dkms status tm16xx
tm16xx/1.0, 6.18.33-ophub, aarch64: installed (Original modules exist)

############################################################
#  STAGE 5 - load the module (at boot + now)
############################################################
$ sudo cp /home/claude/x96max-armbian-frontpanel/system/etc/modules-load.d/tm16xx.conf /etc/modules-load.d/tm16xx.conf
$ sudo modprobe tm16xx_spi
$ ls /sys/class/leds | grep display
display
display::apps
display::colon
display::cvbs
display::hd
display::sd
display::setup
display::usb

############################################################
#  STAGE 6 - boot-time self-heal service (rebuilds after kernel updates)
############################################################
$ sudo install -m 0755 /home/claude/x96max-armbian-frontpanel/system/usr/sbin/tm16xx-dkms-ensure /usr/sbin/tm16xx-dkms-ensure
$ sudo install -m 0644 /home/claude/x96max-armbian-frontpanel/system/etc/systemd/system/tm16xx-dkms.service /etc/systemd/system/tm16xx-dkms.service
$ sudo mkdir -p /etc/systemd/system/display.service.d
$ sudo install -m 0644 /home/claude/x96max-armbian-frontpanel/system/etc/systemd/system/display.service.d/10-dkms-ensure.conf /etc/systemd/system/display.service.d/10-dkms-ensure.conf
$ sudo systemctl daemon-reload
$ sudo systemctl enable tm16xx-dkms.service
Created symlink '/etc/systemd/system/multi-user.target.wants/tm16xx-dkms.service' → '/etc/systemd/system/tm16xx-dkms.service'.

############################################################
#  STAGE 7 - install + start the clock/status daemon
############################################################
$ sudo install -m 0755 /home/claude/tm16xx-display/display-service /usr/sbin/display-service
$ sudo install -m 0644 /home/claude/tm16xx-display/display.service /lib/systemd/system/display.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now display.service
Created symlink '/etc/systemd/system/basic.target.wants/display.service' → '/usr/lib/systemd/system/display.service'.

############################################################
#  STAGE 8 - AFTER: driver bound, panel shows the clock (this matches the 'after' photo)
############################################################
$ spi0.0 driver:
tm16xx-spi
$ ls /sys/class/leds | grep display
display
display::apps
display::colon
display::cvbs
display::hd
display::sd
display::setup
display::usb
$ cat /sys/class/leds/display/message
1022
$ systemctl is-active display tm16xx-dkms
active
active

>>> Done. Panel now shows the clock; the self-heal service rebuilds the module after future kernel updates.

############################################################
#  STAGE 9 - PERSISTENCE AFTER REBOOT (no manual steps)
############################################################

>>> Rebooted the box. modules-load + the self-heal service bring the panel back on their own.
$ uptime -p
up 0 minutes
$ self-heal service log (this boot):
Jun 28 10:39:17 x96max tm16xx-dkms-ensure[394]: tm16xx-dkms-ensure: already installed for 6.18.33-ophub
$ basename readlink /sys/bus/spi/devices/spi0.0/driver
tm16xx-spi
$ ls /sys/class/leds | grep display
display
display::apps
display::colon
display::cvbs
display::hd
display::sd
display::setup
display::usb
$ cat /sys/class/leds/display/message
1039
$ systemctl is-active display tm16xx-dkms
active
active

>>> Confirmed: after a cold reboot the panel shows the clock automatically, no manual steps.

3 (openvfd vs tm16xx: they're mutually exclusive). Both drive the same physical panel over the same bit-banged GPIO lines (on the X96 Max, CLK/DAT/STB = GPIO 64/63/10), so only one can be active. tm16xx binds to the fd628/tm1628 SPI device the device tree already creates at /sys/bus/spi/devices/spi0.0; openvfd wants those same GPIOs through its own path. On this box openvfd never actually binds (no /sys/class/leds/openvfd appears), so there's nothing to coexist with in practice, but in general they contend for the pins. The helper is meant only for boxes where the DT instantiates that SPI node and nothing drives it, not for boxes that genuinely need openvfd. I can make it refuse to run with a clear message when openvfd is active, if you want that guard.

4 (building into the 6.18.y kernel). The driver is jefflessard's tm16xx, currently in mainline review under auxdisplay (series). What this PR ships and what I've tested is the out-of-tree DKMS build against your 6.18.33 headers, verified across a reboot and a kernel-update rebuild. I haven't built it in-tree against ophub/linux-6.18.y, and since it's the author's driver with an active mainline series, the in-tree integration is really his call. I've reached out to him about it. Once that's sorted (mainline landing, or his sign-off on an ophub carry), building it in as a module (CONFIG_TM16XX=m) should be straightforward, and I appreciate your offer to test a kernel PR on your side when it gets there.

@B00StER

B00StER commented Jun 28, 2026

Copy link
Copy Markdown
Author

The follow-up with some photos:

Dark panel (LEDs off):
x96max-led-off

Active panel (displaying the clock):
x96max-led-on

More panel activity (Clock, along with the "Card" icon indicating activity of the mmc0 device):
x96max-led-on-card
Interesting detail: this icon glowing is a buggy feature: it is supposed to indicate the activity of the SD card, but, due to the x96Max' specifics, it's indicating the activity of the wifi interface instead. See this PR for details: jefflessard/tm16xx-display#15

@ophub

ophub commented Jun 28, 2026

Copy link
Copy Markdown
Owner

According to the OpenVFD documentation: https://github.com/ophub/amlogic-s9xxx-armbian/blob/main/documents/led_screen_display_control.md, the service can be disabled using the armbian-openvfd 0 command. Since armbian-tm16xx and armbian-openvfd have a hardware resource conflict, it is recommended to add conflict detection logic to the armbian-tm16xx script. When the script detects that the openvfd service is running or enabled, it should automatically disable OpenVFD first (e.g., by calling armbian-openvfd 0 and stopping the service) before starting the tm16xx service.

Additionally, I would like to understand more about how armbian-tm16xx handles compatibility and control for different devices. In OpenVFD, users must specify different configurations depending on the device (for example, using armbian-openvfd 11 to enable the LED panel for X96Max). How does armbian-tm16xx achieve multi-device support? Does the script automatically detect the hardware to adapt, compile, and install, or does it require the user to manually input specific commands or parameters to specify the device type?

Finally, to make it easier for users to understand, use, and debug this new LED control panel tool, could you provide a corresponding usage and debugging guide for armbian-tm16xx based on the format of the OpenVFD documentation? This document can be placed in the documents directory: https://github.com/ophub/amlogic-s9xxx-armbian/tree/main/documents

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants