Pre-merged panel DTBs: eliminate U-Boot fdt apply bugs

Users reported that non-default panel overlays caused broken display,
audio, and controls. Root cause: BSP U-Boot's fdt apply has bugs with
property replacement of different-sized data, corrupting the DTB.

Fix: apply overlays at build-time using fdtoverlay (verified working)
instead of at boot-time. Each panel now gets a pre-merged kernel-*.dtb.
U-Boot simply loads the right DTB by name — no fdt apply needed.

- generate-panel-dtbos.sh: add fdtoverlay pre-merge step
- build-image.sh: copy pre-merged DTBs instead of ScreenFiles
- boot.ini: load PanelDTB by name, remove fdt addr/resize/apply
- panel-detect.py: write DTB filename instead of DTBO overlay path
This commit is contained in:
Douglas Teles
2026-02-28 00:14:54 -03:00
parent e4294ab47b
commit a1efd4fc01
4 changed files with 148 additions and 139 deletions

View File

@@ -266,40 +266,33 @@ cp "$ROOTFS_DIR/boot/Image" "$MOUNT_BOOT/"
cp "$ROOTFS_DIR/boot/$KERNEL_DTB_NAME" "$MOUNT_BOOT/kernel.dtb"
log " kernel.dtb <- $KERNEL_DTB_NAME"
# --- Boot partition: Panel DTBO overlays (variant-specific) ---
PANELS_DIR="$OUTPUT_DIR/panels/ScreenFiles"
if [ -d "$PANELS_DIR" ]; then
mkdir -p "$MOUNT_BOOT/ScreenFiles"
# --- Generate pre-merged panel DTBs (overlay applied at build-time) ---
# Runs generate-panel-dtbos.sh to create DTBOs + pre-merged DTBs in one step.
log " Generating panel DTBs..."
"$SCRIPT_DIR/scripts/generate-panel-dtbos.sh"
# --- Boot partition: Pre-merged panel DTBs ---
# Each panel gets its own kernel-*.dtb with the overlay already merged.
# No fdt apply needed at boot — U-Boot just loads the right DTB by name.
MERGED_DIR="$OUTPUT_DIR/panels/merged"
if [ -d "$MERGED_DIR" ]; then
panel_count=0
# Copy variant-specific panels (explicit names — NO glob with spaces)
if [ "$VARIANT" = "original" ]; then
# R36S original: Panel 0 through Panel 5
for i in 0 1 2 3 4 5; do
if [ -d "$PANELS_DIR/Panel $i" ]; then
cp -r "$PANELS_DIR/Panel $i" "$MOUNT_BOOT/ScreenFiles/"
panel_count=$((panel_count + 1))
fi
# R36S original: kernel-panel0.dtb through kernel-panel5.dtb
for dtb in "$MERGED_DIR"/kernel-panel*.dtb; do
[ -f "$dtb" ] && cp "$dtb" "$MOUNT_BOOT/" && panel_count=$((panel_count + 1))
done
else
# R36S clone: Clone Panel 1 through Clone Panel 10 + extras
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ -d "$PANELS_DIR/Clone Panel $i" ]; then
cp -r "$PANELS_DIR/Clone Panel $i" "$MOUNT_BOOT/ScreenFiles/"
panel_count=$((panel_count + 1))
fi
done
for extra in "R36 Max" "RX6S"; do
if [ -d "$PANELS_DIR/$extra" ]; then
cp -r "$PANELS_DIR/$extra" "$MOUNT_BOOT/ScreenFiles/"
panel_count=$((panel_count + 1))
fi
# R36S clone: kernel-clone*.dtb + kernel-r36max.dtb + kernel-rx6s.dtb
for dtb in "$MERGED_DIR"/kernel-clone*.dtb "$MERGED_DIR"/kernel-r36max.dtb "$MERGED_DIR"/kernel-rx6s.dtb; do
[ -f "$dtb" ] && cp "$dtb" "$MOUNT_BOOT/" && panel_count=$((panel_count + 1))
done
fi
log " ScreenFiles: ${panel_count} panel overlays ($VARIANT)"
log " Panel DTBs: ${panel_count} pre-merged ($VARIANT)"
else
warn "Panel DTBOs not found! Run scripts/generate-panel-dtbos.sh first"
warn "Pre-merged panel DTBs not found! Run scripts/generate-panel-dtbos.sh first"
fi
# --- Boot partition: U-Boot display DTB ---

