Raspberry Pi Touch Display 2 (5-inch) on BeagleY-AI: black screen after successful panel init

Raspberry Pi Touch Display 2 (5-inch) on BeagleY-AI: black screen after successful panel init

Hardware: BeagleY-AI (TI AM67A / J722S), kernel 6.1.83-ti-arm64-r74

Display: Raspberry Pi Touch Display 2, 5-inch (ILI9881C MIPI DSI panel, 720x1280, 2 data lanes, connected via CSI1 FPC connector)

Touch: Goodix GT911 at 0x5d on i2c-3, no interrupt pin (using polling)

What works

The display pipeline comes up successfully:

  • Ported rpi-panel-v2-regulator driver: the MCU at 0x45 on i2c-3 is bound and functional, backlight and panel power sequencing work

  • Ported ILI9881C panel driver with the raspberrypi,dsi-5inch entry: all 185 DSI LP init commands succeed

  • DCS read of GET_POWER_MODE (0x0A) returns 0x9c: display on, sleep out, booster on

  • DRM pipeline: crtc-1 active, DSI-1 connector linked, plane with framebuffer assigned, mode 720x1280@60Hz

  • Framebuffer at 32-bit DMA address (cma=128M@0xb0000000, dma_addr=0xb0200000)

  • MIPI switch GPIOs correct: OE=LOW (switch enabled), SEL=LOW (DSI selected)

  • HDMI output works fine

  • Touch works (Goodix polling mode at ~60 Hz)

What does not work

Screen is lit (backlight on) but completely black. No image visible. Writing test patterns to /dev/fb0 produces no visible change.

What we tried and ruled out

All of these made no difference:

  • Fixing mode validation (drm_mode_set_crtcinfo)

  • Fixing pixel clock (htotal=937, clock=83393 kHz)

  • Adding MEDIA_BUS_FMT_RGB888_1X24 in get_modes

  • prepare_upstream_first = true

  • PANEL_ON_IN_PREPARE flag (DISPLAY_ON sent in LP mode before HS video)

  • MIPI_DSI_MODE_VIDEO_BURST mode flags (cdns-dsi does not support burst mode, returns -ENOTSUPP)

  • MIPI_DSI_CLOCK_NON_CONTINUOUS

  • CMA placement at 32-bit address range

  • Removing SET_TEAR_ON from prepare()

What we think the root cause is

We dug into the cdns-dsi driver (drivers/gpu/drm/bridge/cadence/cdns-dsi-core.c) and read the hardware registers at 0x30500000 via /dev/mem. Here is what we found:

PHY_CTL register (0x08) = 0x00000011

This means only DATA_LANE_EN(1) is set (physical lane 0). DATA_LANE_EN(2) (physical lane 1) is not set. The LANE_STS register confirms: lane 0 is in WRITE state (transmitting), lane 1 is stuck in START state (never transitions).

The source code in cdns_dsi_init_link() has:


for (i = 1; i < output->dev->lanes; i++)

val |= DATA_LANE_EN(i);

The DATA_LANE_EN macro uses 1-based indexing: BIT((x) - 1). So for a 2-lane panel, the loop runs only for i=1 (enabling physical lane 0), and misses i=2 (physical lane 1). Changing < to <= would fix the loop.

However, when we disassembled the compiled kernel binary, the ARM64 code actually uses b.cs (branch if carry set, meaning branch if lanes >= i), which already produces i=1,2 for lanes=2. This suggests the lanes value loaded at runtime from phy_opts.mipi_dphy.lanes might be 1, not 2, despite the panel descriptor having .lanes = 2.

MAIN_STS register (0x24) = 0x0000000F: PLL locked, clock lane ready, both data lanes report ready. DPHY_ERR = 0 (no errors). The DSI link appears healthy at the physical level, but only one data lane is actually transmitting.

We also tried rebuilding the kernel from the same git tag (beagleboard/linux at 6.1.83-ti-arm64-r74) with the same config (from /proc/config.gz) and the lane fix applied. The rebuilt kernels consistently have broken DSI interrupts (0 counts on IRQ 366 at /proc/interrupts) while the original kernel has 191 interrupts. All DCS commands time out with -110. We could not determine why: the source is identical, the config is identical, and the toolchain is the same (gcc 14.2.0 on the board itself). This prevented us from testing the lane fix.

