7 Commits

Author SHA1 Message Date
Douglas Teles 70342ca457 ROADMAP: detail beta1.2 system changes and update last-updated
Input merger, RetroArch tuning, panel ordering, no-panel variant,
mirror list, boot.ini autodetect.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:24:00 -03:00
Douglas Teles b6eff2e748 ROADMAP: beta1.1 pre-merged DTBs + beta1.2 Flasher diary entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 02:20:21 -03:00
Douglas Teles 9c30a92cab Add variant-sync service, update mirrors, native DTB names
- build-rootfs.sh: archr-variant-sync.service copies /boot/variant
  to /etc/archr/variant on first boot (for Flasher no-panel images).
  Runs once via ConditionPathExists, Before=panel-detect.service
- build-rootfs.sh: update ALARM mirror list (remove eu.mirror/403,
  add de4/gr/tw/tw2/ca.us, reorder Americas first for Brazil)
- build-image.sh: no-panel variant uses native DTB names
  (kernel-panel4.dtb, kernel-clone8.dtb) instead of inherited
  kernel-original.dtb / kernel-clone.dtb

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:11:28 -03:00
Douglas Teles cbc39b8802 Add no-panel universal image variant for Flasher app
Third build variant (--variant no-panel) that includes all 18 panel
DTBs for both original and clone hardware. Designed for the future
Arch R Flasher app to inject the correct kernel.dtb at flash time.

- boot.ini: auto-detect root device from mmcdev (no more __ROOTDEV__)
- build-image.sh: no-panel uses mainline U-Boot, copies all DTBs
- panel-detect.py: wizard disabled for no-panel variant
- build-all.sh: generates third image (ArchR-R36S-no-panel)
2026-03-01 22:11:37 -03:00
Douglas Teles 87719600d2 Update README: split panel list by variant, fix panel.txt format
Separate panel tables into Original (6 panels) and Clone (12 panels)
with numerical ordering and beep counts. Update manual selection to
use PanelDTB= format (pre-merged DTBs). Note: ordering available
from beta1.2.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:54:37 -03:00
Douglas Teles baaeb69a19 Input merger, RetroArch tuning, panel ordering fix
- input-merge daemon: combines gpio-keys + adc-joystick into single
  virtual "Arch R Gamepad" via uinput so RetroArch sees one device
  (fixes analog sticks ignored due to max_users=1)
- retroarch-launch.sh: start/stop input-merge, VT blank on launch
- retroarch.cfg: ROCKNIX-based tuning (48kHz audio, triple buffer,
  late input polling, autosave, core options path)
- retroarch-core-options.cfg: per-core tuning from ROCKNIX RK3326
  (mupen64plus, pcsx_rearmed, flycast, mame2003-plus, melonds, etc.)
- archr-gamepad.cfg: autoconfig for merged virtual device
- panel-detect.py: panels in numerical order (beep count = position),
  default_panel searches by empty dtb_name instead of panels[0]
- build-rootfs.sh: compile input-merge, install core options and
  autoconfig, add input-merge and kill to sudoers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 21:48:49 -03:00