View File

@@ -16,7 +16,7 @@ odroidgoa-uboot-config
# --- Turn on LED immediately (feedback: device is booting) ---
gpio set b5
setenv dtbo_loadaddr "0x01e00000"
setenv tmp_loadaddr "0x01e00000"
setenv dtb_loadaddr "0x01f00000"
setenv loadaddr "0x02000000"
@@ -31,27 +31,25 @@ else
poweroff
fi
# --- Panel overlay (read panel.txt written by panel-detect.py) ---
setenv PanelDTBO ""
mw.b ${dtbo_loadaddr} 0x00 0x400
if load mmc ${mmcdev}:1 ${dtbo_loadaddr} panel.txt; then
env import -t ${dtbo_loadaddr} ${filesize}
# --- Panel DTB selection (read panel.txt written by panel-detect.py) ---
# Pre-merged DTBs: overlay already applied at build-time (no fdt apply needed).
# panel.txt contains PanelDTB=kernel-panelN.dtb (or empty for default).
setenv PanelDTB ""
mw.b ${tmp_loadaddr} 0x00 0x400
if load mmc ${mmcdev}:1 ${tmp_loadaddr} panel.txt; then
env import -t ${tmp_loadaddr} ${filesize}
fi
# Select DTB: pre-merged panel DTB if set, otherwise default kernel.dtb
if test -n "${PanelDTB}"; then
setenv dtb_name "${PanelDTB}"
echo "Panel DTB: ${PanelDTB}"
else
setenv dtb_name "kernel.dtb"
fi
# --- Load DTB and boot (Image already loaded above) ---
if load mmc ${mmcdev}:1 ${dtb_loadaddr} kernel.dtb; then
# Apply panel DTBO overlay if PanelDTBO is set (non-default panel)
if test -n "${PanelDTBO}"; then
if load mmc ${mmcdev}:1 ${dtbo_loadaddr} "${PanelDTBO}"; then
fdt addr ${dtb_loadaddr}
fdt resize 8192
fdt apply ${dtbo_loadaddr}
echo "Panel overlay: ${PanelDTBO}"
else
echo "WARN: overlay not found: ${PanelDTBO}"
fi
fi
if load mmc ${mmcdev}:1 ${dtb_loadaddr} ${dtb_name}; then
setenv bootargs "root=__ROOTDEV__ rootwait rw console=ttyFIQ0 loglevel=0 quiet vt.global_cursor_default=0 consoleblank=0 printk.devkmsg=off fsck.mode=skip"

View File

