5 Commits

Author SHA1 Message Date
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
13 changed files with 726 additions and 81 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
+54 -1
View File
@@ -1915,6 +1915,59 @@ 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 image changes:**
Published new images with accumulated fixes since beta1.1. Nothing dramatic — mostly polish
and small fixes that came out of Flasher development and continued testing.
---
## What's Left for v1.0 Stable
### Critical — Must Work Before Release
@@ -1950,7 +2003,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 |
+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
#------------------------------------------------------------------------------
+36 -13
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)"
@@ -278,7 +285,23 @@ MERGED_DIR="$OUTPUT_DIR/panels/merged"
if [ -d "$MERGED_DIR" ]; then
panel_count=0
if [ "$VARIANT" = "original" ]; then
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))
@@ -338,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
@@ -483,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"
+12 -5
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!).
@@ -48,10 +47,18 @@ 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} ${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"
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"
+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;
}
+21 -13
View File
@@ -12,7 +12,7 @@ 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
@@ -65,30 +65,32 @@ MAX_CYCLES = 2 # auto-confirm default after this many full cycles
# (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
# 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", "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"),
("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", "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)"),
("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)"),
]
@@ -349,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]}")
@@ -388,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...")
+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