The 4" 720×720 RGB display on my Luckfox Pico Ultra W was blank under a custom Yocto image. It worked fine on the stock buildroot firmware from the vendor. Everything I could measure matched: device tree, VOP registers, clocks, IOMUX, CMA totals, DRM state. Days of register-level debugging got nowhere.
The actual cause: the panel adapter board carries a tiny WCH CH32V003F4 RISC-V MCU whose reset line is wired to GPIO0_A1 on the RV1106. Stock's U-Boot releases that reset early in boot with gpio set 1 1. My U-Boot didn't. So the panel's on-board MCU sat in reset forever, the panel never ran its init sequence, and no matter how correctly the VOP fed it pixels, nothing showed up.
The fix: three lines.
static int luckfox_execute_cmd(void)
{
run_command("gpio set 1 1", 0);
return 0;
}
… plus CONFIG_CMD_GPIO=y (otherwise run_command silently fails with Unknown command 'gpio') and CONFIG_LOGO=y + CONFIG_FRAMEBUFFER_CONSOLE=y in the Linux kernel (otherwise DRM allocates an empty framebuffer and VOP scans zeros onto the now-awake panel, which the human eye is absolutely incapable of distinguishing from "still blank").
None of that is in the upstream rockchip-linux/u-boot tree I was building from. All of it lives quietly in the Luckfox SDK fork.
If you're here because your Luckfox Pico Ultra W's screen is dark, the short version is: add those three things. The rest of this post is the debugging journey that got me there, because I think the journey matters.
I was building a Yocto BSP for the Luckfox Pico Ultra W because the stock buildroot image is fine for a hello-world demo but not for anything that needs a modern distro, modern systemd, reproducible builds, or packages I actually wanted.
The hardware:
RV1106G3 (Cortex-A7, 1.1 GHz, single-core)
256 MB DDR3L
8 GB eMMC
AIC8800DC for WiFi/BT
4" round 720×720 LCD over parallel RGB + GT911 touch over I²C
All the peripherals came up under my Yocto image: NPU hit 332 FPS on BlazeFace, the microphone captured stereo PCM, Ethernet and the SDIO WiFi chip enumerated correctly. Backlight PWM toggled. Everything except the actual pixels on the actual LCD.
Power on. Backlight lights. Screen stays uniformly dark. Not a flicker, not an eyestrain-level-of-gray. Black. Stock firmware on the exact same board ran the Luckfox logo on the screen from U-Boot onwards, through the kernel handoff, all the way to the buildroot login prompt. So the hardware worked. Something in my build didn't.
I'll summarise the theories I chased and then marked "does not help" in my memory notes. I spent somewhere north of three weeks on this.
DRM_MODE_CONNECTOR_DPI missing from the VOP output-switch. The Rockchip rgb driver sets output_type = DPI, but the VOP's atomic_enable switch only had cases for LVDS, HDMI, DSI. So rgb_en never got set. This was a real bug — in an older commit of the SDK kernel. Current kernel already had a DPI: fallthrough to LVDS:. No-op fix.
GRF_SOC_CON1 register comparison. A community thread suggested this register held RGB output-mux bits. I dumped it on the stock board (0x6C4) and on mine (also 0x6C4 after correcting a README typo). Identical. Dead end, but a confusing one because the README initially said 0xC8 and I believed it for days.
U-Boot needs to do the display init. Some Rockchip boards do the first display init from U-Boot and hand off to the kernel via the drm-logo reserved-memory region. I spent two evenings wiring that up and verifying the panel/backlight/VOP nodes appeared in the U-Boot DTB. Then I dumped the stock U-Boot's DTB — it has no display nodes at all. Stock U-Boot does not touch the display. Dead end.
rockchip,data-sync-bypass on the rgb node. Someone's working config had it. Mine didn't. Added it. Still blank. Checked stock: stock doesn't have it either. Reverted.
assigned-clock-rates for dclk_vop. Without it the clock came up at 125 MHz instead of 30 MHz (per clk_summary). Added it. Clock came up right. Still blank.
reset-gpios on the panel node with a 200 ms reset-delay. Stock has no reset-gpios. Reverted.
route_rgb status = "okay". Stock has it disabled. Reverted.
CMA_INACTIVE. Stock's dtsi has linux,cma { inactive; } which I read as "CMA never starts". I was wrong — the keyword is parsed by the Rockchip-specific CONFIG_CMA_INACTIVE kernel option and means "don't activate until a consumer asks for it", which is what you want. CMA on mine reported 42 MB.
rk_dma_heap_cma=66M on the kernel command line. This one was at least real and useful: the Rockchip DRM driver allocates from a dedicated rk_dma_heap CMA pool separate from linux,cma. Without the cmdline parameter, CmaTotal only showed the small dtsi pool. Adding it gave me 77824 kB total, which matched stock exactly. The panel was still blank. At this point I stopped and re-read my own notes because every individual data point now matched a working system and the panel was still dark.
Out of ideas, I wrote a small shell script (S99zdump) that captured /proc/cmdline, /proc/meminfo, /sys/class/drm/*, the VOP register window via busybox devmem, and a tarball of /proc/device-tree, and dd'd the output to /oem. I injected it into a copy of the stock buildroot rootfs with debugfs, re-flashed, let it boot, rebooted into maskrom, and used upgrade_tool rl to read /oem back. Roughly an hour per round trip.
The dump showed a few interesting things:
CmaTotal = 77824 kB. Matched mine after I added rk_dma_heap_cma.
/proc/cmdline was almost identical to mine.
VOP register dump was nearly all zero.
That last one threw me. On my system the VOP register window was fully populated — all the timing registers, framebuffer addresses, win1 config, the lot. On stock, almost every register was 0.
I spent another hour convincing myself stock was driving the display a completely different way. Maybe through OP-TEE. Maybe through a secure-world handoff. Maybe U-Boot set up the whole thing and kernel just left it alone.
None of that was right either. The real explanation was much simpler and much more embarrassing: the stock image I captured had never been configured to enable RGB output. The stock buildroot ships with the panel, rgb, and linux,cma nodes all set to disabled, and the user (me, any user) has to run luckfox-config to turn on the display. Running it flips those three nodes to okay and uses fdtoverlay to patch the DTB in the boot partition in-place. I flashed stock, checked /mnt/cfg, saw nothing there, concluded "stock doesn't use overlays at runtime", and missed that stock as I had flashed it was also blank. The VOP registers were zero on stock because the driver had never bound. I was comparing a blank system to a blank system.
I had been treating that data as the reference for weeks.
At this point I stopped probing and just looked at the adapter board with real eyes. Three things stood out on the silkscreen:
a pad marked DIO
a pin marked GT911
WCHV003F4
The first two I shrugged off. DIO is ambiguous, and GT911 is the touch controller I already knew about.
The third one hit like a brick. WCHV003F4 is the silkscreen for a WCH CH32V003F4, a 20-pin TSSOP RISC-V microcontroller. It's $0.10 in bulk. And it has no business being on a passive RGB panel adapter.
Suddenly my mental model shattered. The adapter isn't a passive dumb RGB panel. It's an active board with an MCU that drives the panel's internal controller. That MCU needs to be awake before the panel will show anything. My custom Yocto build wasn't booting it.
I went looking for where stock wakes it up.
In the Luckfox SDK tree there's a file called project/cfg/BoardConfig_IPC/BoardConfig-EMMC-Buildroot-RV1106_Luckfox_Pico_Ultra-IPC.mk. One line jumped out:
export RK_UBOOT_DEFCONFIG_FRAGMENT="rk-emmc.config rv1106-luckfox-rgb-reset.config"
rv1106-luckfox-rgb-reset.config. That's very specific. Inside:
CONFIG_LUCKFOX_EXECUTE_CMD=y
And in the Luckfox fork of U-Boot, arch/arm/mach-rockchip/board.c:
#ifdef CONFIG_LUCKFOX_EXECUTE_CMD
static int luckfox_execute_cmd(void)
{
/* Luckfox Pico RGB MCU reset gpio */
run_command("gpio set 1 1", 0);
return 0;
}
#endif
And further down in the same file, inside board_late_init:
#ifdef CONFIG_LUCKFOX_EXECUTE_CMD
luckfox_execute_cmd();
#endif
There it is. gpio set 1 1 in U-Boot command syntax means "set GPIO number 1 high" — that's GPIO0_A1, the reset line for the adapter's CH32V003. Without it, the MCU stays in reset forever.
This symbol and function do not exist in rockchip-linux/u-boot, which is the tree my Yocto recipe fetches from. They only live in the LuckfoxTECH fork. My build had no knowledge of any of this.
I added the Kconfig symbol, the function, and the call site via a do_configure:prepend awk script in my U-Boot recipe, rebuilt, reflashed, and rebooted. The screen was still dark.
This was the point where I almost lost it. I had found the exact patch stock uses, ported it correctly, confirmed it landed in the built U-Boot binary, and the display was still blank.
I broke into U-Boot, typed gpio set 1 1 at the prompt, and got back:
Unknown command 'gpio' - try 'help'
My U-Boot didn't have CONFIG_CMD_GPIO=y. The gpio command didn't exist. run_command("gpio set 1 1", 0) had been silently returning an error and luckfox_execute_cmd had been ignoring that error. For the entire boot, every boot, since I had added the code.
Added CONFIG_CMD_GPIO=y to the same config fragment. Rebuilt. Reflashed. Booted. Screen came up.
Except it came up showing… black.
I knew at this point that the panel was alive (the adapter MCU was now being released from reset, and a subtle flicker on boot told me something was going through). But the framebuffer was empty. With the MCU awake, I filled /dev/fb0 with 0xffs. Screen stayed dark.
That's when I noticed what was in rv1106-emmc.cfg:
# Note: stock Buildroot does NOT enable VT_CONSOLE or FRAMEBUFFER_CONSOLE.
# fbcon allocates its own framebuffer (at a different address than drm-logo)
# which interferes with the proper VOP→panel handoff. Display stays blank
# despite all other registers being correct. Match stock's minimal config.
I wrote that comment. Weeks ago. When I was still debugging via register comparisons and had convinced myself, wrongly, that fbcon was interfering. The cargo cult ran deep.
The truth was the opposite. Stock's real kernel defconfig (luckfox_rv1106_linux_defconfig, which I had never built against because I used the generic rv1106_defconfig instead) enables CONFIG_FRAMEBUFFER_CONSOLE=y and CONFIG_LOGO=y. Without them, the kernel allocates a DRM framebuffer and then never writes anything into it. The VOP faithfully scans that buffer to the panel at 31.25 MHz, 49 Hz, and the panel faithfully renders all-zero pixels. Which is black. Which my eye reads as "still broken".
Enabled the four options — VT, VT_CONSOLE, FRAMEBUFFER_CONSOLE, LOGO, LOGO_LINUX_CLUT224 — rebuilt, reflashed, rebooted.
The Tux penguin came up.
Question the dataset, not just the hypothesis. I spent a lot of energy comparing things to a "working stock" that was never actually working. Every time I would have saved days by re-checking whether my reference was the reference I thought it was.
Ask what it looks like, not what your probes say. The silkscreen on the adapter board was visible all along. I could have caught WCHV003F4 in a five-second visual inspection on day one. Instead I was three weeks deep reading VOP register definitions.
Silent failures in wrapper layers are the worst failures. U-Boot's run_command silently ignoring "Unknown command 'gpio'" cost me hours on its own — and it wasn't the only one on this project. (The Rockchip kernel's scripts/gcc-wrapper.py silently deletes .o files whose gcc output contains a non-whitelisted warning, which made an entire separate debugging session on the AIC8800DC WiFi driver look like a mysterious "make: Error 1 with no output" when it was really a GCC 13 dangling-pointer false positive.)
Cargo-cult comments are worse than no comments. The fbcon-disabled comment in my cfg file wasn't just wrong, it was actively blocking me from trying the right thing, because I trusted my own past self.
The LuckfoxTECH SDK is a fork of upstream Rockchip. If you build from upstream, you will miss patches that the vendor quietly relies on. Plan for that. Either build from the LuckfoxTECH repo directly, or track which fork-only patches your board needs and port them explicitly.
A Yocto BSP for the Luckfox Pico Ultra W lives at github.com/OOHehir/luckfox-pico-yocto. It builds a bootable WIC image with a working display, working NPU (332 FPS on BlazeFace via rknn_set_io_mem), working microphone, working AIC8800DC WiFi (driver compiles on SDK kernel 5.10.160 once you suppress the GCC 13 -Wdangling-pointer false positive in rwnx_rx.c), and a small CGI status page with a toggleable heartbeat LED, reachable at http://<board-ip>/ once you get DHCP.
The recipe layer is self-contained — it needs only poky, nothing from the RK3506-targeted meta-luckfox-bsp or meta-luckfox-distro layers. Clone, source oe-init-build-env, bitbake luckfox-image-minimal, flash with rkdeveloptool, boot.
Still open:
camera CSI (sc3336 / mis5001),
hardware H.264 via mpp_vcodec,
OP-TEE secure boot,
watchdog,
RTC alarms — all still untapped, all in the DT.
If you're building for the same board and hit the blank-screen wall, skip the register dumps and go straight to the three things above. You'll save yourself a month.
The WCHV003F4 located on the screen adapter board