@@ -475,64 +475,99 @@ for panel_name in "${CLONE_ORDER[@]}"; do
done
#------------------------------------------------------------------------------
# Create ScreenFiles directory structure (for boot.ini panel auto-detect)
# Pre-merge DTBs (overlay applied at build-time, not boot-time)
#
# This eliminates U-Boot's fdt apply at boot, which has known bugs in BSP
# U-Boot (property replacement with different-sized data can corrupt the DTB).
# Instead, fdtoverlay runs on the build host where it's verified to work.
#------------------------------------------------------------------------------
log ""
log "Creating ScreenFiles directory structure..."
log "=== Pre-merging Panel DTBs ==="
SCREENFILES_DIR="$OUTPUT_DIR/ScreenFiles"
rm -rf "$SCREENFILES_DIR"
MERGED_DIR="$OUTPUT_DIR/merged"
rm -rf "$MERGED_DIR"
mkdir -p "$MERGED_DIR"
# Check for fdtoverlay
if ! command -v fdtoverlay &>/dev/null; then
error "fdtoverlay not found. Install with: sudo apt install device-tree-compiler"
fi
# Determine base DTBs (passed via --base-dtb-original and --base-dtb-clone, or auto-detect)
BASE_DTB_ORIGINAL="${BASE_DTB_ORIGINAL:-$PROJECT_DIR/output/boot/rk3326-gameconsole-r36s.dtb}"
BASE_DTB_CLONE="${BASE_DTB_CLONE:-$PROJECT_DIR/output/boot/rk3326-gameconsole-r36s-clone-type5.dtb}"
MERGED_COUNT=0
# R36S original panels (Panel 0-5)
for panel_num in 0 1 2 3 4 5; do
dtbo_file="$OUTPUT_DIR/panel${panel_num}.dtbo"
if [ -f "$dtbo_file" ]; then
target_dir="$SCREENFILES_DIR/Panel ${panel_num}"
mkdir -p "$target_dir"
cp "$dtbo_file" "$target_dir/mipi-panel.dtbo"
log " ScreenFiles/Panel ${panel_num}/mipi-panel.dtbo"
fi
done
if [ -f "$BASE_DTB_ORIGINAL" ]; then
log "Base DTB (original): $BASE_DTB_ORIGINAL"
for panel_num in 0 1 2 3 4 5; do
dtbo_file="$OUTPUT_DIR/panel${panel_num}.dtbo"
merged_file="$MERGED_DIR/kernel-panel${panel_num}.dtb"
if [ -f "$dtbo_file" ]; then
if fdtoverlay -i "$BASE_DTB_ORIGINAL" -o "$merged_file" "$dtbo_file" 2>/dev/null; then
log " kernel-panel${panel_num}.dtb ($(stat -c%s "$merged_file") bytes)"
MERGED_COUNT=$((MERGED_COUNT + 1))
else
warn " Failed to merge panel${panel_num}.dtbo"
fi
fi
done
else
warn "Base DTB (original) not found: $BASE_DTB_ORIGINAL"
fi
# Clone panels
for panel_name in "${CLONE_ORDER[@]}"; do
safe_name=$(echo "$panel_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')
dtbo_file="$OUTPUT_DIR/${safe_name}.dtbo"
if [ -f "$dtbo_file" ]; then
target_dir="$SCREENFILES_DIR/${panel_name}"
mkdir -p "$target_dir"
cp "$dtbo_file" "$target_dir/mipi-panel.dtbo"
log " ScreenFiles/${panel_name}/mipi-panel.dtbo"
fi
done
if [ -f "$BASE_DTB_CLONE" ]; then
log "Base DTB (clone): $BASE_DTB_CLONE"
# Map: panel_name -> merged DTB filename
declare -A CLONE_MERGED_NAME=(
["Clone Panel 1"]="kernel-clone1.dtb"
["Clone Panel 2"]="kernel-clone2.dtb"
["Clone Panel 3"]="kernel-clone3.dtb"
["Clone Panel 4"]="kernel-clone4.dtb"
["Clone Panel 5"]="kernel-clone5.dtb"
["Clone Panel 6"]="kernel-clone6.dtb"
["Clone Panel 7"]="kernel-clone7.dtb"
["Clone Panel 8"]="kernel-clone8.dtb"
["Clone Panel 9"]="kernel-clone9.dtb"
["Clone Panel 10"]="kernel-clone10.dtb"
["R36 Max"]="kernel-r36max.dtb"
["RX6S"]="kernel-rx6s.dtb"
)
for panel_name in "${CLONE_ORDER[@]}"; do
safe_name=$(echo "$panel_name" | tr ' ' '_' | tr '[:upper:]' '[:lower:]')
dtbo_file="$OUTPUT_DIR/${safe_name}.dtbo"
merged_name="${CLONE_MERGED_NAME[$panel_name]}"
merged_file="$MERGED_DIR/$merged_name"
if [ -f "$dtbo_file" ]; then
if fdtoverlay -i "$BASE_DTB_CLONE" -o "$merged_file" "$dtbo_file" 2>/dev/null; then
log " $merged_name ($(stat -c%s "$merged_file") bytes)"
MERGED_COUNT=$((MERGED_COUNT + 1))
else
warn " Failed to merge ${safe_name}.dtbo"
fi
fi
done
else
warn "Base DTB (clone) not found: $BASE_DTB_CLONE"
fi
#------------------------------------------------------------------------------
# Summary
#------------------------------------------------------------------------------
log ""
log "=== Panel DTBO Generation Complete ==="
log "Generated: ${GENERATED} panels"
[ $FAILED -gt 0 ] && warn "Failed: ${FAILED} panels"
log "=== Panel Generation Complete ==="
log "DTBOs generated: ${GENERATED}"
log "Pre-merged DTBs: ${MERGED_COUNT}"
[ $FAILED -gt 0 ] && warn "Failed: ${FAILED}"
log ""
log "Structure:"
log " output/panels/ScreenFiles/"
log " ├── Panel 0/mipi-panel.dtbo (R36S original)"
log " ├── Panel 1/mipi-panel.dtbo (R36S original)"
log " ├── Panel 2/mipi-panel.dtbo (R36S original)"
log " ├── Panel 3/mipi-panel.dtbo (R36S original)"
log " ├── Panel 4/mipi-panel.dtbo (R36S Panel4-V22, default)"
log " ├── Panel 5/mipi-panel.dtbo (R36S original)"
log " ├── Clone Panel 1/mipi-panel.dtbo (ST7703)"
log " ├── Clone Panel 2/mipi-panel.dtbo (ST7703)"
log " ├── Clone Panel 3/mipi-panel.dtbo (NV3051D)"
log " ├── Clone Panel 4/mipi-panel.dtbo (NV3051D)"
log " ├── Clone Panel 5/mipi-panel.dtbo (ST7703)"
log " ├── Clone Panel 6/mipi-panel.dtbo (NV3051D)"
log " ├── Clone Panel 7/mipi-panel.dtbo (JD9365DA)"
log " ├── Clone Panel 8/mipi-panel.dtbo (ST7703)"
log " ├── Clone Panel 9/mipi-panel.dtbo (NV3051D)"
log " ├── Clone Panel 10/mipi-panel.dtbo (ST7703 variant)"
log " ├── R36 Max/mipi-panel.dtbo (ST7703 720x720)"
log " └── RX6S/mipi-panel.dtbo (NV3051D)"
log "Output: $MERGED_DIR/"
ls -1 "$MERGED_DIR"/*.dtb 2>/dev/null | while read f; do
log " $(basename "$f")"
done
log ""
log "Copy to boot partition: cp -r output/panels/ScreenFiles /boot/"
log "These pre-merged DTBs go on BOOT partition. boot.ini loads the selected one."

View File

@@ -18,7 +18,7 @@ Flow:
6. After 2 full cycles without confirm: auto-confirm default
Panel selection is persistent:
- panel.txt: U-Boot reads PanelDTBO variable (overlay path)
- panel.txt: U-Boot reads PanelDTB variable (pre-merged DTB name)
- panel-confirmed: marker file (>1 byte = confirmed, ≤1 byte = reset)
- Hold X during boot to reset (U-Boot overwrites panel-confirmed with 1 byte)
"""
@@ -62,52 +62,35 @@ WAIT_PER_PANEL = 15 # seconds to wait for input per panel
MAX_CYCLES = 2 # auto-confirm default after this many full cycles
# --- Panel definitions per variant ---
# (panel_num, dtbo_path, friendly_name)
# Empty dtbo_path = default panel (hardcoded in base DTB, no overlay needed)
# (panel_num, dtb_name, friendly_name)
# Empty dtb_name = default panel (hardcoded in base DTB kernel.dtb, no overlay)
# Non-empty dtb_name = pre-merged DTB with overlay applied at build-time
# Order: most common first
# R36S Original — 6 panels, default is Panel 4-V22 (~60% of units)
PANELS_ORIGINAL = [
("4", "",
"Panel 4-V22 (Default)"),
("3", "ScreenFiles/Panel 3/mipi-panel.dtbo",
"Panel 3-V20"),
("5", "ScreenFiles/Panel 5/mipi-panel.dtbo",
"Panel 5-V22 Q8"),
("0", "ScreenFiles/Panel 0/mipi-panel.dtbo",
"Panel 0"),
("1", "ScreenFiles/Panel 1/mipi-panel.dtbo",
"Panel 1-V10"),
("2", "ScreenFiles/Panel 2/mipi-panel.dtbo",
"Panel 2-V12"),
("4", "", "Panel 4-V22 (Default)"),
("3", "kernel-panel3.dtb", "Panel 3-V20"),
("5", "kernel-panel5.dtb", "Panel 5-V22 Q8"),
("0", "kernel-panel0.dtb", "Panel 0"),
("1", "kernel-panel1.dtb", "Panel 1-V10"),
("2", "kernel-panel2.dtb", "Panel 2-V12"),
]
# R36S Clone — 12 panels, default is Clone 8 ST7703 (G80CA-MB)
PANELS_CLONE = [
("C8", "",
"Clone 8 ST7703 G80CA (Default)"),
("C1", "ScreenFiles/Clone Panel 1/mipi-panel.dtbo",
"Clone 1 (ST7703)"),
("C3", "ScreenFiles/Clone Panel 3/mipi-panel.dtbo",
"Clone 3 (NV3051D)"),
("C7", "ScreenFiles/Clone Panel 7/mipi-panel.dtbo",
"Clone 7 (JD9365DA)"),
("C9", "ScreenFiles/Clone Panel 9/mipi-panel.dtbo",
"Clone 9 (NV3051D)"),
("C10", "ScreenFiles/Clone Panel 10/mipi-panel.dtbo",
"Clone 10 (ST7703)"),
("C2", "ScreenFiles/Clone Panel 2/mipi-panel.dtbo",
"Clone 2 (ST7703)"),
("C4", "ScreenFiles/Clone Panel 4/mipi-panel.dtbo",
"Clone 4 (NV3051D)"),
("C5", "ScreenFiles/Clone Panel 5/mipi-panel.dtbo",
"Clone 5 (ST7703)"),
("C6", "ScreenFiles/Clone Panel 6/mipi-panel.dtbo",
"Clone 6 (NV3051D)"),
("MAX", "ScreenFiles/R36 Max/mipi-panel.dtbo",
"R36 Max (720x720)"),
("RX6S", "ScreenFiles/RX6S/mipi-panel.dtbo",
"RX6S (NV3051D)"),
("C8", "", "Clone 8 ST7703 G80CA (Default)"),
("C1", "kernel-clone1.dtb", "Clone 1 (ST7703)"),
("C3", "kernel-clone3.dtb", "Clone 3 (NV3051D)"),
("C7", "kernel-clone7.dtb", "Clone 7 (JD9365DA)"),
("C9", "kernel-clone9.dtb", "Clone 9 (NV3051D)"),
("C10", "kernel-clone10.dtb", "Clone 10 (ST7703)"),
("C2", "kernel-clone2.dtb", "Clone 2 (ST7703)"),
("C4", "kernel-clone4.dtb", "Clone 4 (NV3051D)"),
("C5", "kernel-clone5.dtb", "Clone 5 (ST7703)"),
("C6", "kernel-clone6.dtb", "Clone 6 (NV3051D)"),
("MAX", "kernel-r36max.dtb", "R36 Max (720x720)"),
("RX6S", "kernel-rx6s.dtb", "RX6S (NV3051D)"),
]
@@ -286,13 +269,13 @@ def fsync_write(path, data):
os.close(dir_fd)
def write_panel_config(panel_num, dtbo_path):
def write_panel_config(panel_num, dtb_name):
"""Write panel.txt for U-Boot to load on next boot."""
content = f"PanelNum={panel_num}\n"
if dtbo_path:
content += f"PanelDTBO={dtbo_path}\n"
if dtb_name:
content += f"PanelDTB={dtb_name}\n"
else:
content += "PanelDTBO=\n"
content += "PanelDTB=\n"
fsync_write(PANEL_TXT, content)
@@ -395,9 +378,9 @@ def main():
# Panel selection loop
for cycle in range(MAX_CYCLES):
for idx, (panel_num, dtbo_path, name) in enumerate(panels):
for idx, (panel_num, dtb_name, name) in enumerate(panels):
# Write panel config (ready for confirm)
write_panel_config(panel_num, dtbo_path)
write_panel_config(panel_num, dtb_name)
# Visual feedback on tty1
position = f"[{idx + 1}/{len(panels)}]"
@@ -419,15 +402,15 @@ def main():
play_confirm_sound(confirm_beep)
confirm_panel()
subprocess.run(["sync"])
if dtbo_path:
# Non-default panel: overlay applied by U-Boot on next reset
if dtb_name:
# Non-default panel: pre-merged DTB loaded on next boot
write_tty(f"Confirmed: {name}\n\n Press RESET to apply.")
print(f" Non-default panel — waiting for RESET")
# Hold here until user presses RESET (no timeout)
while True:
time.sleep(60)
else:
# Default panel: no overlay needed, continue booting
# Default panel: kernel.dtb used, continue booting
write_tty(f"Confirmed: {name}")
print(f" Default panel — continuing boot")
sys.exit(0)