Questions for the community

  1. Why might phy_opts.mipi_dphy.lanes be 1 at runtime when the panel descriptor sets .lanes = 2? Is there a DT property or DSI host quirk that overrides the lane count?

  2. Has anyone successfully used a 2-lane MIPI DSI panel with the cdns-dsi driver on AM67A/J721E? If so, what was needed?

  3. Is the off-by-one in the DATA_LANE_EN loop a known issue? The same loop pattern exists in the upstream Cadence DSI driver.

  4. Are there known issues with rebuilding the BeagleBoard kernel from the git tag that would cause interrupts to break? We tried both cross-compilation (aarch64-linux-gnu-gcc 13.3.0) and native compilation on the board (gcc 14.2.0), same result.

Full kernel logs, device tree overlay, and register dumps available if helpful.

Tomi has been posting RFC’s to get this to work on mainline, now would be a good time to double check and test them. (i’ve got them back ported in our 7.0/7.1 branches)..

Regards,

Thanks Robert, the 7.0 kernel works much better, the overlay applies correctly, the rpi_touchscreen_v2 driver binds to the MCU, and the DSI pipeline comes up. We’re almost there but found one remaining bug.

The cdns-dsi driver in 7.0.11 has an off-by-one error in cdns_dsi_init_link() the loop for (i = 1; i < lanes; i++) only enables N-1 data lanes, so lane 1 never transmits. Changing < to <= fixes it. The PHY_CTL register confirms: 0x11 (only lane 0) instead of 0x13 (both lanes).

We tried rebuilding the kernel:

  • Cloned v7.0.11 from kernel.org stable, applied the fix, used the board’s /proc/config.gz

  • Build fails, several TI-specific drivers are missing from upstream and the config doesn’t translate cleanly

  • Tried the same with the GitHub beagleboard/linux repo at the old 6.1 tag, builds succeed but DSI interrupts break (identical source and config, different result)

  • Also tried live register writes and kprobe patches, but lane 1 stays stuck in START without proper D-PHY init at boot

Could you share the build script or source tree you use to produce the 7.0.11-arm64-k3-r20 kernel? A tarball or git branch with the TI patches applied would let us add this one-line fix and be done.

Here’s my repo, GitHub - RobertCNelson/arm64-multiplatform · GitHub use the v7.0 branch to match that kernel patchset

@ss13ms110 here is Tomi’s latest patchset: Making sure you're not a bot! (helps when not on phone, he’d love to here your dsi lane update..)

Regards,

Thank you very much Robert. I am in field for this week, will get back on Monday and post an update.

Thank you

Thanks Robert,

I found the linux-stable-rcn-ee repo at v7.0.11, applied the fix, used patches/defconfig from arm64-multiplatform, and got a successful build after disabling the custom logo (LOGO_BEAGLE_CLUT224) and built-in firmware (EXTRA_FIRMWARE).

The Image compiles but U-Boot falls back to the original kernel on boot.

Is there a specific defconfig or build step I am missing to produce a bootable Image matching the 7.0.11-arm64-k3-r20 deb?

Thank you

Increments the build number, install via dpkg and disable the 7.0 meta package that forces the next stable release when I tag and push out

Hi Robert,

Some progress, and thank you - your arm64-multiplatform v7.0 build worked
end to end. AUTO_BUILD=1 ./build_deb.sh produced a linux-image-7.0.11-arm64-k3-r20
deb with our one-line cdns-dsi lane fix, dpkg -i installed it cleanly, and after
apt-mark hold bbb.io-kernel-7.0-k3 bbb.io-kernel-tasks it sticks across upgrades.
The “U-Boot falls back” problem is gone.

I’ve now got the RPi Touch Display 2 (5-inch) almost fully up on 7.0, and I
think I found a backport gap you might be able to close in the v7.0 BeagleY-AI dtb.

