4DCape LCD on BeagleBone Black Debian 13.5 2026-05-19 IoT (v6.18.x)

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:

  1. Device tree — modern OF-graph binding. Replaced the legacy ti,tilcdc,panel + panel-info with compatible = "panel-dpi" + a panel-timing subnode, and un-commented / wired up the port { endpoint } on both the panel and &lcdc (panel_0lcdc_0). After that, tilcdc registers a DPI-1 connector and the no encoders/connectors found error is gone. Same rev-02 480×272 timings, same pinmux, same blue-and-red-wiring = "straight".

  2. 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 to ADDFB a 32bpp XRGB8888 buffer. DefaultDepth 16 + AccelMethod none + ShadowFB didn’t clear it. Switching to xf86-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: 0x4000xB000 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’s bpp = 16 has no panel-dpi equivalent. The 16bpp RGB565
    format stays on &lcdc via blue-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 keep gpio-keys in 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:

  1. A u32 z_threshold field on struct titsc.
  2. titsc_parse_dt() reads ti,z-threshold from the device tree (default 0 =
    gate off, stock behaviour).
  3. titsc_irq() gates on z >= 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

  1. Is panel-dpi the 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 panel compatible we should use, or any required
    data-mapping / bus-format property? We rely on
    blue-and-red-wiring = "straight" on &lcdc for the RGB565 format — is that
    still the right place for it under the OF-graph binding?

  2. Should the connector fix go upstream into bb.org’s overlay? As of
    v6.18.34-bone36.1, the LCD4 .dtbo ships with the kernel package — but
    the merged overlay is the stock, still-broken one (the //FIXME and
    commented-out endpoints are still there). The same panel-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.

  3. Is there a supported way to make xf86-video-modesetting use RGB565 on a
    tilcdc plane?
    We couldn’t get it to stop emitting failed to add fb -22
    even at DefaultDepth 16 + AccelMethod none. Falling back to fbdev works
    and the kiosk is stable, but modesetting feels like the more correct long-term
    answer. If there’s a supported ADDFB2 path with an explicit
    DRM_FORMAT_RGB565 fourcc, we’d like to know.

  4. 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.

1 Like

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.