Douglas Teles a1efd4fc01 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
2026-02-28 00:14:54 -03:00
14 changed files with 892 additions and 213 deletions
+3
View File
@@ -88,3 +88,6 @@ bootloader/u-boot-r36s-working/extracted_dtb_*.dtb
# Clone U-Boot build output
bootloader/u-boot-clone-build/
# Others
feedback/
+51 -32
View File
@@ -153,7 +153,11 @@ Arch-R/
## Supported Panels
Arch R supports 18 display panels via DTBO overlays. The default image boots with **Panel 4-V22** (the most common R36S panel).
Arch R supports 18 display panels via pre-merged DTBs. Each panel variant has its own DTB file with the correct init sequence and timings applied at build-time.
There are two separate images: **Original** (for genuine R36S) and **Clone** (for R36S clones like G80CA, K36, etc.). Each image has its own panel list.
> **Note:** The numerical panel ordering below is available starting from **beta1.2**. Earlier versions may have a different order.
### Panel Auto-Detection
@@ -161,66 +165,81 @@ On first boot, Arch R runs a **panel detection wizard** that guides you through
1. **If your screen works** (you see text on screen): press **A** to confirm the current panel
2. **If your screen is black** (wrong panel): listen for audio beeps and use buttons to navigate:
- Each panel plays a different number of beeps (1 beep = first panel, 2 beeps = second, etc.)
- Each panel plays a different number of beeps (Panel 0 = 1 beep, Panel 1 = 2 beeps, etc.)
- Press **B** to skip to the next panel
- Press **A** to confirm (you'll hear 3 rapid beeps)
- The system reboots with the correct panel applied
Your selection is saved permanently. To reset it later, **hold X during boot**.
### Panel List
### R36S Original (6 panels)
| # | Panel | Notes |
|---|-------|-------|
| 1 | **Panel 4-V22** *(default)* | Most common (~60% of R36S units) |
| 2 | Panel 3-V20 | V20 board marking |
| 3 | Panel 5-V22 Q8 | V22 Q8 variant |
| 4 | Clone Panel 8 | ST7703 (G80CA-MB) |
| 5 | Panel 0 | Early R36S units |
| 6 | Panel 1-V10 | V10 board marking |
| 7 | Panel 2-V12 | V12 board marking |
| 8 | Clone Panel 1 | ST7703 |
| 9 | Clone Panel 3 | NV3051D |
| 10 | Clone Panel 7 | JD9365DA |
| 11 | Clone Panel 9 | NV3051D |
| 12 | Clone Panel 10 | ST7703 variant |
| 13 | Clone Panel 2 | ST7703 |
| 14 | Clone Panel 4 | NV3051D |
| 15 | Clone Panel 5 | ST7703 |
| 16 | Clone Panel 6 | NV3051D |
| 17 | R36 Max | 720x720 ST7703 |
| 18 | RX6S | NV3051D variant |
Default: **Panel 4-V22** (~60% of units)
| Beeps | Panel | DTB | Notes |
|-------|-------|-----|-------|
| 1 | Panel 0 | kernel-panel0.dtb | Early R36S units |
| 2 | Panel 1-V10 | kernel-panel1.dtb | V10 board marking |
| 3 | Panel 2-V12 | kernel-panel2.dtb | V12 board marking |
| 4 | Panel 3-V20 | kernel-panel3.dtb | V20 board marking |
| 5 | **Panel 4-V22** *(default)* | kernel.dtb | Most common |
| 6 | Panel 5-V22 Q8 | kernel-panel5.dtb | V22 Q8 variant |
### R36S Clone (12 panels)
Default: **Clone 8 ST7703 G80CA**
| Beeps | Panel | DTB | Notes |
|-------|-------|-----|-------|
| 1 | Clone 1 | kernel-clone1.dtb | ST7703 |
| 2 | Clone 2 | kernel-clone2.dtb | ST7703 |
| 3 | Clone 3 | kernel-clone3.dtb | NV3051D |
| 4 | Clone 4 | kernel-clone4.dtb | NV3051D |
| 5 | Clone 5 | kernel-clone5.dtb | ST7703 |
| 6 | Clone 6 | kernel-clone6.dtb | NV3051D |
| 7 | Clone 7 | kernel-clone7.dtb | JD9365DA |
| 8 | **Clone 8** *(default)* | kernel.dtb | ST7703 (G80CA-MB) |
| 9 | Clone 9 | kernel-clone9.dtb | NV3051D |
| 10 | Clone 10 | kernel-clone10.dtb | ST7703 variant |
| 11 | R36 Max | kernel-r36max.dtb | 720x720 ST7703 |
| 12 | RX6S | kernel-rx6s.dtb | NV3051D variant |
### Manual Panel Selection
If you prefer to set the panel manually (e.g., you know which panel you have from ArkOS/dArkOS):
**Step 1 Mount the BOOT partition on your PC**
**Step 1 -- Mount the BOOT partition on your PC**
```bash
lsblk
sudo mount /dev/sdX1 /mnt
```
**Step 2 Create panel.txt**
**Step 2 -- Create panel.txt**
```bash
# Example: Panel 3-V20
# Example: Panel 3-V20 (original R36S)
echo 'PanelNum=3
PanelDTBO=ScreenFiles/Panel 3/mipi-panel.dtbo' | sudo tee /mnt/panel.txt
PanelDTB=kernel-panel3.dtb' | sudo tee /mnt/panel.txt
# Mark as confirmed (skip wizard on next boot)
echo 'confirmed' | sudo tee /mnt/panel-confirmed
```
For default Panel 4-V22 (no overlay needed):
For default panels (no custom DTB needed):
```bash
# Original R36S: Panel 4-V22 (default)
echo 'PanelNum=4
PanelDTBO=' | sudo tee /mnt/panel.txt
PanelDTB=' | sudo tee /mnt/panel.txt
# Clone R36S: Clone 8 G80CA (default)
echo 'PanelNum=C8
PanelDTB=' | sudo tee /mnt/panel.txt
echo 'confirmed' | sudo tee /mnt/panel-confirmed
```
**Step 3 Unmount and boot**
**Step 3 -- Unmount and boot**
```bash
sudo umount /mnt
@@ -235,8 +254,8 @@ sync
Power On
→ U-Boot (idbloader → trust → uboot.img)
→ logo.bmp displayed on screen
→ boot.ini: load panel.txt → apply DTBO overlay (if non-default panel)
→ Kernel 6.6.89 + rk3326-gameconsole-r36s.dtb
→ boot.ini: load panel.txt → load pre-merged DTB (kernel-panelN.dtb)
→ Kernel 6.6.89 + initramfs splash (0.7s)
→ systemd → panel-detect.service (first boot only)
→ archr-boot-setup (GPU + governors)
→ emulationstation.service → EmulationStation UI
+80 -2
View File
@@ -1915,6 +1915,84 @@ The panel wizard can only write config for the NEXT boot — no hot-swapping pan
---
### 2026-02-27 — Pre-merged Panel DTBs (beta1.1)
The biggest pain point since beta1 was panel selection. BSP U-Boot's `fdt apply` was silently
corrupting DTBs whenever an overlay tried to replace a property with different-sized data — the
init-sequence byte arrays vary per panel, so any non-default panel produced a broken DTB and a
black screen.
The fix was surprisingly clean: run `fdtoverlay` at **build time** instead of at boot time.
`generate-panel-dtbos.sh` now pre-merges each panel overlay with the kernel DTB, producing
`kernel-panel0.dtb` through `kernel-panel5.dtb` (original) and 12 clone variants. The boot
script reads `PanelDTB=kernel-panel3.dtb` from `panel.txt` and loads the complete DTB directly —
no `fdt apply` at all. Published as v1.0-beta1.1 and pushed to the community for testing.
---
### 2026-03-01 — Arch R Flasher & beta1.2
Two things happened in parallel this week: the Flasher app was born, and the distro itself
got some housekeeping.
**Arch R Flasher — feature-complete.**
Built the desktop flashing tool (Tauri 2 — Rust backend, vanilla HTML/CSS/JS frontend) that
was "Plano C" in case pre-merged DTBs didn't fully work. Turns out having a proper Flasher is
valuable regardless — it eliminates the dd-to-SD-card ceremony and lets users select their
console type (Original vs Clone) and panel before flashing. The Flasher injects the correct
kernel DTB, U-Boot, and panel config into the image at write time. Zero runtime detection needed.
Feature set:
- Console selection (Original: 6 panels, Clone: 12 panels)
- Panel picker with defaults marked
- In-app image download from GitHub Releases (with progress bar + SHA256 verification)
- Local file picker (.img / .xz) with XZ decompression
- SD card detection (Linux: sysfs, macOS: diskutil, Windows: PowerShell)
- System disk protection (never lists /, /home, /boot disks)
- Privileged flash (pkexec on Linux, osascript on macOS, UAC on Windows)
- Real-time flash progress via dd monitoring
- Post-flash eject (Linux + macOS)
- Auto-update via tauri-plugin-updater + GitHub Releases (with minisign signing)
- i18n: English, Portuguese, Spanish, Chinese
- CI/CD: GitHub Actions builds for Linux (.deb/.rpm/.AppImage), macOS (.dmg), Windows (.msi/.exe)
The Flasher lives at `archr-linux/archr-flasher` on GitHub. Still ironing out the CI signing
(first build caught a Tauri 2 schema issue with `app.title` and 16-bit icon PNGs), but the
code is feature-complete.
**beta1.2 system changes — a lot more than "just polish":**
The biggest under-the-hood change was the **input merger daemon**. RetroArch sees multiple input
devices (gpio-keys for buttons, adc-joystick for analog sticks) as separate controllers. With
`max_users=1`, it would bind to gpio-keys and ignore the joystick entirely. The fix: a small
C daemon (`input-merge`) that reads both evdev devices and outputs a single virtual "Arch R
Gamepad" via uinput. Now RetroArch sees one unified controller with buttons + analog sticks.
`retroarch-launch.sh` starts the merger before RA and kills it after.
RetroArch itself got a proper config tuning pass, borrowed heavily from ROCKNIX's RK3326
profiles. Audio at 48kHz (native DAC rate), triple buffer, late input polling for reduced
latency, per-core options for mupen64plus, pcsx_rearmed, flycast, mame2003-plus, melonds,
and others. Auto-save enabled, core options path set.
**Panel wizard rework:** Panels now show in numerical order — the beep count matches the panel
position in the list (Panel 1 = 1 beep, Panel 2 = 2 beeps, etc.). Default panel detection
uses `dtb_name` matching instead of always falling back to `panels[0]`.
**Third image variant: no-panel.** For the Flasher app — includes all 18 pre-merged panel DTBs
for both original and clone hardware. The Flasher picks the right DTB at write time. Also added
`variant-sync` systemd service that copies `/boot/variant` to `/etc/archr/variant` on first
boot (necessary because the Flasher writes the variant marker to the FAT32 BOOT partition,
not directly into ext4).
**Mirror list updated:** The old EU mirror was returning 403 errors. Reshuffled to Americas-first
ordering (better for Brazil) and added new mirrors (de4, gr, tw, tw2, ca.us).
**boot.ini fix:** Root device now auto-detected from `mmcdev` variable instead of the hardcoded
`__ROOTDEV__` placeholder — one less thing that could break on different boot configurations.
---
## What's Left for v1.0 Stable
### Critical — Must Work Before Release
@@ -1950,7 +2028,7 @@ The panel wizard can only write config for the NEXT boot — no hot-swapping pan
| 15 | Bluetooth pairing | Not tested | bluez installed |
| 16 | Battery LED indicator | Installed | Python service, needs hardware test |
| 17 | Sleep/wake | Not implemented | PMIC sleep pinctrl in DTS |
| 18 | OTA updates | Not implemented | Future feature |
| 18 | OTA updates | Not implemented | Future feature (Flasher has self-update) |
| 19 | Theme customization | Default only | ES-fcamod default theme |
| 20 | Headphone detection | Not tested | archr-hotkeys.py ALSA switch |
@@ -2024,4 +2102,4 @@ The panel wizard can only write config for the NEXT boot — no hot-swapping pan
---
*Last updated: 2026-02-25 (build pipeline fixes, boot.scr mystery solved, splash positioning)*
*Last updated: 2026-03-01 (beta1.2 release — Flasher app, input merger, RetroArch tuning, no-panel variant)*
+6
View File
@@ -213,6 +213,12 @@ if [ "$BUILD_IMAGE" = true ]; then
log " BUILDING IMAGE (clone)"
log "═══════════════════════════════════════════════════════════════"
"$SCRIPT_DIR/build-image.sh" --variant clone
log ""
log "═══════════════════════════════════════════════════════════════"
log " BUILDING IMAGE (no-panel)"
log "═══════════════════════════════════════════════════════════════"
"$SCRIPT_DIR/build-image.sh" --variant no-panel
fi
#------------------------------------------------------------------------------
+54 -38
View File
@@ -3,11 +3,12 @@
#==============================================================================
# Arch R - SD Card Image Builder
#==============================================================================
# Creates a flashable SD card image for R36S (original or clone)
# Creates a flashable SD card image for R36S
#
# Usage:
# sudo ./build-image.sh --variant original # R36S original
# sudo ./build-image.sh --variant clone # R36S clone (G80CA-MB etc)
# sudo ./build-image.sh --variant no-panel # Universal (all panels, for Flasher)
# sudo ./build-image.sh # defaults to original
#==============================================================================
@@ -37,13 +38,13 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
*)
error "Unknown option: $1\nUsage: $0 --variant original|clone"
error "Unknown option: $1\nUsage: $0 --variant original|clone|no-panel"
;;
esac
done
if [ "$VARIANT" != "original" ] && [ "$VARIANT" != "clone" ]; then
error "Invalid variant: $VARIANT (must be 'original' or 'clone')"
if [ "$VARIANT" != "original" ] && [ "$VARIANT" != "clone" ] && [ "$VARIANT" != "no-panel" ]; then
error "Invalid variant: $VARIANT (must be 'original', 'clone', or 'no-panel')"
fi
#------------------------------------------------------------------------------
@@ -52,11 +53,12 @@ fi
if [ "$VARIANT" = "original" ]; then
IMAGE_SUFFIX="R36S"
KERNEL_DTB_NAME="rk3326-gameconsole-r36s.dtb"
ROOT_DEV="/dev/mmcblk1p2"
else
elif [ "$VARIANT" = "clone" ]; then
IMAGE_SUFFIX="R36S-clone"
KERNEL_DTB_NAME="rk3326-gameconsole-r36s-clone-type5.dtb"
ROOT_DEV="/dev/mmcblk0p2"
elif [ "$VARIANT" = "no-panel" ]; then
IMAGE_SUFFIX="R36S-no-panel"
KERNEL_DTB_NAME="rk3326-gameconsole-r36s.dtb" # placeholder (Flasher replaces)
fi
# U-Boot binaries per variant:
@@ -66,7 +68,12 @@ UBOOT_TYPE=""
UBOOT_BSP_DIR="$SCRIPT_DIR/bootloader/u-boot-rk3326/sd_fuse"
UBOOT_MAINLINE_DIR="$SCRIPT_DIR/bootloader/u-boot-clone-build"
if [ "$VARIANT" = "clone" ] && [ -f "$UBOOT_MAINLINE_DIR/uboot.img" ]; then
if [ "$VARIANT" = "no-panel" ] && [ -f "$UBOOT_MAINLINE_DIR/uboot.img" ]; then
# no-panel: mainline U-Boot works on both original and clone
UBOOT_BIN_DIR="$UBOOT_MAINLINE_DIR"
UBOOT_TYPE="mainline"
log " U-Boot: mainline v2025.10 (no-panel universal)"
elif [ "$VARIANT" = "clone" ] && [ -f "$UBOOT_MAINLINE_DIR/uboot.img" ]; then
UBOOT_BIN_DIR="$UBOOT_MAINLINE_DIR"
UBOOT_TYPE="mainline"
log " U-Boot: mainline v2025.10 (clone)"
@@ -266,40 +273,49 @@ 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
if [ "$VARIANT" = "no-panel" ]; then
# Universal: include ALL panel DTBs (original + clone)
# Every panel has its own native DTB — no base/inherited DTBs.
for dtb in "$MERGED_DIR"/kernel-panel*.dtb "$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
# Default panels: copy base DTBs with their panel number names
# Panel 4-V22 (original default) → kernel-panel4.dtb
[ -f "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s.dtb" ] && \
cp "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s.dtb" "$MOUNT_BOOT/kernel-panel4.dtb" && \
panel_count=$((panel_count + 1))
# Clone 8 G80CA (clone default) → kernel-clone8.dtb
[ -f "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s-clone-type5.dtb" ] && \
cp "$ROOTFS_DIR/boot/rk3326-gameconsole-r36s-clone-type5.dtb" "$MOUNT_BOOT/kernel-clone8.dtb" && \
panel_count=$((panel_count + 1))
elif [ "$VARIANT" = "original" ]; then
# 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 ---
@@ -345,10 +361,11 @@ else
fi
fi
# --- Boot partition: boot script (with root device substituted) ---
# --- Boot partition: boot script ---
# Root device is auto-detected in boot.ini based on mmcdev (no substitution needed).
if [ -f "$SCRIPT_DIR/config/boot.ini" ]; then
sed "s|__ROOTDEV__|$ROOT_DEV|" "$SCRIPT_DIR/config/boot.ini" > "$MOUNT_BOOT/boot.ini"
log " boot.ini installed (root=$ROOT_DEV)"
cp "$SCRIPT_DIR/config/boot.ini" "$MOUNT_BOOT/boot.ini"
log " boot.ini installed (root auto-detect)"
# Mainline U-Boot: also create boot.scr (compiled boot script)
# Mainline uses distro boot flow → scans for boot.scr, not boot.ini
@@ -490,7 +507,6 @@ IMAGE_SIZE_ACTUAL=$(du -h "$IMAGE_FILE" | cut -f1)
log "Image: $IMAGE_FILE"
log "Size: $IMAGE_SIZE_ACTUAL"
log "Variant: $VARIANT"
log "Root: $ROOT_DEV"
log ""
log "To flash to SD card:"
log " sudo dd if=$IMAGE_FILE of=/dev/sdX bs=4M status=progress"
+48 -9
View File
@@ -123,15 +123,19 @@ log " ✓ Pacman CheckSpace disabled (QEMU chroot compatibility)"
# Add multiple fallback mirrors (default mirror often has stale/404 packages)
cat > "$ROOTFS_DIR/etc/pacman.d/mirrorlist" << 'MIRRORS_EOF'
# Arch Linux ARM mirrors - multiple fallbacks for reliability
Server = http://de.mirror.archlinuxarm.org/$arch/$repo
Server = http://eu.mirror.archlinuxarm.org/$arch/$repo
Server = http://dk.mirror.archlinuxarm.org/$arch/$repo
Server = http://de3.mirror.archlinuxarm.org/$arch/$repo
Server = http://hu.mirror.archlinuxarm.org/$arch/$repo
Server = http://mirror.archlinuxarm.org/$arch/$repo
# Arch Linux ARM mirrors - all official mirrors, Americas first
Server = http://fl.us.mirror.archlinuxarm.org/$arch/$repo
Server = http://nj.us.mirror.archlinuxarm.org/$arch/$repo
Server = http://ca.us.mirror.archlinuxarm.org/$arch/$repo
Server = http://de.mirror.archlinuxarm.org/$arch/$repo
Server = http://de4.mirror.archlinuxarm.org/$arch/$repo
Server = http://dk.mirror.archlinuxarm.org/$arch/$repo
Server = http://hu.mirror.archlinuxarm.org/$arch/$repo
Server = http://gr.mirror.archlinuxarm.org/$arch/$repo
Server = http://de3.mirror.archlinuxarm.org/$arch/$repo
Server = http://tw.mirror.archlinuxarm.org/$arch/$repo
Server = http://tw2.mirror.archlinuxarm.org/$arch/$repo
Server = http://mirror.archlinuxarm.org/$arch/$repo
MIRRORS_EOF
log " ✓ Multiple ALARM mirrors configured (fallback for 404s)"
@@ -368,6 +372,27 @@ FB_EOF
systemctl enable firstboot
# Variant sync: copies variant marker from BOOT → /etc/archr/variant
# Only runs if /etc/archr/variant doesn't exist yet (no-panel images flashed by Flasher)
cat > /etc/systemd/system/archr-variant-sync.service << 'VARSYNC_EOF'
[Unit]
Description=Sync variant from BOOT partition
After=local-fs.target
Before=panel-detect.service
RequiresMountsFor=/boot
ConditionPathExists=!/etc/archr/variant
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'test -f /boot/variant && mkdir -p /etc/archr && cp /boot/variant /etc/archr/variant'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
VARSYNC_EOF
systemctl enable archr-variant-sync
# Panel auto-detect wizard (first boot / X-button reset)
cat > /etc/systemd/system/panel-detect.service << 'PANELDET_EOF'
[Unit]
@@ -521,7 +546,7 @@ dd if=/dev/urandom of=/var/lib/systemd/random-seed bs=512 count=1 2>/dev/null
chmod 600 /var/lib/systemd/random-seed
# Sudoers for perfmax/perfnorm/pmic-poweroff (allow archr to run without password)
echo "archr ALL=(ALL) NOPASSWD: /usr/local/bin/perfmax, /usr/local/bin/perfnorm, /usr/local/bin/pmic-poweroff, /usr/bin/ln, /usr/bin/dmesg, /usr/bin/chvt, /usr/bin/cp, /usr/bin/chmod, /bin/bash" > /etc/sudoers.d/archr-perf
echo "archr ALL=(ALL) NOPASSWD: /usr/local/bin/perfmax, /usr/local/bin/perfnorm, /usr/local/bin/pmic-poweroff, /usr/local/bin/input-merge, /usr/bin/kill, /usr/bin/ln, /usr/bin/dmesg, /usr/bin/chvt, /usr/bin/cp, /usr/bin/chmod, /bin/bash" > /etc/sudoers.d/archr-perf
chmod 440 /etc/sudoers.d/archr-perf
# Allow archr to use negative nice values (needed for nice -n -19 in ES launch commands)
@@ -952,8 +977,22 @@ install -m 755 "$SCRIPT_DIR/scripts/perfmax" "$ROOTFS_DIR/usr/local/bin/perfmax"
install -m 755 "$SCRIPT_DIR/scripts/perfnorm" "$ROOTFS_DIR/usr/local/bin/perfnorm"
install -m 755 "$SCRIPT_DIR/scripts/pmic-poweroff" "$ROOTFS_DIR/usr/local/bin/pmic-poweroff"
install -m 755 "$SCRIPT_DIR/scripts/retroarch-launch.sh" "$ROOTFS_DIR/usr/local/bin/retroarch-launch"
# RetroArch joypad autoconfig (gpio-keys buttons + adc-joystick analog sticks)
# RetroArch core options (per-core tuning for RK3326, from ROCKNIX)
install -m 644 "$SCRIPT_DIR/config/retroarch-core-options.cfg" "$ROOTFS_DIR/home/archr/.config/retroarch/retroarch-core-options.cfg"
log " ✓ RetroArch core options installed"
# Input merger daemon: combines gpio-keys + adc-joystick into single virtual device
# RetroArch needs all inputs on one device (udev driver assigns each to separate port)
if command -v aarch64-linux-gnu-gcc &>/dev/null; then
aarch64-linux-gnu-gcc -static -O2 -o "$ROOTFS_DIR/usr/local/bin/input-merge" \
"$SCRIPT_DIR/scripts/input-merge.c"
chmod 755 "$ROOTFS_DIR/usr/local/bin/input-merge"
log " ✓ input-merge compiled and installed"
else
warn "aarch64-linux-gnu-gcc not found — input-merge not compiled"
fi
# RetroArch joypad autoconfig (merged device + individual fallbacks)
mkdir -p "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev"
install -m 644 "$SCRIPT_DIR/config/autoconfig/udev/archr-gamepad.cfg" "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev/"
install -m 644 "$SCRIPT_DIR/config/autoconfig/udev/gpio-keys.cfg" "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev/"
install -m 644 "$SCRIPT_DIR/config/autoconfig/udev/adc-joystick.cfg" "$ROOTFS_DIR/usr/share/retroarch/autoconfig/udev/"
# systemd shutdown hook — runs pmic-poweroff at the very end of shutdown sequence
+73
View File
@@ -0,0 +1,73 @@
# Arch R Gamepad (merged gpio-keys + adc-joystick)
# Created by input-merge daemon — combines buttons and analog sticks
# into a single virtual device for RetroArch.
input_driver = "udev"
input_device = "Arch R Gamepad"
input_device_display_name = "Arch R Gamepad"
# Face buttons
input_b_btn = "0"
input_a_btn = "1"
input_y_btn = "2"
input_x_btn = "3"
# Shoulders
input_l_btn = "4"
input_r_btn = "5"
input_l2_btn = "6"
input_r2_btn = "7"
# Select / Start / Hotkey
input_select_btn = "8"
input_start_btn = "9"
input_enable_hotkey_btn = "10"
# Thumbstick clicks
input_l3_btn = "11"
input_r3_btn = "12"
# D-Pad
input_up_btn = "13"
input_down_btn = "14"
input_left_btn = "15"
input_right_btn = "16"
# Left analog stick
input_l_x_plus_axis = "+0"
input_l_x_minus_axis = "-0"
input_l_y_plus_axis = "+1"
input_l_y_minus_axis = "-1"
# Right analog stick
input_r_x_plus_axis = "+2"
input_r_x_minus_axis = "-2"
input_r_y_plus_axis = "+3"
input_r_y_minus_axis = "-3"
# Labels
input_b_btn_label = "B"
input_a_btn_label = "A"
input_y_btn_label = "Y"
input_x_btn_label = "X"
input_l_btn_label = "L1"
input_r_btn_label = "R1"
input_l2_btn_label = "L2"
input_r2_btn_label = "R2"
input_select_btn_label = "Select"
input_start_btn_label = "Start"
input_enable_hotkey_btn_label = "Mode"
input_l3_btn_label = "L3"
input_r3_btn_label = "R3"
input_up_btn_label = "D-Pad Up"
input_down_btn_label = "D-Pad Down"
input_left_btn_label = "D-Pad Left"
input_right_btn_label = "D-Pad Right"
input_l_x_plus_axis_label = "LS Right"
input_l_x_minus_axis_label = "LS Left"
input_l_y_plus_axis_label = "LS Down"
input_l_y_minus_axis_label = "LS Up"
input_r_x_plus_axis_label = "RS Right"
input_r_x_minus_axis_label = "RS Left"
input_r_y_plus_axis_label = "RS Down"
input_r_y_minus_axis_label = "RS Up"
+29 -24
View File
@@ -3,11 +3,10 @@ odroidgoa-uboot-config
########################################################################
# Arch R Boot Configuration
#
# Auto-detects SD card MMC device:
# Original R36S: eMMC=mmc0, SD=mmc1
# Clone (no eMMC): SD=mmc0
# Auto-detects SD card MMC device AND root partition:
# Original R36S: eMMC=mmc0, SD=mmc1 → root=/dev/mmcblk1p2
# Clone (no eMMC): SD=mmc0 → root=/dev/mmcblk0p2
#
# Root device (__ROOTDEV__) substituted by build-image.sh per variant.
# Panel config from panel.txt (managed by panel-detect.py).
# Panel reset handled in Linux (panel-detect.py) -- no GPIO check here
# because X button GPIO differs between boards (modular!).
@@ -16,7 +15,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,29 +30,35 @@ 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
# --- Auto-detect root device (SD card partition 2) ---
# mmcdev already detected above: 1 = original (eMMC+SD), 0 = clone (SD only)
if test "${mmcdev}" = "1"; then
setenv rootdev "/dev/mmcblk1p2"
else
setenv rootdev "/dev/mmcblk0p2"
fi
# --- Load DTB and boot (Image already loaded above) ---
if load mmc ${mmcdev}:1 ${dtb_loadaddr} kernel.dtb; then
if load mmc ${mmcdev}:1 ${dtb_loadaddr} ${dtb_name}; 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
setenv bootargs "root=__ROOTDEV__ rootwait rw console=ttyFIQ0 loglevel=0 quiet vt.global_cursor_default=0 consoleblank=0 printk.devkmsg=off fsck.mode=skip"
setenv bootargs "root=${rootdev} rootwait rw console=ttyFIQ0 loglevel=0 quiet vt.global_cursor_default=0 consoleblank=0 printk.devkmsg=off fsck.mode=skip"
# Load initramfs (splash appears immediately after kernel boot, before systemd)
setenv initrd_loadaddr "0x04000000"
+152
View File
@@ -0,0 +1,152 @@
mupen64plus-alt-map = "False"
mupen64plus-aspect = "4:3"
mupen64plus-astick-deadzone = "15"
mupen64plus-astick-sensitivity = "100"
mupen64plus-BackgroundMode = "Stripped"
mupen64plus-BilinearMode = "standard"
mupen64plus-CorrectTexrectCoords = "Auto"
mupen64plus-CountPerOp = "0"
mupen64plus-CountPerOpDenomPot = "0"
mupen64plus-cpucore = "dynamic_recompiler"
mupen64plus-d-cbutton = "C3"
mupen64plus-DitheringPattern = "True"
mupen64plus-DitheringQuantization = "True"
mupen64plus-EnableCopyAuxToRDRAM = "True"
mupen64plus-EnableCopyColorToRDRAM = "Async"
mupen64plus-EnableCopyDepthToRDRAM = "Software"
mupen64plus-EnableEnhancedHighResStorage = "False"
mupen64plus-EnableEnhancedTextureStorage = "False"
mupen64plus-EnableFBEmulation = "True"
mupen64plus-EnableFragmentDepthWrite = "False"
mupen64plus-EnableHiResAltCRC = "False"
mupen64plus-EnableHWLighting = "True"
mupen64plus-EnableInaccurateTextureCoordinates = "False"
mupen64plus-EnableLegacyBlending = "False"
mupen64plus-EnableLODEmulation = "True"
mupen64plus-EnableNativeResFactor = "1"
mupen64plus-EnableNativeResTexrects = "Optimized"
mupen64plus-EnableOverscan = "Enabled"
mupen64plus-EnableTexCoordBounds = "False"
mupen64plus-EnableTextureCache = "True"
mupen64plus-ForceDisableExtraMem = "False"
mupen64plus-FrameDuping = "True"
mupen64plus-Framerate = "Original"
mupen64plus-FXAA = "0"
mupen64plus-GLideN64IniBehaviour = "late"
mupen64plus-HybridFilter = "True"
mupen64plus-IgnoreTLBExceptions = "False"
mupen64plus-l-cbutton = "C2"
mupen64plus-MaxHiResTxVramLimit = "0"
mupen64plus-MaxTxCacheSize = "8000"
mupen64plus-MultiSampling = "0"
mupen64plus-OverscanBottom = "0"
mupen64plus-OverscanLeft = "0"
mupen64plus-OverscanRight = "0"
mupen64plus-OverscanTop = "0"
mupen64plus-pak1 = "memory"
mupen64plus-pak2 = "none"
mupen64plus-pak3 = "none"
mupen64plus-pak4 = "none"
mupen64plus-r-cbutton = "C1"
mupen64plus-rdp-plugin = "gliden64"
mupen64plus-RDRAMImageDitheringMode = "BlueNoise"
mupen64plus-rsp-plugin = "hle"
mupen64plus-ThreadedRenderer = "True"
mupen64plus-txCacheCompression = "True"
mupen64plus-txEnhancementMode = "None"
mupen64plus-txFilterIgnoreBG = "True"
mupen64plus-txFilterMode = "None"
mupen64plus-txHiresEnable = "False"
mupen64plus-txHiresFullAlphaChannel = "False"
mupen64plus-u-cbutton = "C4"
mupen64plus-virefresh = "Auto"
pcsx_rearmed_analog_axis_modifier = "circle"
pcsx_rearmed_async_cd = "sync"
pcsx_rearmed_bios = "auto"
pcsx_rearmed_display_internal_fps = "disabled"
pcsx_rearmed_dithering = "enabled"
pcsx_rearmed_drc = "enabled"
pcsx_rearmed_duping_enable = "enabled"
pcsx_rearmed_frameskip_type = "disabled"
pcsx_rearmed_gteregsunneeded = "disabled"
pcsx_rearmed_gunconadjustratiox = "1"
pcsx_rearmed_gunconadjustratioy = "1"
pcsx_rearmed_gunconadjustx = "0"
pcsx_rearmed_gunconadjusty = "0"
pcsx_rearmed_idiablofix = "disabled"
pcsx_rearmed_input_sensitivity = "1.00"
pcsx_rearmed_inuyasha_fix = "disabled"
pcsx_rearmed_memcard2 = "disabled"
pcsx_rearmed_multitap1 = "auto"
pcsx_rearmed_multitap2 = "auto"
pcsx_rearmed_negcon_deadzone = "0"
pcsx_rearmed_negcon_response = "linear"
pcsx_rearmed_neon_enhancement_enable = "disabled"
pcsx_rearmed_neon_enhancement_no_main = "disabled"
pcsx_rearmed_neon_interlace_enable = "disabled"
pcsx_rearmed_nocdaudio = "enabled"
pcsx_rearmed_nogteflags = "disabled"
pcsx_rearmed_nosmccheck = "disabled"
pcsx_rearmed_noxadecoding = "enabled"
pcsx_rearmed_pad1type = "standard"
pcsx_rearmed_pad2type = "standard"
pcsx_rearmed_pad3type = "none"
pcsx_rearmed_pad4type = "none"
pcsx_rearmed_pad5type = "none"
pcsx_rearmed_pad6type = "none"
pcsx_rearmed_pad7type = "none"
pcsx_rearmed_pad8type = "none"
pcsx_rearmed_pe2_fix = "disabled"
pcsx_rearmed_psxclock = "57"
pcsx_rearmed_region = "auto"
pcsx_rearmed_show_bios_bootlogo = "disabled"
pcsx_rearmed_show_other_input_settings = "disabled"
pcsx_rearmed_spu_interpolation = "simple"
pcsx_rearmed_spu_reverb = "enabled"
pcsx_rearmed_spuirq = "disabled"
pcsx_rearmed_vibration = "enabled"
gambatte_gb_colorization = "disabled"
gambatte_gb_internal_palette = ""
mame2003-plus_analog = "digital"
mame2003-plus_art_resolution = "1"
mame2003-plus_brightness = "1.0"
mame2003-plus_cheat_input_ports = "disabled"
mame2003-plus_core_save_subfolder = "enabled"
mame2003-plus_core_sys_subfolder = "enabled"
mame2003-plus_dcs_speedhack = "enabled"
mame2003-plus_deadzone = "20"
mame2003-plus_digital_joy_centering = "enabled"
mame2003-plus_display_artwork = "enabled"
mame2003-plus_display_setup = "disabled"
mame2003-plus_four_way_emulation = "disabled"
mame2003-plus_frameskip = "0"
mame2003-plus_gamma = "1.0"
mame2003-plus_input_interface = "retropad"
mame2003-plus_machine_timing = "enabled"
mame2003-plus_mame_remapping = "enabled"
mame2003-plus_mouse_device = "mouse"
mame2003-plus_sample_rate = "48000"
mame2003-plus_skip_disclaimer = "disabled"
mame2003-plus_skip_warnings = "disabled"
flycast_internal_resolution = "640x480"
flycast_alpha_sorting = "per-strip (fast, least accurate)"
flycast_enable_dsp = "disabled"
flycast_threaded_rendering = "enabled"
flycast_anisotropic_filtering = "off"
flycast_synchronous_rendering = "enabled"
flycast_enable_rtt = "disabled"
flycast_enable_rttb = "disabled"
flycast_delay_frame_swapping = "disabled"
flycast_div_matching = "auto"
flycast_texupscale = "off"
flycast_auto_skip_frame = "normal"
opera_swi_hle = "enabled"
opera_dsp_threaded = "enabled"
opera_madam_matrix_engine = "software"
puae_deadzone = "25"
melonds_screen_layout = "Left/Right"
melonds_swapscreen_mode = "Toggle"
melonds_threaded_renderer = "enabled"
melonds_touch_mode = "Joystick"
virtualjaguar_bios = "enabled"
virtualjaguar_usefastblitter = "enabled"
+17 -8
View File
@@ -1,5 +1,5 @@
# Arch R - RetroArch Configuration for R36S
# Optimized for 640x480 display
# Optimized for 640x480 display, tuned from ROCKNIX RK3326 defaults
# Video
video_driver = "gl"
@@ -7,22 +7,22 @@ video_context_driver = ""
video_fullscreen = "true"
video_windowed_fullscreen = "false"
video_vsync = "true"
video_max_swapchain_images = "2"
video_max_swapchain_images = "3"
video_smooth = "false"
video_scale_integer = "true"
video_scale_integer = "false"
video_rotation = "0"
video_aspect_ratio_auto = "true"
video_fullscreen_x = "640"
video_fullscreen_y = "480"
# Audio — matches dArkOS RG351MP (RK3326) settings
# Audio — tuned for RK3326 rk817 codec (48kHz native)
# alsathread: non-blocking audio, better for ARM
# audio_device empty: uses ALSA default (plug → hw:0,0 from asound.conf)
# 48kHz avoids double resampling (RA→44.1k→ALSA→48k)
# audio_volume +6dB: software boost to compensate rk817 codec low output level
audio_driver = "alsathread"
audio_device = ""
audio_out_rate = "44100"
audio_latency = "128"
audio_out_rate = "48000"
audio_latency = "64"
audio_sync = "true"
audio_enable = "true"
audio_volume = "6.000000"
@@ -31,6 +31,7 @@ audio_mixer_mute_enable = "false"
audio_rate_control = "true"
audio_rate_control_delta = "0.005000"
audio_resampler = "sinc"
audio_resampler_quality = "2"
audio_max_timing_skew = "0.050000"
# Microphone — DISABLED (binary built with --disable-microphone, belt-and-suspenders)
@@ -41,9 +42,10 @@ input_driver = "udev"
input_joypad_driver = "udev"
input_autodetect_enable = "true"
input_max_users = "1"
input_poll_type_behavior = "2"
joypad_autoconfig_dir = "/usr/share/retroarch/autoconfig"
# Menu (rgui — lightweight, better for 640x480 like dArkOS)
# Menu (rgui — lightweight, better for 640x480)
menu_driver = "rgui"
menu_swap_ok_cancel_buttons = "false"
@@ -55,10 +57,17 @@ libretro_directory = "/usr/lib/libretro"
libretro_info_path = "/usr/share/libretro/info"
assets_directory = "/usr/share/retroarch/assets"
system_directory = "/roms/bios"
core_options_path = "/home/archr/.config/retroarch/retroarch-core-options.cfg"
# Performance
video_threaded = "true"
video_frame_delay = "0"
game_specific_options = "true"
auto_overrides_enable = "true"
auto_remaps_enable = "true"
# Save protection (autosave SRAM every 10s, prevents loss on power-off)
autosave_interval = "10"
# Power Saving
suspend_screensaver_enable = "false"
+82 -47
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."
+232
View File
@@ -0,0 +1,232 @@
/*
* input-merge — Merge gpio-keys + adc-joystick into single virtual gamepad
*
* The R36S has buttons (gpio-keys) and analog sticks (adc-joystick) as
* separate evdev devices. RetroArch's udev driver assigns each to a
* different player port, so analog sticks end up on "player 2" and are
* ignored when max_users=1.
*
* This daemon:
* 1. Finds both devices by name in /dev/input/
* 2. Grabs them (EVIOCGRAB) so only we read their events
* 3. Creates a virtual "Arch R Gamepad" via uinput with all capabilities
* 4. Forwards all events from both sources to the virtual device
*
* Started by retroarch-launch.sh before RetroArch, killed on RA exit.
* While grabbed, EmulationStation and other programs won't see the
* original devices — that's fine because RA is the only thing running.
*
* Build: aarch64-linux-gnu-gcc -static -O2 -o input-merge input-merge.c
*/
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/uinput.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <unistd.h>
#define DEVICE_NAME "Arch R Gamepad"
#define MAX_EVENTS 16
#define RETRY_DELAY_US 200000 /* 200ms between retries */
#define MAX_RETRIES 50 /* 10 seconds max wait */
static volatile int running = 1;
static void signal_handler(int sig)
{
(void)sig;
running = 0;
}
/* Find /dev/input/eventX by device name, return fd or -1 */
static int find_device(const char *name)
{
char path[64];
char dev_name[256];
int i;
for (i = 0; i < 32; i++) {
int fd;
snprintf(path, sizeof(path), "/dev/input/event%d", i);
fd = open(path, O_RDONLY | O_NONBLOCK);
if (fd < 0)
continue;
if (ioctl(fd, EVIOCGNAME(sizeof(dev_name)), dev_name) >= 0) {
if (strcmp(dev_name, name) == 0)
return fd;
}
close(fd);
}
return -1;
}
/* Create uinput virtual device with capabilities from both sources */
static int setup_uinput(int gpio_fd, int adc_fd)
{
int ufd;
struct uinput_setup setup;
unsigned long bits[(KEY_MAX + 1) / (8 * sizeof(unsigned long)) + 1];
int i;
ufd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (ufd < 0) {
perror("open /dev/uinput");
return -1;
}
/* Enable EV_KEY + copy key bits from gpio-keys */
ioctl(ufd, UI_SET_EVBIT, EV_KEY);
memset(bits, 0, sizeof(bits));
if (ioctl(gpio_fd, EVIOCGBIT(EV_KEY, sizeof(bits)), bits) >= 0) {
for (i = 0; i <= KEY_MAX; i++) {
if (bits[i / (8 * sizeof(unsigned long))] &
(1UL << (i % (8 * sizeof(unsigned long))))) {
ioctl(ufd, UI_SET_KEYBIT, i);
}
}
}
/* Enable EV_ABS + copy abs bits from adc-joystick (with absinfo) */
ioctl(ufd, UI_SET_EVBIT, EV_ABS);
memset(bits, 0, sizeof(bits));
if (ioctl(adc_fd, EVIOCGBIT(EV_ABS, sizeof(bits)), bits) >= 0) {
for (i = 0; i <= ABS_MAX; i++) {
if (bits[i / (8 * sizeof(unsigned long))] &
(1UL << (i % (8 * sizeof(unsigned long))))) {
struct uinput_abs_setup abs_setup;
memset(&abs_setup, 0, sizeof(abs_setup));
abs_setup.code = i;
if (ioctl(adc_fd, EVIOCGABS(i), &abs_setup.absinfo) >= 0)
ioctl(ufd, UI_ABS_SETUP, &abs_setup);
}
}
}
/* Device identity */
memset(&setup, 0, sizeof(setup));
strncpy(setup.name, DEVICE_NAME, UINPUT_MAX_NAME_SIZE - 1);
setup.id.bustype = BUS_VIRTUAL;
setup.id.vendor = 0x4152; /* "AR" */
setup.id.product = 0x3336; /* "36" */
setup.id.version = 1;
if (ioctl(ufd, UI_DEV_SETUP, &setup) < 0) {
perror("UI_DEV_SETUP");
close(ufd);
return -1;
}
if (ioctl(ufd, UI_DEV_CREATE) < 0) {
perror("UI_DEV_CREATE");
close(ufd);
return -1;
}
return ufd;
}
int main(void)
{
int gpio_fd = -1, adc_fd = -1, ufd, epfd;
int retries = 0;
struct epoll_event ev, events[MAX_EVENTS];
struct input_event ie;
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
/* Wait for both devices to appear (kernel may still be probing) */
while (running && retries < MAX_RETRIES) {
if (gpio_fd < 0)
gpio_fd = find_device("gpio-keys");
if (adc_fd < 0)
adc_fd = find_device("adc-joystick");
if (gpio_fd >= 0 && adc_fd >= 0)
break;
usleep(RETRY_DELAY_US);
retries++;
}
if (gpio_fd < 0 || adc_fd < 0) {
fprintf(stderr, "input-merge: devices not found (gpio=%d adc=%d)\n",
gpio_fd, adc_fd);
if (gpio_fd >= 0) close(gpio_fd);
if (adc_fd >= 0) close(adc_fd);
return 1;
}
/* Grab both devices (exclusive access) */
if (ioctl(gpio_fd, EVIOCGRAB, 1) < 0)
perror("EVIOCGRAB gpio-keys");
if (ioctl(adc_fd, EVIOCGRAB, 1) < 0)
perror("EVIOCGRAB adc-joystick");
/* Create virtual combined device */
ufd = setup_uinput(gpio_fd, adc_fd);
if (ufd < 0) {
ioctl(gpio_fd, EVIOCGRAB, 0);
ioctl(adc_fd, EVIOCGRAB, 0);
close(gpio_fd);
close(adc_fd);
return 1;
}
/* Small delay for uinput device to settle */
usleep(50000);
/* Signal ready: write PID file so launcher knows we're up */
{
int pfd = open("/run/input-merge.pid", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (pfd >= 0) {
char buf[16];
int len = snprintf(buf, sizeof(buf), "%d\n", getpid());
write(pfd, buf, len);
close(pfd);
}
}
fprintf(stderr, "input-merge: active (gpio=%d adc=%d -> %s)\n",
gpio_fd, adc_fd, DEVICE_NAME);
/* epoll on both source devices */
epfd = epoll_create1(0);
ev.events = EPOLLIN;
ev.data.fd = gpio_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, gpio_fd, &ev);
ev.data.fd = adc_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, adc_fd, &ev);
/* Event forwarding loop */
while (running) {
int n = epoll_wait(epfd, events, MAX_EVENTS, 1000);
int i;
for (i = 0; i < n; i++) {
while (read(events[i].data.fd, &ie, sizeof(ie)) == sizeof(ie)) {
write(ufd, &ie, sizeof(ie));
}
}
}
/* Cleanup */
ioctl(ufd, UI_DEV_DESTROY);
close(ufd);
ioctl(gpio_fd, EVIOCGRAB, 0);
ioctl(adc_fd, EVIOCGRAB, 0);
close(gpio_fd);
close(adc_fd);
close(epfd);
unlink("/run/input-merge.pid");
fprintf(stderr, "input-merge: stopped\n");
return 0;
}
+44 -53
View File
@@ -12,13 +12,13 @@ Flow:
3. Initialize audio (speaker, 60% volume)
4. Cycle through panels (most common first):
- Show panel name on tty1
- Play N beeps (N = position in list, capped at 5)
- Play N beeps (N = position in list: Panel 0=1 beep, Panel 1=2 beeps, etc.)
- Wait 15s for input: A=confirm, B/DPAD_DOWN=next
5. On confirm: write panel.txt + panel-confirmed → sync → reboot
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,37 @@ 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)
# Order: most common first
# (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: numerical (beep count = position in list, Panel 0 = 1 beep, etc.)
# R36S Original — 6 panels, default is Panel 4-V22 (~60% of units)
# Beeps: Panel 0=1, Panel 1=2, Panel 2=3, Panel 3=4, Panel 4=5, Panel 5=6
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"),
("0", "kernel-panel0.dtb", "Panel 0"),
("1", "kernel-panel1.dtb", "Panel 1-V10"),
("2", "kernel-panel2.dtb", "Panel 2-V12"),
("3", "kernel-panel3.dtb", "Panel 3-V20"),
("4", "", "Panel 4-V22 (Default)"),
("5", "kernel-panel5.dtb", "Panel 5-V22 Q8"),
]
# R36S Clone — 12 panels, default is Clone 8 ST7703 (G80CA-MB)
# Beeps: Clone 1=1, Clone 2=2, ..., Clone 10=10, R36 Max=11, RX6S=12
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)"),
("C1", "kernel-clone1.dtb", "Clone 1 (ST7703)"),
("C2", "kernel-clone2.dtb", "Clone 2 (ST7703)"),
("C3", "kernel-clone3.dtb", "Clone 3 (NV3051D)"),
("C4", "kernel-clone4.dtb", "Clone 4 (NV3051D)"),
("C5", "kernel-clone5.dtb", "Clone 5 (ST7703)"),
("C6", "kernel-clone6.dtb", "Clone 6 (NV3051D)"),
("C7", "kernel-clone7.dtb", "Clone 7 (JD9365DA)"),
("C8", "", "Clone 8 ST7703 G80CA (Default)"),
("C9", "kernel-clone9.dtb", "Clone 9 (NV3051D)"),
("C10", "kernel-clone10.dtb", "Clone 10 (ST7703)"),
("MAX", "kernel-r36max.dtb", "R36 Max (720x720)"),
("RX6S", "kernel-rx6s.dtb", "RX6S (NV3051D)"),
]
@@ -286,13 +271,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)
@@ -366,8 +351,14 @@ def main():
# Determine variant and panel list
variant = get_variant()
# no-panel variant: panel is configured by Flasher app, not wizard
if variant == "no-panel":
log_boot("no-panel variant — wizard disabled (use Flasher app)")
sys.exit(0)
panels = get_panels(variant)
default_panel = panels[0] # First in list is always the default
default_panel = next((p for p in panels if not p[1]), panels[0]) # Default = empty dtb_name
print(f"Arch R Panel Detection Wizard starting (variant: {variant})...")
print(f" {len(panels)} panels available, default: {default_panel[2]}")
@@ -395,9 +386,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)}]"
@@ -405,8 +396,8 @@ def main():
position += f" (cycle {cycle + 1})"
write_tty(f"{position} {name}")
# Audio feedback: N beeps (capped at 5)
beep_count = min(idx + 1, 5)
# Audio feedback: N beeps (panel number + 1)
beep_count = idx + 1
play_beeps(beep_count, beep)
print(f" {position} {name} — waiting...")
@@ -419,15 +410,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)
+21
View File
@@ -33,6 +33,21 @@ amixer -q sset 'DAC' 80% 2>/dev/null
# Performance governor (archr has NOPASSWD sudo for perfmax/perfnorm)
sudo /usr/local/bin/perfmax 2>/dev/null
# Merge gamepad inputs: gpio-keys (buttons) + adc-joystick (analog sticks)
# into a single virtual "Arch R Gamepad" so RetroArch sees one device.
# Without this, RetroArch assigns each to a different player port and
# analog sticks end up ignored (max_users=1).
INPUT_MERGE=/usr/local/bin/input-merge
if [ -x "$INPUT_MERGE" ]; then
sudo "$INPUT_MERGE" &
MERGE_PID=$!
# Wait for virtual device to appear
for i in 1 2 3 4 5 6 7 8 9 10; do
[ -f /run/input-merge.pid ] && break
sleep 0.1
done
fi
# Log file — write to HOME dir (archr can write here), copy to /boot after
# CRITICAL: /boot is FAT32 mounted as root — archr CANNOT write there directly!
LOGFILE="$HOME/retroarch.log"
@@ -66,6 +81,12 @@ fi
retroarch -L "$@" >> "$LOGFILE" 2>&1
ret=$?
# Stop input merger (ungrab devices so ES can use them again)
if [ -n "$MERGE_PID" ]; then
sudo kill "$MERGE_PID" 2>/dev/null
wait "$MERGE_PID" 2>/dev/null
fi
# Restore ALSA mixer state after RetroArch exits (like dArkOS verifyaudio.sh)
# RetroArch may modify mixer controls on exit — restore to known good state
amixer -q sset 'Playback Path' SPK 2>/dev/null