What I found (6.1-ti vs 7.0 base dtb): comparing the stock
k3-am67a-beagley-ai.dtb from the 6.1.83-ti image against the 7.0 one (your
backport of Tomi’s mainline DSS work), the 7.0 base dtb dropped two BeagleY-AI
specific bits that 6.1 had
, both needed to talk to the display over the DSI
connector’s i2c (i2c-3 = dsi0_csi1_i2c = i2c@20000000):

  1. The MIPI mux node - 6.1 has a gpio-leds node driving MIPI_SWITCH_OE
    (main_gpio0 line 1) high and MIPI_SWITCH_SEL (line 2) low, with
    mipi-switch-default-pins muxing the two pads. 7.0 has no such node.

  2. The i2c-3 pinctrl - 6.1’s i2c@20000000 is status=okay with
    pinctrl-0 = <&main_i2c0_pins_default> and clock-frequency. In 7.0 that node
    is disabled and has no pinctrl (the main-i2c0-default-pins node exists but
    nothing references it).

Without those, i2c-3 to the panel’s control MCU (0x45) just times out (-110), which
is what blocked us for days. I re-added both in a DT overlay - the mux as a
gpio-hog on main_gpio0 (so it applies at gpiochip registration), plus
pinctrl-0 = <&main_i2c0_pins_default> + clock-frequency on the i2c node - and
now i2c-3 works: the GT911 touch (ID 911), the 0x45 MCU
(rpi_touchscreen_v2), and the ILI9881C panel (ili9881c-dsi) all bind.

Could the v7.0 BeagleY-AI base dtb carry the mipi-switch + dsi0_csi1_i2c pinctrl
nodes like 6.1 did?
That would make the DSI connector usable out of the box
again (cameras on CSI would want the mux too).

The one remaining blocker is a probe-ordering issue in tidss/cdns-dsi:

[2.000378] [drm] Missing drm_bridge_add() before attach
[2.062051] tidss 30220000.dss: [drm] Cannot find any crtc or sizes

tidss assembles at ~2s with HDMI only, but the DSI panel chain finishes binding
~10s (the panel waits on the MCU over i2c-3, which binds at the deferred-probe
timeout). tidss never defers or re-scans for the DSI output, so there’s no DSI
crtc/connector and the panel never enables.

Two questions where you might save me time before Tomi replies:

  • Do you (or Tomi) have a working 7" or TD2 DSI overlay for 7.0 I could use as a
    reference for the DSS/DSI videoport wiring? Tomi mentioned testing the 7" DSI on
    this board with extra overlays.

  • Any tip on the tidss probe-ordering for an i2c-MCU-gated DSI panel - should
    tidss -EPROBE_DEFER until the DSI bridge is added, or is there a DT way to make
    the panel chain ready before tidss?

I’m also writing up the tidss ordering issue + the cdns-dsi lane fix for Tomi’s
lore thread, but I figured you’d have the fastest read on the base-dtb gap.

Thanks again,
Shubham

@RobertCNelson

Hello Robert,

Any update on this?

Thank you

Best
Shubham

Hi Robert,

Big update, the RPi Touch Display 2 (5-inch) is now alive on your 7.0.11-arm64-k3-r20 build. It initializes and reports display-on over DSI (GET_POWER_MODE = 0x9c). Three fixes got it there:

  • MIPI mux OE polarity (the big one): the BeagleY-AI mux enable is active-low, but the overlay had been driving MIPI_SWITCH_OE high, so the high-speed data-lane mux was disabled while i2c still worked via its own path. Flipping OE low woke the panel.

  • Overlay: DSS port@1 has to be nested under the controller’s ports node (on 7.0 the dss1 symbol is the DSS node, not the ports container like 6.1’s dss1_ports), else tidss returns -ENODEV.

  • cdns-dsi: bridge now adds in probe, so the old “Missing drm_bridge_add() / Cannot find any crtc” probe-ordering issue is gone and tidss attaches.

Build note for anyone following: must be built with the dl/gcc-15.2.0-nolibc toolchain + LOCALVERSION=-arm64-k3-r20; a plain make Image yields 7.0.11+ and the modules won’t load.

One blocker left: the DSS video port scans at a clean 60 Hz and the panel is initialized, but the cdns-dsi data lanes stay 100% idle, the DSS isn’t feeding pixels into the cdns DPI input (same result on VP1 or VP2). So it’s the on-chip DSS to cdns DPI feed, not the DT wiring or the panel.

I’ve posted the full register-level detail + questions to Tomi on the lore v3 thread.

Question for you: does the 7.0.x-arm64-k3 backport carry all of Tomi’s cdns-dsi / DSS-internal-DPI pieces, or is there a known gap there? I’d like to rule out a backport miss before assuming it’s purely config.

Thanks again,

Shubham