Has anyone managed to get the 4DCape LCD working on the BeagleBone Black Debian 13.5 2026-05-19 IoT (v6.18.x) image?
I’ve been adding them as users request, please dump the serial boot log with the cape plugged in… i need to see what u-boot detects..
Regards,
Any suggestions on how to connect to serial header if cape is plugged in as it won’t be accessible?
Is there another way to get what you’re looking for?
I use the rpi official usart debugger and bend them over…
Try sudo beagle-version
This is a bit of an annoying way to go about doing things but I went pin for pin with jumper wires to attach the Cape (LCD) to the BeagleBone Black to fit the 3.3v (UART) to TTL USB dongle.
Although that is way more than most people can stomach for getting data, there is another way. Extension headers will work the same. Then, like @RobertCNelson has stated, the bent header pins can fit comfortably. Heads up!
I’m going to be ordering some stacked headers and may head out to our production house next week and get them to remove the header and insert on reverse side of board.
@RobertCNelson I’ll try the sudo command you suggested as well.
Thanks Robert, here is output of sudo beagle-version
eeprom:[A335BNLT00C02445SBB08054]
model:[TI_AM335x_BeagleBone_Black]
dogtag:[BeagleBoard.org Debian Trixie Base Image 2026-05-19]
bootloader:[microSD-(push-button)]:[/dev/mmcblk0]:[U-Boot SPL 2022.04-g5509547b (Jan 22 2026 - 19:56:08 +0000)]:[location: dd MBR]
bootloader:[eMMC-(default)]:[/dev/mmcblk1]:[U-Boot SPL 2022.04-ge543709d (Jun 27 2025 - 18:09:26 +0000)]:[location: dd MBR]
UBOOT: Booted Device-Tree:[am335x-boneblack-uboot.dts]
UBOOT: Loaded Overlay:[BB-ADC-00A0.kernel]
UBOOT: Loaded Overlay:[BB-BONE-eMMC1-01-00A0.kernel]
UBOOT: Loaded Overlay:[BB-BONE-LCD4-01-00A1.kernel]
UBOOT: Loaded Overlay:[BB-I2C1-RTC-DS3231.kernel]
UBOOT: Loaded Overlay:[BB-PWMSS0-P9_22-P9_42.kernel]
kernel:[6.18.32-bone35]
nodejs:[v22.15.0]
/boot/uEnv.txt Settings:
uboot_overlay_options:[enable_uboot_overlays=1]
uboot_overlay_options:[uboot_overlay_addr3=BB-BONE-LCD4-01-00A1.dtbo]
uboot_overlay_options:[uboot_overlay_addr4=BB-I2C1-RTC-DS3231.dtbo]
uboot_overlay_options:[uboot_overlay_addr5=BB-BONE-AUDI-02-00A0.dtbo]
uboot_overlay_options:[uboot_overlay_addr6=BB-PWMSS0-P9_22-P9_42.dtbo]
uboot_overlay_options:[disable_uboot_overlay_video=1]
uboot_overlay_options:[disable_uboot_overlay_audio=1]
uboot_overlay_options:[uboot_overlay_pru=AM335X-PRU-UIO-00A0.dtbo]
uboot_overlay_options:[enable_uboot_cape_universal=1]
pkg check: to individually upgrade run: [sudo apt install --only-upgrade <pkg>]
pkg:[bb-customizations]:[1.20250808.0-0~trixie+20250808]
pkg:[bb-usb-gadgets]:[1.20260427.1-0~trixie+20260427]
pkg:[bb-wl18xx-firmware]:[1.20230703.0-0~trixie+20240703]
pkg:[kmod]:[34.2-2bbbio1~trixie+20250522]
groups:[debian : debian adm kmem dialout cdrom floppy audio dip video plugdev users systemd-journal input render netdev i2c bluetooth gpio admin tisdk weston-launch]
cmdline:[console=ttyS0,115200n8 root=/dev/mmcblk0p3 ro rootfstype=ext4 rootwait uboot_detected_capes=BB-BONE-LCD4-01, fsck.repair=yes earlycon coherent_pool=1M net.ifnames=0 lpj=1990656 rng_core.default_quality=100 vt.global_cursor_default=0 systemd.show_status=0]
dmesg | grep remote
[ 3.607144] remoteproc remoteproc0: wkup_m3 is available
[ 5.618410] remoteproc remoteproc0: powering up wkup_m3
[ 5.661440] remoteproc remoteproc0: Booting fw image am335x-pm-firmware.elf, size 217148
[ 5.710169] remoteproc remoteproc0: remote processor wkup_m3 is now up
[ 18.015396] systemd[1]: Reached target remote-fs.target - Remote File Systems.
[ 66.187616] remoteproc remoteproc1: 4a334000.pru is available
[ 66.605432] remoteproc remoteproc2: 4a338000.pru is available
dmesg | grep pru
[ 66.187616] remoteproc remoteproc1: 4a334000.pru is available
[ 66.605432] remoteproc remoteproc2: 4a338000.pru is available
dmesg | grep pinctrl-single
[ 3.627136] pinctrl-single 44e10800.pinmux: 142 pins, size 568
dmesg | grep gpio-of-helper
dmesg | grep wlcore
lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 002: ID 0b05:184c ASUSTek Computer, Inc. 802.11ac NIC
END
That’s probably going overboard a little, but if you have the option,
why not just have them leave the holes open (not placed) ?
That way you could solder a provisional header whatever way you needed…
Personally, I’m very fond of Wire-wrapping; no soldering required
and the wires remove very easily after usage.
Would have been a snap to do in your case.
BB-BONE-LCD4-01-00A1 got merged a few days ago: kernel v6.18.34-bone36.1 rebase external git projects · RobertCNelson/bb-kernel@8570e75 · GitHub
Just run:
sudo apt update
sudo apt-get dist-upgrade
To get 6.18.34-bone37
Regards,
Thanks Robert, I will try the update.
Regarding the dtso file:
Thanks Robert — I built and tested the merged BB-BONE-LCD4-01-00A1 overlay on a BeagleBone Black with a 4DCAPE-43T (480×272) on 6.18.32-bone35 / Debian 13.5, and wanted to flag that as merged it still doesn’t bring up the panel. The overlay is the stock one, so the //FIXME - LCD doesn't init is still in there with the panel and &lcdc port/endpoint blocks commented out. With it loaded, tilcdc logs:
tilcdc 4830e000.lcdc: no encoders/connectors found
and X (modesetting) fails to set a mode. No DRM connector is registered, so the screen stays blank.
What got it working for us was two independent changes:
-
Device tree — modern OF-graph binding. Replaced the legacy
ti,tilcdc,panel+panel-infowithcompatible = "panel-dpi"+ apanel-timingsubnode, and un-commented / wired up theport { endpoint }on both the panel and&lcdc(panel_0↔lcdc_0). After that, tilcdc registers aDPI-1connector and theno encoders/connectors founderror is gone. Same rev-02 480×272 timings, same pinmux, sameblue-and-red-wiring = "straight". -
Xorg — fbdev instead of modesetting. Even with the connector up, X stayed blank and spammed
failed to add fb -22: tilcdc’s plane is RGB565-only, and modesetting tries toADDFBa 32bpp XRGB8888 buffer.DefaultDepth 16+AccelMethod none+ShadowFBdidn’t clear it. Switching toxf86-video-fbdev(renders via/dev/fb0) fixed it.
Unless I missed something, the overlay itself still needs the connector fix to actually light the panel. Happy to share the full overlay diff / writeup if useful — and curious whether panel-dpi is the binding you’d want upstream, or if there’s a preferred way to make tilcdc/RGB565 work with generic modesetting rather than falling back to fbdev.
Thanks again.
i’m merging these changes in now.. btw, CONFIG_DRM_TILCDC_PANEL_LEGACY=y hid the issue for us..
Regards,
Thanks, I’m also happy to share what I have done. It appears to be working but this is all beyond my pay grade.
Yeah, i’d love to see your changes… panel-dpi is what we need to move to on mainline..
I am happy to share but have to honest in that I feel out of my depth and have been using my sidekick Claude to give direction. It’s been draining as I normally only use AI agents when I know the subject as I don’t blindly trust it, I have the cape working with touchscreen enable and have done an out of kernel tweak to the lcd dtso file to give ability to tweak some parameters for stopping false touches.
I will pull content and get to you. What’s the best way as I don’t want to flood this thread.
Oh just dump your device tree to the thread.. I’ve been trying different combinations of endpoints, panel and etc, trying to get passed the drm encoder/connection error
I’ll paste this afternoon. We had to make other changes as well so will attach summary doc.
Sorry this took so long, below is my dtso file. Note I did not include buttons as our cape does not have buttons as we need the gpio for other functionality. I also updated charge-delay and added setting of z-threshold for managing of invalid touches.
We updated tsc driver to include reading z-threshold from overlay to set within driver and are loading the driver out of kernel tree.
LCD dtso file
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/
*
* BB-BONE-LCD4-01-00A1-mod — 4D Systems 4DCAPE-43T (480x272 resistive touch)
* EEPROM: BB-BONE-LCD4-01 / 00A1
*
* 6.18 tilcdc DRM fix
* -------------------
* The stock bb.org v6.18.x overlay (and the 5.10-era rev-02 we shipped)
* use the legacy "ti,tilcdc,panel" + "panel-info" binding. On the 6.18
* tilcdc driver that binding never registers a DRM connector:
*
* tilcdc 4830e000.lcdc: no encoders/connectors found
* (EE) modeset(0): failed to set mode: Invalid argument (Xorg)
*
* bb.org's own v6.18.x overlay carries a "//FIXME - LCD doesn't init" with
* the OF-graph endpoints commented out, and no upstream fix exists.
*
* This rewrite drops "ti,tilcdc,panel"/"panel-info" in favour of the modern
* OF-graph DRM binding: a "panel-dpi" panel with a "panel-timing" subnode,
* wired to &lcdc via port/endpoint on both ends (lcdc_0 <-> panel_0). The
* proven 5.10 pinmux, PWM backlight and touchscreen config are unchanged;
* the panel timings are the known-good rev-02 values.
*/
/dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/pinctrl/am33xx.h>
/*
* Helper to show loaded overlays under: /proc/device-tree/chosen/overlays/
*/
&{/chosen} {
overlays {
BB-BONE-LCD4-01-00A1.kernel = __TIMESTAMP__;
};
};
/*
* Free up the pins used by the cape from the pinmux helpers.
*/
&ocp {
P9_12_pinmux { status = "disabled"; }; /* P9_12: gpmc_ben1.gpio1_28 */
P9_14_pinmux { status = "disabled"; }; /* P9_14: gpmc_a2.ehrpwm1a */
P9_27_pinmux { status = "disabled"; }; /* P9_27: mcasp0_fsr.gpio3_19 */
P8_45_pinmux { status = "disabled"; }; /* P8_45: lcd_data0 */
P8_46_pinmux { status = "disabled"; }; /* P8_46: lcd_data1 */
P8_43_pinmux { status = "disabled"; }; /* P8_43: lcd_data2 */
P8_44_pinmux { status = "disabled"; }; /* P8_44: lcd_data3 */
P8_41_pinmux { status = "disabled"; }; /* P8_41: lcd_data4 */
P8_42_pinmux { status = "disabled"; }; /* P8_42: lcd_data5 */
P8_39_pinmux { status = "disabled"; }; /* P8_39: lcd_data6 */
P8_40_pinmux { status = "disabled"; }; /* P8_40: lcd_data7 */
P8_37_pinmux { status = "disabled"; }; /* P8_37: lcd_data8 */
P8_38_pinmux { status = "disabled"; }; /* P8_38: lcd_data9 */
P8_36_pinmux { status = "disabled"; }; /* P8_36: lcd_data10 */
P8_34_pinmux { status = "disabled"; }; /* P8_34: lcd_data11 */
P8_35_pinmux { status = "disabled"; }; /* P8_35: lcd_data12 */
P8_33_pinmux { status = "disabled"; }; /* P8_33: lcd_data13 */
P8_31_pinmux { status = "disabled"; }; /* P8_31: lcd_data14 */
P8_32_pinmux { status = "disabled"; }; /* P8_32: lcd_data15 */
P8_27_pinmux { status = "disabled"; }; /* P8_27: lcd_vsync */
P8_29_pinmux { status = "disabled"; }; /* P8_29: lcd_hsync */
P8_28_pinmux { status = "disabled"; }; /* P8_28: lcd_pclk */
P8_30_pinmux { status = "disabled"; }; /* P8_30: lcd_ac_bias_en */
};
&am33xx_pinmux {
bb_lcd_led_pins: pinmux_bb_lcd_led_pins {
pinctrl-single,pins = <
AM33XX_PADCONF(AM335X_PIN_GPMC_BEN1, PIN_INPUT, MUX_MODE7) /* P9_12: gpmc_ben1.gpio1_28 */
>;
};
bb_lcd_pwm_backlight_pins: pinmux_bb_lcd_pwm_backlight_pins {
pinctrl-single,pins = <
AM33XX_PADCONF(AM335X_PIN_GPMC_A2, PIN_OUTPUT_PULLDOWN, MUX_MODE6) /* P9_14: gpmc_a2.ehrpwm1a */
>;
};
bb_lcd_lcd_pins: pinmux_bb_lcd_lcd_pins {
pinctrl-single,pins = <
AM33XX_PADCONF(AM335X_PIN_MCASP0_FSR, PIN_OUTPUT_PULLUP, MUX_MODE7) /* P9_27: mcasp0_fsr.gpio3_19 */
AM33XX_PADCONF(AM335X_PIN_LCD_DATA0, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA1, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA2, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA3, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA4, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA5, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA6, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA7, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA8, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA9, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA10, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA11, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA12, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA13, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA14, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_DATA15, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_VSYNC, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_HSYNC, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_PCLK, PIN_OUTPUT, MUX_MODE0)
AM33XX_PADCONF(AM335X_PIN_LCD_AC_BIAS_EN, PIN_OUTPUT, MUX_MODE0)
>;
};
};
&epwmss1 {
status = "okay";
};
&ehrpwm1 {
pinctrl-names = "default";
pinctrl-0 = <&bb_lcd_pwm_backlight_pins>;
status = "okay";
};
&lcdc {
status = "okay";
blue-and-red-wiring = "straight";
/*
* Modern OF-graph wiring: the tilcdc DRM driver walks this endpoint
* to find the attached panel and register a connector. Without it
* the 6.18 driver logs "no encoders/connectors found".
*/
port {
lcdc_0: endpoint {
remote-endpoint = <&panel_0>;
};
};
};
&tscadc {
status = "okay";
tsc {
ti,wires = <4>;
ti,x-plate-resistance = <200>;
/*
* Touch release-glitch fix (4DCAPE-43T) — see docs/developer/touch-debounce.md.
* On lift the 4-wire panel emits one bogus full-scale ABS_X sample
* before pen-up, planting a phantom click at the right edge.
*
* charge-delay 0x400 -> 0xB000: BBB-standard ADC settle time; 0x400
* was ~44x too short and let the release transient through. This is
* the actual glitch fix and is free of step-count cost.
*
* coordinate-readouts STAYS AT 5 (do NOT raise to 7). The AM335x TSC
* has a hard 16-step-config limit shared by TSC + ADC. The driver
* uses (coordinate-readouts*2 + 2) TSC steps + (n adc-channels). With
* our 4 ADC channels: readouts=5 -> 5*2+2+4 = 16 (exactly OK);
* readouts=7 -> 7*2+2+4 = 20 -> driver probe fails with
* "Too many step configurations requested" / -EINVAL and the WHOLE
* touchscreen disappears (no /dev/input/eventN). Bench-confirmed on
* biochargerng.local 2026-06-08.
*/
ti,coordinate-readouts = <5>;
ti,wire-config = <0x00 0x11 0x22 0x33>;
ti,charge-delay = <0xB000>;
/*
* Phantom-touch Z-pressure gate (4DCAPE-43T) — see
* ../../../../docs/developer/touch-pressure-gate.md and install_tsc_module.sh.
*
* The panel emits full-scale ABS_X glitch samples at LOW pressure
* (Z ~= 57-58) that fire a phantom far-side click; real touches
* carry Z = 246-303. This property tells the (patched) ti_am335x_tsc
* driver to DROP any sample with pressure below 150 — safely in the
* 58-vs-246 gap. Tunable here without a module rebuild.
*
* REQUIRES the out-of-tree Z-gated driver installed by
* install_tsc_module.sh. On the STOCK in-tree driver this property is
* simply ignored (harmless no-op), so the overlay is safe either way.
*/
ti,z-threshold = <150>;
};
adc {
ti,adc-channels = <4 5 6 7>;
};
};
&{/} {
backlight: backlight {
status = "okay";
compatible = "pwm-backlight";
pwms = <&ehrpwm1 0 500000 0>;
brightness-levels = <
0 1 2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
100
>;
default-brightness-level = <100>;
};
/*
* Modern DRM panel: "panel-dpi" (panel-simple) with a single
* "panel-timing" subnode. This is what registers a DRM connector on
* 6.18 tilcdc, replacing the legacy "ti,tilcdc,panel"/"panel-info".
*
* The 16bpp raster format and blue/red wiring are configured on
* &lcdc (blue-and-red-wiring = "straight"); panel-dpi itself only
* carries the mode timings + backlight handle.
*
* Timings are the known-good rev-02 480x272 values:
* www.newhavendisplay.com/app_notes/OTA5180A.pdf
*/
panel: panel {
status = "okay";
compatible = "panel-dpi";
pinctrl-names = "default";
pinctrl-0 = <&bb_lcd_lcd_pins>;
backlight = <&backlight>;
width-mm = <95>;
height-mm = <54>;
panel-timing {
clock-frequency = <9200000>;
hactive = <480>;
vactive = <272>;
hfront-porch = <8>;
hback-porch = <47>;
hsync-len = <41>;
vfront-porch = <3>;
vback-porch = <2>;
vsync-len = <10>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <1>;
pixelclk-active = <0>;
};
port {
panel_0: endpoint {
remote-endpoint = <&lcdc_0>;
};
};
};
gpio-leds {
compatible = "gpio-leds";
pinctrl-names = "default";
pinctrl-0 = <&bb_lcd_led_pins>;
led-ld0 {
label = "lcd:green:usr0";
gpios = <&gpio1 28 0>;
linux,default-trigger = "heartbeat";
default-state = "off";
};
};
};
Notes/Ideas/Questions:
Below are notes collected and transcribed using Claude - I have not verified with a fine touchpick but it does seem to be accurate to give idea of what was done to get reliable 4DCape up and running. Note most of the effort was to reduce quick and light touches from causing false touch events elsewhere on the screen.
Use at your own discretion.
4DCAPE-43T on BeagleBone Black / Debian 13.5 / kernel 6.18 — complete bring-up notes
This is a full write-up of getting the 4D Systems 4DCAPE-43T (custom cape,
buttons omitted) running reliably on BeagleBone Black with Debian 13.5 (Trixie)
and Robert Nelson’s 6.18.32-bone35 kernel — display, touch, and calibration.
It turned into a five-layer problem. Posting the whole thing because the next
person shouldn’t have to rediscover any of it.
Feedback welcome — particularly on the open questions at the end.
Hardware / software environment
| Board | BeagleBone Black (TI AM335x Cortex-A8, 512 MB RAM) |
| Cape | Custom cape based on 4D Systems 4DCAPE-43T, 480×272, resistive touch, without buttons |
| Cape EEPROM | BB-BONE-LCD4-01, rev 00A1 |
| OS | Debian 13.5 Trixie (armhf), BeagleBoard.org IoT image |
| Kernel | 6.18.32-bone35 (Robert Nelson’s BeagleBone TI build) |
| Display controller | on-SoC LCDC → tilcdc DRM/KMS driver (4830e000.lcdc) |
| Touch | tscadc / ti-tsc (4-wire resistive ADC) |
| X server | Xorg 21.x (lightdm-managed) |
TL;DR
Five independent problems, five fixes, each at a different layer of the stack:
| # | Problem | Fix | Layer |
|---|---|---|---|
| 1 | Blank screen — no DRM connector | panel-dpi + OF-graph port/endpoint wiring |
device tree |
| 2 | X can’t paint to display | Switch from modesetting to xf86-video-fbdev |
Xorg config |
| 3 | Touch coordinates off (worst near top edge) | libinput CalibrationMatrix (evdev options silently ignored on Debian 13) |
libinput / Xorg |
| 4 | Phantom click on touch release | ti,charge-delay: 0x400 → 0xB000 |
device tree (overlay) |
| 5 | Self-contained phantom press at far side of screen | Out-of-tree ti_am335x_tsc module with ti,z-threshold = <150> |
kernel driver |
All five are required. None is redundant with the others — each catches a
failure mode the others can’t.
Root cause 1 — legacy tilcdc panel binding doesn’t work on 6.18
The merged BB-BONE-LCD4-01-00A1 overlay describes the panel with the legacy
binding:
panel {
compatible = "ti,tilcdc,panel";
panel-info { ac-bias = <255>; bpp = <16>; ... };
display-timings { native-mode = <&timing0>; timing0: 480x272 { ... }; };
};
On 6.18 tilcdc this binding no longer results in a registered DRM connector.
The driver reports:
tilcdc 4830e000.lcdc: no encoders/connectors found
Screen stays blank. Robert explained the cause: CONFIG_DRM_TILCDC_PANEL_LEGACY=y
was set in their build tree, keeping the old binding alive. Without it,
ti,tilcdc,panel is a no-op. The issue was invisible to them until we reported it.
The upstream v6.18.x overlay source (beagleboard/BeagleBoard-DeviceTrees)
actually carries a //FIXME - LCD doesn't init comment with the OF-graph
port/endpoint sections commented out — the gap was known but unfixed.
Fix: panel-dpi + OF-graph wiring
Replace the legacy binding with the generic panel-dpi binding, and wire both
the panel and &lcdc with OF-graph port/endpoint blocks so the DRM connector
registers:
&lcdc {
status = "okay";
blue-and-red-wiring = "straight";
port {
lcdc_0: endpoint {
remote-endpoint = <&panel_0>;
};
};
};
panel: panel {
status = "okay";
compatible = "panel-dpi";
pinctrl-names = "default";
pinctrl-0 = <&bb_lcd_lcd_pins>;
backlight = <&backlight>;
width-mm = <95>;
height-mm = <54>;
panel-timing {
clock-frequency = <9200000>;
hactive = <480>; vactive = <272>;
hfront-porch = <8>; hback-porch = <47>; hsync-len = <41>;
vfront-porch = <3>; vback-porch = <2>; vsync-len = <10>;
hsync-active = <0>; vsync-active = <0>;
de-active = <1>; pixelclk-active = <0>;
};
port {
panel_0: endpoint {
remote-endpoint = <&lcdc_0>;
};
};
};
Key points:
- The timings are the known-good rev-02 480×272 values — unchanged from the
working 5.10 build. Only the binding changed, not the hardware numbers. panel-info’sbpp = 16has nopanel-dpiequivalent. The 16bpp RGB565
format stays on&lcdcviablue-and-red-wiring = "straight".- The proven 5.10 pinmux — 16 LCD data lines, vsync/hsync/pclk/ac_bias, P9_14
ehrpwm1 backlight, P9_12 user LED — is unchanged. - We removed
gpio-keys(the 5 directional buttons the stock overlay defines on
P9_15/P9_23/P9_16/P9_30/P9_24). In our case those pins are shared with our own
GPIO and the audio cape (BB-BONE-AUDI-02-00A0). This is product-specific —
anyone upstreaming the connector fix should keepgpio-keysin the overlay.
The two changes are independent and should be submitted separately.
After this: tilcdc binds cleanly, a DPI-1 connector appears, xrandr
reports 480x272 connected.
Root cause 2 — Xorg failed to add fb -22
With the connector working, /dev/fb0 worked fine but X did not. Writing random
bytes directly proved the kernel side was fine:
cat /dev/urandom > /dev/fb0 # colour noise on the screen → panel scans out fine
But Xorg spammed failed to add fb -22 thousands of times and the screen stayed
blank.
-22 is -EINVAL — the kernel rejecting DRM_IOCTL_MODE_ADDFB. The tilcdc
plane only advertises DRM_FORMAT_RGB565. The default xf86-video-modesetting
driver allocates a 32bpp XRGB8888 buffer and calls ADDFB; tilcdc rejects it
because no plane handles that format. X never gets a scannable framebuffer.
We tried forcing 16bpp with DefaultDepth 16 + AccelMethod none +
ShadowFB true. The spam rate dropped (the visual depth was 16, confirmed via
xwininfo), but failed to add fb -22 kept growing and the screen stayed blank.
We couldn’t persuade modesetting to stop requesting a format tilcdc rejects.
Fix: xf86-video-fbdev
Since /dev/fb0 worked, we switched X to xf86-video-fbdev, which renders
through the framebuffer device and bypasses the ADDFB path entirely:
Section "Device"
Identifier "tilcdc-fbdev"
Driver "fbdev"
Option "fbdev" "/dev/fb0"
Option "ShadowFB" "true"
EndSection
Section "Screen"
Identifier "tilcdc-screen"
Device "tilcdc-fbdev"
DefaultDepth 16
EndSection
Xorg.0.log confirms:
(II) FBDEV(0): using /dev/fb0
(**) FBDEV(0): Depth 16, (--) framebuffer bpp 16
(==) FBDEV(0): RGB weight 565
(II) FBDEV(0): hardware: tilcdcdrmfb (video memory: 255kB)
Package required: xserver-xorg-video-fbdev (in Trixie; not installed by
default).
One cosmetic wrinkle: fbdev tries to load a hardware colormap. On RGB565 there’s
no palette, so FBIOPUTCMAP returns -EINVAL at every DPMS cycle. Disabling
DPMS (right call for a kiosk anyway) stops the repeating spam; only a single
harmless startup log line remains.
Root cause 3 — touch calibration: libinput, not evdev
With the display working, touch tracked the finger but coordinates were off —
worst near the top edge.
The trap is a driver shift between Debian 11 and Debian 13. On 5.10 the
touchscreen was driven by evdev, so xinput_calibrator worked — it writes
Option "Calibration" / SwapAxes / InvertX/Y in an evdev InputClass. On
Debian 13 the same device binds to the libinput X driver, and libinput
silently ignores every one of those evdev options. It only honours
Option "CalibrationMatrix" (a 3×3 affine). The carried-over calibration script
was writing a file the running driver discarded; touch was running on libinput’s
identity matrix.
Why it was worst at the top: the ti-tsc driver reports ABS_X/ABS_Y range
0..4095 but the panel only uses a sub-range (roughly X: 195–3910, Y: 405–3740).
With the identity matrix a touch at the top edge (raw Y ≈ 405) maps to
405/4095 ≈ 10% down the screen. The Y offset (405) is larger than the X offset
(195), so vertical error dominated.
The fix is a libinput CalibrationMatrix:
Section "InputClass"
Identifier "calibration"
MatchProduct "ti-tsc"
MatchIsTouchscreen "on"
MatchDriver "libinput"
Option "CalibrationMatrix" "1.09 0 -0.05 0 1.16 -0.11 0 0 1"
EndSection
(Row-major a b c / d e f / g h i, written to
/etc/X11/xorg.conf.d/99-calibration.conf. The exact numbers are per-unit —
measured from a calibration wizard, not a shipped constant.)
We still use xinput_calibrator for the four-target tapping UI — it works fine
as an X client on fbdev — but we discard its output (the evdev InputClass
libinput ignores) and instead parse the measured min/max it prints, converting to
a CalibrationMatrix with our own script. The calibrator normalises in a 0..65535
space (not ADC 0..4095), so the scale is 65535 / (max - min).
Drop xserver-xorg-input-evdev entirely. With both evdev and libinput present,
driver selection for ti-tsc is ambiguous. Without evdev, libinput owns it
deterministically.
Root cause 4 — release transient: ti,charge-delay
With display and calibration working, every touch release fired a phantom click
at the far right edge of the screen. evtest /dev/input/event1 made the cause
unambiguous:
ABS_X 431 ABS_PRESSURE 250 ← real touch, left side of screen
ABS_X 4085 ABS_PRESSURE 57 ← garbage sample at lift-off
BTN_TOUCH 0
The 4-wire resistive panel emits one bogus near-max ABS_X reading at the
instant of lift, before the pen-up event. The driver passes it to userspace;
libinput plants the cursor at the right edge; kiosk registers a phantom click there.
This is a classic 4-wire resistive release transient / ADC settling problem.
The fix is in the overlay’s &tscadc tsc node. The existing
ti,charge-delay = <0x400> was roughly 44× too short for this panel — not
enough settle time for the ADC to recover from the release transient. Raising it
to <0xB000>:
tsc {
ti,wires = <4>;
ti,x-plate-resistance = <200>;
ti,coordinate-readouts = <5>;
ti,charge-delay = <0xB000>; /* was 0x400 */
ti,wire-config = <0x00 0x11 0x22 0x33>;
};
On-device result across 9 deliberate release events: 8 of 9 clean (no
ABS_X ≈ 4085 spike preceding pen-up). The residual 1-in-9 low-pressure sample
is handled by the next fix.
Don’t raise ti,coordinate-readouts beyond 5. The AM335x TSC shares a hard
16-step budget with the ADC. With 4 ADC channels the arithmetic is
5×2 + 2 + 4 = 16 — exactly at the limit. Raising to 7 pushes it to 20 steps,
the driver throws -EINVAL on probe, and the entire touchscreen disappears.
Confirmed on the bench and reverted.
Rebuild the .dtbo after any .dtso change:
cpp -nostdinc -undef -x assembler-with-cpp \
-I/usr/src/linux-headers-6.18.32-bone35/include \
BB-BONE-LCD4-01-00A1-mod.dtso \
| dtc -W no-unit_address_vs_reg -O dtb -o BB-BONE-LCD4-01-00A1.dtbo -
Overlays apply at U-Boot; a reboot is required.
Root cause 5 — self-contained phantom press: out-of-tree Z-pressure gate
After fixing the release transient, a second phantom remained: tapping one side
of the screen occasionally triggered an event on the opposite side — for us,
tapping “previous” would fire “next”. evtest captured it:
BTN_TOUCH 0 ABS_PRESSURE 0 ← real touch ends cleanly
ABS_X 4072 ABS_PRESSURE 58 BTN_TOUCH 1 ← ~1.3 ms later: phantom press, far right
This is a self-contained phantom DOWN — a complete press at far-right
coordinates, 1.3 ms after a real release. It arrives at the browser as a
positionally-valid tap and is indistinguishable from a real one. The
discriminating signal — Z pressure — is present in the kernel (ABS_PRESSURE 58
vs. 246–303 for real touches) but the browser always receives pressure: 0
for every pointer event. There is no way to catch this client-side; it must be
stopped in the driver.
The fix: out-of-tree ti_am335x_tsc module
The stock driver already computes Z and reports it as ABS_PRESSURE. Its only
gate is if (z <= MAX_12BIT) — it accepts 0..4095. The fix adds a lower bound:
drop any sample below a configurable ti,z-threshold.
Three minimal additions to the driver:
- A
u32 z_thresholdfield onstruct titsc. titsc_parse_dt()readsti,z-thresholdfrom the device tree (default 0 =
gate off, stock behaviour).titsc_irq()gates onz >= ts_dev->z_threshold && z <= MAX_12BIT.
The threshold ships in the overlay as ti,z-threshold = <150> — safely between
the 58 phantom floor and the 246 real-touch floor:
tsc {
...
ti,z-threshold = <150>;
};
One thing worth noting on source provenance: bone35 differs from vanilla
mainline v6.18 by exactly one line in titsc_config_wires() — a bounds-check
fix (>= instead of >). We based our patched copy on the bone35 source, not
mainline, to preserve that fix. Had we used mainline as the base we’d have
silently shipped a regression of a downstream fix.
Build and install
The module is built on the device against the running kernel’s headers
during setup:
make -C /lib/modules/$(uname -r)/build M=$PWD modules
It installs to /lib/modules/$kernel/updates/, which depmod gives precedence
over the stock in-tree module. The stock driver is a loadable module
(CONFIG_TOUCHSCREEN_TI_AM335X_TSC=m), so no kernel image change is needed —
our rebuilt .ko shadows it from next boot.
We vendor the already-patched .c directly in the repo (no on-device patch
step). A verified unified diff is also vendored alongside it for reconstruction
if needed. On a kernel bump: fetch the upstream driver at the new tag, re-apply
the three marked edits (trivial if upstream hasn’t rewritten titsc_irq()),
rebuild on the device.
Verification
# The gated module is loaded (not the stock one):
modinfo -n ti_am335x_tsc # → .../updates/ti_am335x_tsc.ko
# Ground-truth tapping trace (debian is in input group; no sudo needed):
evtest /dev/input/event1 2>&1 | tee /tmp/touch.log
grep -E 'value 40[0-9][0-9]' /tmp/touch.log # expect: empty
After reboot with the patched module and updated .dtbo: tapping trace showed
zero full-scale ABS_X glitch samples. The far-side “next” phantom could no
longer be reproduced. A 12-hour idle evtest capture logged zero events —
confirming there is no idle phantom at the hardware level.
The complete touch stack
Chromium kiosk coordinate teleport guard — full-pressure jump guard (client)
▲
libinput / Xorg CalibrationMatrix — coordinate mapping (root cause 3)
▲
kernel ti_am335x_tsc ti,z-threshold gate — low-pressure phantom drop (root cause 5)
▲
TSC controller (overlay) ti,charge-delay — release transient settle (root cause 4)
▲
4-wire resistive panel
Each layer catches something the others can’t. The Z-gate is the only layer that
sees pressure; the coordinate guard catches full-pressure teleports that a Z-gate
won’t stop. They’re complementary.
Open questions
-
Is
panel-dpithe intended 6.18 binding for tilcdc DPI panels? It works
and matches the OF-graph endpoints the upstream overlay left commented out. Is
there a preferred panelcompatiblewe should use, or any required
data-mapping/bus-formatproperty? We rely on
blue-and-red-wiring = "straight"on&lcdcfor the RGB565 format — is that
still the right place for it under the OF-graph binding? -
Should the connector fix go upstream into bb.org’s overlay? As of
v6.18.34-bone36.1, the LCD4.dtboships with the kernel package — but
the merged overlay is the stock, still-broken one (the//FIXMEand
commented-out endpoints are still there). The samepanel-dpi/OF-graph change
applies to both LCD4 and LCD7 (LCD7 carries the identical FIXME, only with
800×480 timings). Happy to share the diff formally if that helps. -
Is there a supported way to make
xf86-video-modesettinguse RGB565 on a
tilcdc plane? We couldn’t get it to stop emittingfailed to add fb -22
even atDefaultDepth 16+AccelMethod none. Falling back tofbdevworks
and the kiosk is stable, but modesetting feels like the more correct long-term
answer. If there’s a supportedADDFB2path with an explicit
DRM_FORMAT_RGB565fourcc, we’d like to know. -
Should tilcdc advertise XRGB8888 (or a format-converting plane) so
generic KMS userspace works without an explicit 16-bit config?
Verified on BeagleBone Black, Debian 13.5 Trixie, kernel 6.18.32-bone35, with
a custom cape based on the 4D Systems 4DCAPE-43T — display, touch, and
calibration all functional.
Thank you for the device tree on 6.18.x with this change.. i’ve been pulling me hair out on 7.1.x, well that error is back from something else.. Your changes as long as i stay in 6.18.x fixes that issue.. so pushed BB-BONE-LCD4-01-00A1: merge Colin Bester no encoders/connectors found… · beagleboard/BeagleBoard-DeviceTrees@2000f89 · GitHub
So here’s the real fun with “panel-dpi”… BB-BONE-LCD4-01-00A1: switch from panel-dpi/panel-timing -> newhaven,… · beagleboard/BeagleBoard-DeviceTrees@439c2dd · GitHub
diff --git a/src/arm/overlays/BB-BONE-LCD4-01-00A1.dtso b/src/arm/overlays/BB-BONE-LCD4-01-00A1.dtso
index 35c53fd3..fd89ab9b 100644
--- a/src/arm/overlays/BB-BONE-LCD4-01-00A1.dtso
+++ b/src/arm/overlays/BB-BONE-LCD4-01-00A1.dtso
@@ -170,27 +170,11 @@
panel: panel {
status = "okay";
- compatible = "panel-dpi";
+ compatible = "newhaven,nhd-4.3-480272ef-atxl", "panel-dpi";
pinctrl-names = "default";
pinctrl-0 = <&bb_lcd_lcd_pins>;
backlight = <&backlight>;
- panel-timing {
- clock-frequency = <9200000>;
- hactive = <480>;
- vactive = <272>;
- hfront-porch = <8>;
- hback-porch = <47>;
- hsync-len = <41>;
- vfront-porch = <3>;
- vback-porch = <2>;
- vsync-len = <10>;
- hsync-active = <0>;
- vsync-active = <0>;
- de-active = <1>;
- pixelclk-active = <0>;
- };
-
port {
panel_0: endpoint {
remote-endpoint = <&lcdc_0>;
For reference, here is my trusted old xorg config: (it can be simplified now days..)
voodoo@BBB-LCD:~$ cat /etc/X11/xorg.conf.d/10-xorg.conf
#/etc/X11/xorg.conf
Section "Monitor"
Identifier "Builtin Default Monitor"
EndSection
Section "Device"
Identifier "Builtin Default fbdev Device 0"
Driver "fbdev"
EndSection
Section "Screen"
Identifier "Builtin Default fbdev Screen 0"
Device "Builtin Default fbdev Device 0"
Monitor "Builtin Default Monitor"
EndSection
Section "ServerLayout"
Identifier "Builtin Default Layout"
Screen "Builtin Default fbdev Screen 0"
EndSection
Changes synced into: 6.19.x
voodoo@BBB-LCD:~$ uname -r ; dmesg | grep drm
6.19.14-bone19.4
[ 6.325198] [drm] Initialized tilcdc 1.0.0 for 4830e000.lcdc on minor 0
[ 6.454428] tilcdc 4830e000.lcdc: [drm] fb0: tilcdcdrmfb frame buffer device
[ 20.166917] systemd[1]: Starting modprobe@drm.service - Load Kernel Module drm...
Synced into 7.0.x
voodoo@BBB-LCD:~$ uname -r ; dmesg | grep drm
7.0.12-bone19.1
[ 6.399372] [drm] Initialized tilcdc 1.0.0 for 4830e000.lcdc on minor 0
[ 6.541289] tilcdc 4830e000.lcdc: [drm] fb0: tilcdcdrmfb frame buffer device
[ 20.390062] systemd[1]: Starting modprobe@drm.service - Load Kernel Module drm...
Synced into 7.1.x
voodoo@BBB-LCD:~$ uname -r ; dmesg | grep drm
7.1.0-bone8.1
[ 6.240694] [drm] Initialized tilcdc 1.0.0 for 4830e000.lcdc on minor 0
[ 6.368136] tilcdc 4830e000.lcdc: [drm] fb0: tilcdcdrmfb frame buffer device
[ 19.839837] systemd[1]: Starting modprobe@drm.service - Load Kernel Module drm...
Merged fix for issue 4: BB-BONE-LCD4-01-00A1: update ti,charge-delay to 0xB000, Colin Bester:… · beagleboard/BeagleBoard-DeviceTrees@7fde129 · GitHub
Did you get it working with compatible = "newhaven,nhd-4.3-480272ef-atxl", "panel-dpi";? newhaven seems to always give me issues somewhere or other.