From 8a1ea92c7dc39ac41759a54b367e7e115fb059b0 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 14:57:04 -0800 Subject: [PATCH 01/10] Add Retroid platform for LessOS devices. Supports Retroid Pocket 5, Pocket Mini V2, and Pocket Flip 2 running LessOS (Snapdragon 865/SM8250). Platform features: - Multi-device variant detection via LESSOS_DEVICE - Dynamic input device discovery by name - AMOLED brightness curves - 1080p/1240p HDMI support - Overscan enabled for Mini V2 (near-square display) --- Makefile | 2 +- README.md | 53 +- skeleton/BOOT/common/updater | 3 + skeleton/SYSTEM/retroid/bin/shutdown | 9 + skeleton/SYSTEM/retroid/cores/.keep | 0 skeleton/SYSTEM/retroid/dat/.keep | 0 skeleton/SYSTEM/retroid/lib/.keep | 0 skeleton/SYSTEM/retroid/system.cfg | 2 + toolchains.json | 4 + .../all/paks/LessUI/platforms/retroid/init.sh | 10 + workspace/all/paks/Tools/Clock/pak.json | 3 +- workspace/all/paks/Tools/Files/pak.json | 3 +- workspace/all/paks/Tools/Input/pak.json | 3 +- .../all/paks/Tools/Kitchen Sink/pak.json | 3 +- workspace/all/paks/Tools/Simple Wifi/pak.json | 2 +- workspace/all/paks/Tools/Splore/pak.json | 2 +- .../all/paks/Tools/System Report/pak.json | 3 +- workspace/all/paks/config/platforms.json | 8 + workspace/retroid/Makefile | 28 + workspace/retroid/README.md | 132 ++++ workspace/retroid/install/boot.sh | 77 +++ workspace/retroid/libmsettings/Makefile | 32 + workspace/retroid/libmsettings/msettings.c | 228 +++++++ workspace/retroid/libmsettings/msettings.h | 25 + workspace/retroid/platform/Makefile.copy | 13 + workspace/retroid/platform/Makefile.env | 6 + workspace/retroid/platform/platform.c | 622 ++++++++++++++++++ workspace/retroid/platform/platform.h | 238 +++++++ workspace/retroid/show/Makefile | 14 + workspace/retroid/show/show.c | 83 +++ 30 files changed, 1575 insertions(+), 33 deletions(-) create mode 100755 skeleton/SYSTEM/retroid/bin/shutdown create mode 100644 skeleton/SYSTEM/retroid/cores/.keep create mode 100644 skeleton/SYSTEM/retroid/dat/.keep create mode 100644 skeleton/SYSTEM/retroid/lib/.keep create mode 100644 skeleton/SYSTEM/retroid/system.cfg create mode 100644 workspace/all/paks/LessUI/platforms/retroid/init.sh create mode 100644 workspace/retroid/Makefile create mode 100644 workspace/retroid/README.md create mode 100755 workspace/retroid/install/boot.sh create mode 100644 workspace/retroid/libmsettings/Makefile create mode 100644 workspace/retroid/libmsettings/msettings.c create mode 100644 workspace/retroid/libmsettings/msettings.h create mode 100644 workspace/retroid/platform/Makefile.copy create mode 100644 workspace/retroid/platform/Makefile.env create mode 100644 workspace/retroid/platform/platform.c create mode 100644 workspace/retroid/platform/platform.h create mode 100755 workspace/retroid/show/Makefile create mode 100644 workspace/retroid/show/show.c diff --git a/Makefile b/Makefile index 1530ee87..66c10a13 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ endif # Default platforms to build (can be overridden with PLATFORMS=...) ifeq (,$(PLATFORMS)) -PLATFORMS = miyoomini trimuismart rg35xx rg35xxplus my355 tg5040 zero28 rgb30 m17 my282 magicmini +PLATFORMS = miyoomini trimuismart rg35xx rg35xxplus my355 tg5040 zero28 rgb30 m17 my282 magicmini retroid endif ########################################################### diff --git a/README.md b/README.md index 445754e0..3b2b116c 100644 --- a/README.md +++ b/README.md @@ -75,31 +75,34 @@ For detailed installation steps and device-specific setup, see the `README.txt` ## Supported Devices -| Manufacturer | Device | Released | Status | -| ------------ | ------------ | ------------- | ---------- | -| **Anbernic** | RG35XX | December 2022 | ✓ | -| **Anbernic** | RG35XX Plus | November 2023 | ✓ | -| **Anbernic** | RG35XXH | January 2024 | ✓ | -| **Anbernic** | RG28XX | April 2024 | ✓ | -| **Anbernic** | RG35XXSP | May 2024 | ✓ | -| **Anbernic** | RG40XXH | July 2024 | ✓ | -| **Anbernic** | RG40XXV | August 2024 | ✓ | -| **Anbernic** | RG CubeXX | October 2024 | ✓ | -| **Anbernic** | RG34XX | December 2024 | ✓ | -| **Anbernic** | RG34XXSP | May 2025 | ✓ | -| **Miyoo** | Mini | December 2021 | ✓ | -| **Miyoo** | Mini Plus | February 2023 | ✓ | -| **Miyoo** | A30 | May 2024 | ✓ | -| **Miyoo** | Mini Flip | October 2024 | ✓ | -| **Miyoo** | Flip | January 2025 | ✓ | -| **Trimui** | Smart | October 2022 | ✓ | -| **Trimui** | Smart Pro | November 2023 | ✓ | -| **Trimui** | Brick | October 2024 | ✓ | -| **Powkiddy** | RGB30 | August 2023 | ✓ | -| **MagicX** | Mini Zero 28 | January 2025 | ✓ | -| **GKD** | Pixel | January 2024 | Deprecated | -| **Generic** | M17 | October 2023 | Deprecated | -| **MagicX** | XU Mini M | May 2024 | Deprecated | +| Manufacturer | Device | Released | Status | +| ------------ | -------------- | ------------- | ---------- | +| **Anbernic** | RG35XX | December 2022 | ✓ | +| **Anbernic** | RG35XX Plus | November 2023 | ✓ | +| **Anbernic** | RG35XXH | January 2024 | ✓ | +| **Anbernic** | RG28XX | April 2024 | ✓ | +| **Anbernic** | RG35XXSP | May 2024 | ✓ | +| **Anbernic** | RG40XXH | July 2024 | ✓ | +| **Anbernic** | RG40XXV | August 2024 | ✓ | +| **Anbernic** | RG CubeXX | October 2024 | ✓ | +| **Anbernic** | RG34XX | December 2024 | ✓ | +| **Anbernic** | RG34XXSP | May 2025 | ✓ | +| **Miyoo** | Mini | December 2021 | ✓ | +| **Miyoo** | Mini Plus | February 2023 | ✓ | +| **Miyoo** | A30 | May 2024 | ✓ | +| **Miyoo** | Mini Flip | October 2024 | ✓ | +| **Miyoo** | Flip | January 2025 | ✓ | +| **Trimui** | Smart | October 2022 | ✓ | +| **Trimui** | Smart Pro | November 2023 | ✓ | +| **Trimui** | Brick | October 2024 | ✓ | +| **Powkiddy** | RGB30 | August 2023 | ✓ | +| **Retroid** | Pocket 5 | November 2024 | ✓ | +| **Retroid** | Pocket Mini V2 | October 2024 | ✓ | +| **Retroid** | Pocket Flip 2 | March 2025 | ✓ | +| **MagicX** | Mini Zero 28 | January 2025 | ✓ | +| **GKD** | Pixel | January 2024 | Deprecated | +| **Generic** | M17 | October 2023 | Deprecated | +| **MagicX** | XU Mini M | May 2024 | Deprecated | > [!NOTE] > Deprecated devices will continue to work with current LessUI releases but will not receive new features or platform-specific fixes. diff --git a/skeleton/BOOT/common/updater b/skeleton/BOOT/common/updater index 3795b744..da929ffb 100755 --- a/skeleton/BOOT/common/updater +++ b/skeleton/BOOT/common/updater @@ -8,6 +8,9 @@ if [ -n "$LESSOS_DEVICE" ]; then *"RGB30"*) PLATFORM="rgb30" ;; + *"Retroid"*|*"Pocket"*) + PLATFORM="retroid" + ;; *"RG353P"*|*"RG353V"*|*"RG353M"*|*"RG353"*) # Future: map to rg353 platform if/when we add it # For now, unsupported diff --git a/skeleton/SYSTEM/retroid/bin/shutdown b/skeleton/SYSTEM/retroid/bin/shutdown new file mode 100755 index 00000000..9d089347 --- /dev/null +++ b/skeleton/SYSTEM/retroid/bin/shutdown @@ -0,0 +1,9 @@ +#!/bin/sh + +if [ -n "$DATETIME_PATH" ]; then + echo `date +'%F %T'` > "$DATETIME_PATH" + sync +fi + +cat /dev/zero > /dev/fb0 +poweroff diff --git a/skeleton/SYSTEM/retroid/cores/.keep b/skeleton/SYSTEM/retroid/cores/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skeleton/SYSTEM/retroid/dat/.keep b/skeleton/SYSTEM/retroid/dat/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skeleton/SYSTEM/retroid/lib/.keep b/skeleton/SYSTEM/retroid/lib/.keep new file mode 100644 index 00000000..e69de29b diff --git a/skeleton/SYSTEM/retroid/system.cfg b/skeleton/SYSTEM/retroid/system.cfg new file mode 100644 index 00000000..f2e1550f --- /dev/null +++ b/skeleton/SYSTEM/retroid/system.cfg @@ -0,0 +1,2 @@ +-player_screen_sharpness = Crisp + diff --git a/toolchains.json b/toolchains.json index be87624d..d6b1c354 100644 --- a/toolchains.json +++ b/toolchains.json @@ -9,6 +9,10 @@ "rgb30": { "platform": "linux/amd64", "toolchain": "rk3566" + }, + "retroid": { + "platform": "linux/amd64", + "toolchain": "rk3566" } } } diff --git a/workspace/all/paks/LessUI/platforms/retroid/init.sh b/workspace/all/paks/LessUI/platforms/retroid/init.sh new file mode 100644 index 00000000..83c926ab --- /dev/null +++ b/workspace/all/paks/LessUI/platforms/retroid/init.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# Retroid Pocket SM8250 platform initialization (LessOS) +# Devices: Pocket 5, Pocket Flip 2, Pocket Mini V2 +# OS: LessOS (ROCKNIX-based) + +# CPU governor (speed controlled by frontend) +echo userspace >/sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null + +# Start keymon +LOG_FILE="$LOGS_PATH/keymon.log" keymon.elf & diff --git a/workspace/all/paks/Tools/Clock/pak.json b/workspace/all/paks/Tools/Clock/pak.json index 30d214ca..ee6e7be7 100644 --- a/workspace/all/paks/Tools/Clock/pak.json +++ b/workspace/all/paks/Tools/Clock/pak.json @@ -15,7 +15,8 @@ "rgb30", "m17", "my282", - "magicmini" + "magicmini", + "retroid" ], "build": { diff --git a/workspace/all/paks/Tools/Files/pak.json b/workspace/all/paks/Tools/Files/pak.json index 3866541f..80496db9 100644 --- a/workspace/all/paks/Tools/Files/pak.json +++ b/workspace/all/paks/Tools/Files/pak.json @@ -15,7 +15,8 @@ "rgb30", "my282", "magicmini", - "m17" + "m17", + "retroid" ], "launch": "launch.sh" diff --git a/workspace/all/paks/Tools/Input/pak.json b/workspace/all/paks/Tools/Input/pak.json index 8828d172..1a29501c 100644 --- a/workspace/all/paks/Tools/Input/pak.json +++ b/workspace/all/paks/Tools/Input/pak.json @@ -15,7 +15,8 @@ "rgb30", "m17", "my282", - "magicmini" + "magicmini", + "retroid" ], "build": { diff --git a/workspace/all/paks/Tools/Kitchen Sink/pak.json b/workspace/all/paks/Tools/Kitchen Sink/pak.json index dba7e13b..ec4c617e 100644 --- a/workspace/all/paks/Tools/Kitchen Sink/pak.json +++ b/workspace/all/paks/Tools/Kitchen Sink/pak.json @@ -16,7 +16,8 @@ "rgb30", "m17", "my282", - "magicmini" + "magicmini", + "retroid" ], "launch": "launch.sh" diff --git a/workspace/all/paks/Tools/Simple Wifi/pak.json b/workspace/all/paks/Tools/Simple Wifi/pak.json index 04d81c66..d4041818 100644 --- a/workspace/all/paks/Tools/Simple Wifi/pak.json +++ b/workspace/all/paks/Tools/Simple Wifi/pak.json @@ -4,7 +4,7 @@ "description": "Simple WiFi toggle using stock firmware utilities", "version": "1.0.0", - "platforms": ["rgb30"], + "platforms": ["rgb30", "retroid"], "build": { "type": "shell-only" diff --git a/workspace/all/paks/Tools/Splore/pak.json b/workspace/all/paks/Tools/Splore/pak.json index 9ee524db..9867818f 100644 --- a/workspace/all/paks/Tools/Splore/pak.json +++ b/workspace/all/paks/Tools/Splore/pak.json @@ -4,7 +4,7 @@ "description": "Launch PICO-8 in Splore mode for browsing and playing BBS games", "version": "1.0.0", - "platforms": ["rgb30"], + "platforms": ["rgb30", "retroid"], "launch": "launch.sh" } diff --git a/workspace/all/paks/Tools/System Report/pak.json b/workspace/all/paks/Tools/System Report/pak.json index 8667f0e3..103d979e 100644 --- a/workspace/all/paks/Tools/System Report/pak.json +++ b/workspace/all/paks/Tools/System Report/pak.json @@ -15,7 +15,8 @@ "rgb30", "m17", "my282", - "magicmini" + "magicmini", + "retroid" ], "build": { diff --git a/workspace/all/paks/config/platforms.json b/workspace/all/paks/config/platforms.json index 6e514c66..cab9c79e 100644 --- a/workspace/all/paks/config/platforms.json +++ b/workspace/all/paks/config/platforms.json @@ -77,6 +77,14 @@ "sdcard_path": "/mnt/sdcard", "shutdown_cmd": "shutdown", "nice_prefix": "" + }, + "retroid": { + "description": "Retroid Pocket 5 / Mini V2 / Flip 2 (LessOS)", + "arch": "arm64", + "sdcard_path": "/storage", + "shutdown_cmd": "poweroff", + "nice_prefix": "", + "lessos": true } } } diff --git a/workspace/retroid/Makefile b/workspace/retroid/Makefile new file mode 100644 index 00000000..ac97826a --- /dev/null +++ b/workspace/retroid/Makefile @@ -0,0 +1,28 @@ +########################################################### + +ifeq (,$(PLATFORM)) +PLATFORM = $(UNION_PLATFORM) +endif + +ifeq (,$(PLATFORM)) +$(error please specify PLATFORM, eg. PLATFORM=retroid make) +endif + +########################################################### + +# LessOS provides SDL2 natively - no sdl12-compat needed + +# keymon.elf is now built in workspace/all/utils +# LessOS doesn't need DinguxCommander (file manager for stock firmware) +all: readmes show + +.PHONY: show +show: + @$(MAKE) -C show + +# No early dependencies for retroid (LessOS-based platform) +early: + +clean: + +include ../all/readmes/Makefile \ No newline at end of file diff --git a/workspace/retroid/README.md b/workspace/retroid/README.md new file mode 100644 index 00000000..d0efdfe9 --- /dev/null +++ b/workspace/retroid/README.md @@ -0,0 +1,132 @@ +# Retroid Platform + +Platform implementation for Retroid Pocket devices running LessOS. + +## Devices + +| Device | Display | Resolution | Notes | +|--------|---------|------------|-------| +| **Pocket 5** | 5.5" AMOLED | 1920x1080 | Flagship model | +| **Pocket Flip 2** | 5.5" AMOLED | 1920x1080 | Clamshell design | +| **Pocket Mini V2** | 3.92" AMOLED | 1240x1080 | Compact form factor | + +**OS**: LessOS (ROCKNIX-based) + +## Hardware + +| Feature | Specification | +|---------|---------------| +| SoC | Qualcomm Snapdragon 865 (SM8250) | +| CPU | 1×A77@2.84G, 3×A77@2.4G, 4×A55@1.8G | +| GPU | Adreno 650 (Vulkan, OpenGL ES 3.2) | +| RAM | 6-8GB LPDDR4x | +| Storage | 128GB UFS 3.1 + microSD | +| HDMI | USB-C DisplayPort (1080p output) | +| Input | D-pad, A/B/X/Y, L1/R1, analog L2/R2, dual hall-effect sticks (L3/R3) | +| Audio | ALSA, USB-C audio | +| Connectivity | WiFi 6, Bluetooth 5.1 | + +## Input + +Uses `retroid-pocket-gamepad` kernel driver via serdev/evdev. + +| Combination | Function | +|-------------|----------| +| PLUS/MINUS | Volume | +| MENU + PLUS/MINUS | Brightness | +| POWER | Sleep/wake | +| HOME (MODE) | Menu | + +## Directory Structure + +``` +retroid/ +├── platform/ Platform definitions and render backend +├── keymon/ Button monitoring daemon (prebuilt) +├── libmsettings/ Settings library (volume, brightness, HDMI) +├── show/ Boot splash display +└── install/ Boot script +``` + +## Building + +```bash +make PLATFORM=retroid shell +cd /root/workspace/retroid +make +``` + +Uses the `sm8250` toolchain (shared via `toolchains.json`). + +## Installation + +LessOS handles boot via standard ROCKNIX init. LessUI installs to: + +``` +/storage/ +├── .system/retroid/ Platform binaries and paks +├── .userdata/retroid/ User settings +└── Roms/ ROM files +``` + +## Environment Variables + +LessOS provides these environment variables: + +| Variable | Description | Example | +|----------|-------------|---------| +| `LESSOS_PLATFORM` | SoC/platform family | `SM8250` | +| `LESSOS_DEVICE` | Device model | `Retroid Pocket 5` | +| `LESSOS_STORAGE` | Writable storage path | `/storage` | + +## Platform Features + +- **OpenGL ES**: Hardware-accelerated rendering via Adreno 650 +- **Vulkan**: Full Vulkan support via Turnip driver +- **HDMI**: USB-C DisplayPort with auto-detection +- **Overscan**: Supported on Mini V2 (near-square aspect) +- **Effects**: Grid/line overlays via OpenGL +- **Rumble**: Haptic feedback via pmi8998_haptics + +## Device Variants + +The platform auto-detects device variant via `LESSOS_DEVICE`: + +| Variant | Devices | Resolution | Aspect | +|---------|---------|------------|--------| +| `VARIANT_RETROID_FHD` | Pocket 5, Flip 2 | 1920x1080 | 16:9 | +| `VARIANT_RETROID_MINI_V2` | Pocket Mini V2 | 1240x1080 | ~31:27 | + +## LessOS/ROCKNIX References + +For platform implementation reference (in LessOS repository): + +**Kernel & Drivers:** +- Retroid gamepad driver: `projects/ROCKNIX/devices/SM8250/patches/linux/0008-retroid-gamepad.patch` +- Device tree (common): `projects/ROCKNIX/devices/SM8250/patches/linux/0000-sm8250-retroidpocket-common.patch` +- Device tree (RP5): `projects/ROCKNIX/devices/SM8250/patches/linux/0002-sm8250-retroidpocket-rp5.patch` +- Device tree (Mini): `projects/ROCKNIX/devices/SM8250/patches/linux/0003-sm8250-retroidpocket-rpmini.patch` + +**Input Configuration:** +- RetroArch joypad config: `packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/Retroid Pocket Gamepad.cfg` +- SDL2 gamepad mapping: `packages/apps/gamecontrollerdb/config/gamecontrollerdb.txt` +- Input discovery script: `packages/sysutils/system-utils/sources/scripts/input_sense` +- UDEV rules: `devices/SM8250/filesystem/usr/lib/udev/rules.d/99-retroid-pocket.rules` + +**Platform Configuration:** +- Device options: `devices/SM8250/options` +- Audio (ALSA UCM): `packages/audio/alsa-ucm-conf/patches/SM8250/0002_Add-Retroid-Pocket-SM8250-configuration.patch` + +**Button Codes from Kernel Driver:** +- D-Pad: BTN_DPAD_UP/DOWN/LEFT/RIGHT (544-547) +- Face: BTN_SOUTH/EAST/WEST/NORTH (304,305,308,307) +- Shoulders: BTN_TL/TR (310,311), analog triggers ABS_HAT2X/Y +- Thumbsticks: BTN_THUMBL/THUMBR (317,318) +- System: BTN_MODE (316), BTN_BACK (158) + +## Related + +- Platform header: `platform/platform.h` +- Shared code: `../all/launcher/`, `../all/player/` +- LessOS repository: `~/Code/lessui-hq/LessOS` +- Upstream ROCKNIX: https://github.com/ROCKNIX/distribution diff --git a/workspace/retroid/install/boot.sh b/workspace/retroid/install/boot.sh new file mode 100755 index 00000000..0c49485e --- /dev/null +++ b/workspace/retroid/install/boot.sh @@ -0,0 +1,77 @@ +#!/bin/sh +# NOTE: becomes .tmp_update/retroid.sh + +PLATFORM="retroid" +# Use LessOS storage path if set, otherwise default +SDCARD_PATH="${LESSOS_STORAGE:-/storage}" +UPDATE_PATH="$SDCARD_PATH/LessUI.7z" +SYSTEM_PATH="$SDCARD_PATH/.system" +LOG_FILE="$SDCARD_PATH/lessui-install.log" +BOOT_LOG="$SDCARD_PATH/lessui-boot.log" + +# Source shared update functions +. "$(dirname "$0")/install/update-functions.sh" + +# Boot logging helper +boot_log() { + echo "[$(date '+%H:%M:%S')] $*" >>"$BOOT_LOG" +} +echo "=== LessUI boot started: $(date) ===" >"$BOOT_LOG" +boot_log "PLATFORM=$PLATFORM" +boot_log "SDCARD_PATH=$SDCARD_PATH" +boot_log "LESSOS_PLATFORM=$LESSOS_PLATFORM" +boot_log "LESSOS_DEVICE=$LESSOS_DEVICE" +boot_log "LESSOS_STORAGE=$LESSOS_STORAGE" + +# CPU setup (Snapdragon 865 - use userspace governor for manual control) +echo userspace >/sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null +CPU_PATH=/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed +CPU_SPEED_PERF=2419200 +echo $CPU_SPEED_PERF >$CPU_PATH 2>/dev/null +boot_log "CPU governor set" + +# install/update +if [ -f "$UPDATE_PATH" ]; then + cd $(dirname "$0")/$PLATFORM + boot_log "Changed to $(pwd)" + + if [ -d "$SYSTEM_PATH" ]; then + ACTION="update" + boot_log "Running: ./show.elf ./updating.png" + ./show.elf ./updating.png 2>>"$BOOT_LOG" + boot_log "show.elf exit code: $?" + else + ACTION="installation" + boot_log "Running: ./show.elf ./installing.png" + ./show.elf ./installing.png 2>>"$BOOT_LOG" + boot_log "show.elf exit code: $?" + fi + + log_info "Starting LessUI $ACTION..." + + # Perform atomic update with automatic rollback + atomic_system_update "$UPDATE_PATH" "$SDCARD_PATH" "$SYSTEM_PATH" "$LOG_FILE" + boot_log "atomic_system_update completed: $?" + + # Run platform-specific install script + run_platform_install "$SYSTEM_PATH/$PLATFORM/bin/install.sh" "$LOG_FILE" + boot_log "run_platform_install completed: $?" + + # Return to valid directory (atomic_system_update may have deleted .tmp_update) + cd "$SDCARD_PATH" + boot_log "Returned to $SDCARD_PATH" +fi + +LAUNCH_PATH="$SYSTEM_PATH/$PLATFORM/paks/LessUI.pak/launch.sh" +boot_log "LAUNCH_PATH=$LAUNCH_PATH" +boot_log "File exists: $([ -f "$LAUNCH_PATH" ] && echo yes || echo no)" + +while [ -f "$LAUNCH_PATH" ]; do + boot_log "Starting launch.sh..." + "$LAUNCH_PATH" 2>>"$BOOT_LOG" + LAUNCH_EXIT=$? + boot_log "launch.sh exited with code: $LAUNCH_EXIT" +done + +boot_log "Exiting boot loop, rebooting..." +reboot diff --git a/workspace/retroid/libmsettings/Makefile b/workspace/retroid/libmsettings/Makefile new file mode 100644 index 00000000..bc879d7b --- /dev/null +++ b/workspace/retroid/libmsettings/Makefile @@ -0,0 +1,32 @@ +ifeq (,$(CROSS_COMPILE)) +$(error missing CROSS_COMPILE for this toolchain) +endif +ifeq (,$(PREFIX)) +$(error missing PREFIX for this toolchain) +endif + +TARGET = msettings + +.PHONY: build +.PHONY: clean + +CC = $(CROSS_COMPILE)gcc + +SYSROOT := $(shell $(CC) --print-sysroot) + +INCLUDEDIR = $(SYSROOT)/usr/include +# OPT_FLAGS from parent makefile (-O3 for release, -O0 -g for debug) +OPT_FLAGS ?= -O3 +CFLAGS = -I$(INCLUDEDIR) $(OPT_FLAGS) +LDFLAGS = -ldl -lrt -s + +build: + @$(CC) -c -Werror -fpic "$(TARGET).c" -Wl,--no-as-needed $(CFLAGS) + @$(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" $(LDFLAGS) + @cp "$(TARGET).h" "$(PREFIX)/include" + @cp "lib$(TARGET).so" "$(PREFIX)/lib" +clean: + rm -f *.o + rm -f "lib$(TARGET).so" + rm -f $(PREFIX)/include/$(TARGET).h + rm -f $(PREFIX)/lib/lib$(TARGET).so \ No newline at end of file diff --git a/workspace/retroid/libmsettings/msettings.c b/workspace/retroid/libmsettings/msettings.c new file mode 100644 index 00000000..180f5d57 --- /dev/null +++ b/workspace/retroid/libmsettings/msettings.c @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "msettings.h" + +/////////////////////////////////////// + +#define SETTINGS_VERSION 2 +typedef struct Settings { + int version; // future proofing + int brightness; + int headphones; + int speaker; + int unused[2]; // for future use + // NOTE: doesn't really need to be persisted but still needs to be shared + int jack; + int hdmi; +} Settings; +static Settings DefaultSettings = { + .version = SETTINGS_VERSION, + .brightness = 5, + .headphones = 8, + .speaker = 12, + .jack = 0, + .hdmi = 0, +}; +static Settings* settings; + +#define SHM_KEY "/SharedSettings" +static char SettingsPath[256]; +static int shm_fd = -1; +static int is_host = 0; +static int shm_size = sizeof(Settings); + +// SM8250 uses panel0-backlight for AMOLED displays +#define BRIGHTNESS_PATH "/sys/class/backlight/panel0-backlight/brightness" +#define BRIGHTNESS_MAX_PATH "/sys/class/backlight/panel0-backlight/max_brightness" +#define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" + +static int max_brightness = 255; + +int getInt(char* path) { + int i = 0; + FILE* file = fopen(path, "r"); + if (file != NULL) { + fscanf(file, "%i", &i); + fclose(file); + } + return i; +} + +void InitSettings(void) { + sprintf(SettingsPath, "%s/msettings.bin", getenv("USERDATA_PATH")); + + // Read max brightness from sysfs + max_brightness = getInt(BRIGHTNESS_MAX_PATH); + if (max_brightness <= 0) + max_brightness = 255; + + shm_fd = shm_open(SHM_KEY, O_RDWR | O_CREAT | O_EXCL, 0644); // see if it exists + if (shm_fd == -1 && errno == EEXIST) { // already exists + puts("Settings client"); + shm_fd = shm_open(SHM_KEY, O_RDWR, 0644); + settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + } else { // host + puts("Settings host"); // should always be keymon + is_host = 1; + // we created it so set initial size and populate + ftruncate(shm_fd, shm_size); + settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + + int fd = open(SettingsPath, O_RDONLY); + if (fd >= 0) { + read(fd, settings, shm_size); + close(fd); + } else { + // load defaults + memcpy(settings, &DefaultSettings, shm_size); + } + } + + int hdmi = getInt(HDMI_STATE_PATH); + printf("brightness: %i (hdmi: %i)\nspeaker: %i\n", settings->brightness, hdmi, + settings->speaker); + fflush(stdout); + + SetHDMI(hdmi); + SetBrightness(GetBrightness()); +} + +void QuitSettings(void) { + munmap(settings, shm_size); + if (is_host) + shm_unlink(SHM_KEY); +} + +static inline void SaveSettings(void) { + int fd = open(SettingsPath, O_CREAT | O_WRONLY, 0644); + if (fd >= 0) { + write(fd, settings, shm_size); + close(fd); + sync(); + } +} + +int GetBrightness(void) { // 0-10 + return settings->brightness; +} + +void SetBrightness(int value) { + if (settings->hdmi) + return; + + // Map 0-10 to exponential curve for AMOLED perception + int raw; + switch (value) { + case 0: + raw = max_brightness * 1 / 100; + break; + case 1: + raw = max_brightness * 2 / 100; + break; + case 2: + raw = max_brightness * 4 / 100; + break; + case 3: + raw = max_brightness * 8 / 100; + break; + case 4: + raw = max_brightness * 15 / 100; + break; + case 5: + raw = max_brightness * 25 / 100; + break; + case 6: + raw = max_brightness * 40 / 100; + break; + case 7: + raw = max_brightness * 55 / 100; + break; + case 8: + raw = max_brightness * 70 / 100; + break; + case 9: + raw = max_brightness * 85 / 100; + break; + case 10: + raw = max_brightness; + break; + default: + raw = max_brightness / 2; + break; + } + SetRawBrightness(raw); + settings->brightness = value; + SaveSettings(); +} + +int GetVolume(void) { // 0-20 + return settings->jack ? settings->headphones : settings->speaker; +} + +void SetVolume(int value) { + if (settings->hdmi) + return; + + if (settings->jack) + settings->headphones = value; + else + settings->speaker = value; + + int raw = value * 5; + SetRawVolume(raw); + SaveSettings(); +} + +void SetRawBrightness(int val) { + if (settings->hdmi) + return; + + int fd = open(BRIGHTNESS_PATH, O_WRONLY); + if (fd >= 0) { + dprintf(fd, "%d", val); + close(fd); + } +} + +void SetRawVolume(int val) { // 0 - 100 + char cmd[256]; + sprintf(cmd, "amixer sset -M 'Master' %i%% &> /dev/null", val); + system(cmd); +} + +// monitored and set by thread in keymon +int GetJack(void) { + return settings->jack; +} + +void SetJack(int value) { + settings->jack = value; + SetVolume(GetVolume()); +} + +int GetHDMI(void) { + return settings->hdmi; +} + +void SetHDMI(int value) { + settings->hdmi = value; + if (value) + SetRawVolume(100); // max + else + SetVolume(GetVolume()); // restore +} + +int GetMute(void) { + return 0; +} +void SetMute(int value) { +} diff --git a/workspace/retroid/libmsettings/msettings.h b/workspace/retroid/libmsettings/msettings.h new file mode 100644 index 00000000..47e5fb00 --- /dev/null +++ b/workspace/retroid/libmsettings/msettings.h @@ -0,0 +1,25 @@ +#ifndef __msettings_h__ +#define __msettings_h__ + +void InitSettings(void); +void QuitSettings(void); + +int GetBrightness(void); +int GetVolume(void); + +void SetRawBrightness(int value); // 0-1024 +void SetRawVolume(int value); // 0-40 + +void SetBrightness(int value); // 0-10 +void SetVolume(int value); // 0-20 + +int GetJack(void); +void SetJack(int value); // 0-1 + +int GetHDMI(void); +void SetHDMI(int value); // 0-1 + +int GetMute(void); +void SetMute(int value); // 0-1 + +#endif // __msettings_h__ diff --git a/workspace/retroid/platform/Makefile.copy b/workspace/retroid/platform/Makefile.copy new file mode 100644 index 00000000..4cfcbe0d --- /dev/null +++ b/workspace/retroid/platform/Makefile.copy @@ -0,0 +1,13 @@ +$(PLATFORM): + # $@ + # keymon.elf already installed by workspace/all/utils install target + # show.elf (platform-specific build) + cp ./workspace/$@/show/show.elf ./build/SYSTEM/$@/bin/ + # LessOS provides SDL2 natively - no sdl12-compat needed + # installer + rsync -a ./workspace/$@/install/boot.sh ./build/BOOT/common/$@.sh + mkdir -p ./build/BOOT/common/$@/ + cp ./workspace/$@/show/show.elf ./build/BOOT/common/$@/ + # boot assets (use @3x for 1080p FHD displays) + rsync -a ./skeleton/SYSTEM/res/installing@3x.png ./build/BOOT/common/$@/installing.png + rsync -a ./skeleton/SYSTEM/res/updating@3x.png ./build/BOOT/common/$@/updating.png \ No newline at end of file diff --git a/workspace/retroid/platform/Makefile.env b/workspace/retroid/platform/Makefile.env new file mode 100644 index 00000000..26d90cad --- /dev/null +++ b/workspace/retroid/platform/Makefile.env @@ -0,0 +1,6 @@ +# retroid - LessOS toolchain (using rk3566 for now) +# TODO: When sm8250 toolchain is available, update to: +# ARCH = -mtune=cortex-a77.cortex-a55 -march=armv8.2-a+crypto -O3 +ARCH = -mtune=cortex-a55 -march=armv8.2-a -O3 +LIBS = -flto -lrga -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 +SDL = SDL2 \ No newline at end of file diff --git a/workspace/retroid/platform/platform.c b/workspace/retroid/platform/platform.c new file mode 100644 index 00000000..d5d340c8 --- /dev/null +++ b/workspace/retroid/platform/platform.c @@ -0,0 +1,622 @@ +/** + * platform.c - Retroid Pocket SM8250 platform implementation + * + * REFACTORED VERSION - Uses shared render_sdl2 backend + * + * Supports multiple device variants in the Retroid Pocket family: + * - Retroid Pocket 5 (1920x1080) + * - Retroid Pocket Flip 2 (1920x1080) + * - Retroid Pocket Mini V2 (1240x1080) + * + * Hardware features: + * - SDL2-based video with HDMI support (via render_sdl2) + * - Retroid Pocket Gamepad via serdev kernel driver + * - Haptic feedback via pmi8998_haptics + * - Display effects (scanlines, grid) + * + * Device detection via LESSOS_DEVICE environment variable. + * + * LessOS/ROCKNIX upstream references (in LessOS repository): + * - Retroid gamepad kernel driver: devices/SM8250/patches/linux/0008-retroid-gamepad.patch + * - Input discovery pattern: packages/sysutils/system-utils/sources/scripts/input_sense + * - Button mappings: packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/Retroid Pocket Gamepad.cfg + * - UDEV rules: devices/SM8250/filesystem/usr/lib/udev/rules.d/99-retroid-pocket.rules + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "api.h" +#include "defines.h" +#include "platform.h" +#include "utils.h" + +#include "gl_video.h" +#include "render_sdl2.h" +#include "scaler.h" + +// Paths for HDMI detection +#define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" + +/////////////////////////////// +// Device Registry and Variant Configuration +/////////////////////////////// + +// Device registry - all known devices that work with this platform +static const DeviceInfo retroid_devices[] = { + // 1920x1080 FHD devices + {.device_id = "pocket5", .display_name = "Pocket 5", .manufacturer = "Retroid"}, + {.device_id = "flip2", .display_name = "Pocket Flip 2", .manufacturer = "Retroid"}, + + // 1240x1080 Mini V2 + {.device_id = "miniv2", .display_name = "Pocket Mini V2", .manufacturer = "Retroid"}, + + // Sentinel + {NULL, NULL, NULL}}; + +// Variant configuration table +typedef struct { + VariantType variant; + int screen_width; + int screen_height; + float screen_diagonal_default; + uint32_t hw_features; +} VariantConfig; + +static const VariantConfig retroid_variants[] = { + {.variant = VARIANT_RETROID_FHD, + .screen_width = 1920, + .screen_height = 1080, + .screen_diagonal_default = 5.5f, + .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, + {.variant = VARIANT_RETROID_MINI_V2, + .screen_width = 1240, + .screen_height = 1080, + .screen_diagonal_default = 3.92f, + .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, + {.variant = VARIANT_NONE} // Sentinel +}; + +// Device-to-variant mapping +typedef struct { + const char* device_string; // What to look for in LESSOS_DEVICE + VariantType variant; // Which variant config to use + const DeviceInfo* device; // Which device info to use + float screen_diagonal; // Override if different from variant default (0 = use default) +} DeviceVariantMap; + +static const DeviceVariantMap retroid_device_map[] = { + // 1920x1080 FHD devices - VARIANT_RETROID_FHD + {"Retroid Pocket 5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, + {"Pocket 5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, + {"RP5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, + + {"Retroid Pocket Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + {"Pocket Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + {"Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + {"RPF2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + + // 1240x1080 Mini V2 - VARIANT_RETROID_MINI_V2 + {"Retroid Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"RPMV2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + + // Sentinel + {NULL, VARIANT_NONE, NULL, 0.0f}}; + +static const VariantConfig* getVariantConfig(VariantType variant) { + for (int i = 0; retroid_variants[i].variant != VARIANT_NONE; i++) { + if (retroid_variants[i].variant == variant) + return &retroid_variants[i]; + } + return NULL; +} + +void PLAT_detectVariant(PlatformVariant* v) { + v->platform = PLATFORM; + v->has_hdmi = 1; + + // Read device from LessOS environment variable + char* device = getenv("LESSOS_DEVICE"); + if (!device) { + LOG_debug("LESSOS_DEVICE not set, defaulting to Pocket 5\n"); + device = "Retroid Pocket 5"; // Fallback to default + } else { + LOG_debug("LESSOS_DEVICE=%s\n", device); + } + + // Look up device in mapping table (substring match) + const DeviceVariantMap* map = NULL; + for (int i = 0; retroid_device_map[i].device_string != NULL; i++) { + if (containsString((char*)device, (char*)retroid_device_map[i].device_string)) { + LOG_debug("Matched device: %s (table entry: %s)\n", device, + retroid_device_map[i].device_string); + map = &retroid_device_map[i]; + break; + } + } + + // Fallback to default if not found + if (!map) { + LOG_warn("Unknown device '%s', defaulting to Pocket 5\n", device); + map = &retroid_device_map[0]; + } + + // Set device info + v->device = map->device; + v->variant = map->variant; + + // Apply variant configuration + const VariantConfig* config = getVariantConfig(map->variant); + if (config) { + v->screen_width = config->screen_width; + v->screen_height = config->screen_height; + v->screen_diagonal = + map->screen_diagonal > 0 ? map->screen_diagonal : config->screen_diagonal_default; + v->hw_features = config->hw_features; + } + + // Check for HDMI connection (runtime override) + v->hdmi_active = getInt(HDMI_STATE_PATH); + if (v->hdmi_active) { + v->screen_width = HDMI_WIDTH; + v->screen_height = HDMI_HEIGHT; + } + + LOG_info("Detected device: %s %s (%s variant, %dx%d, %.1f\")\n", v->device->manufacturer, + v->device->display_name, v->variant == VARIANT_RETROID_MINI_V2 ? "Mini V2" : "FHD", + v->screen_width, v->screen_height, v->screen_diagonal); +} + +/////////////////////////////// +// Video - Using shared SDL2 backend +/////////////////////////////// + +static SDL2_RenderContext vid_ctx; + +static SDL2_Config vid_config = { + // No rotation needed (landscape display) + .auto_rotate = 0, + .rotate_cw = 0, + .rotate_null_center = 0, + // Display features + .has_hdmi = 1, + .default_sharpness = SHARPNESS_SOFT, +}; + +SDL_Surface* PLAT_initVideo(void) { + // Detect device variant + PLAT_detectVariant(&platform_variant); + + // Use detected resolution (may be overridden by HDMI) + int w = FIXED_WIDTH; + int h = FIXED_HEIGHT; + vid_ctx.on_hdmi = platform_variant.hdmi_active; + + return SDL2_initVideo(&vid_ctx, w, h, &vid_config); +} + +void PLAT_quitVideo(void) { + SDL2_quitVideo(&vid_ctx); +} + +void PLAT_clearVideo(SDL_Surface* screen) { + SDL2_clearVideo(&vid_ctx); +} + +void PLAT_clearAll(void) { + SDL2_clearAll(&vid_ctx); +} + +SDL_Surface* PLAT_resizeVideo(int w, int h, int p) { + return SDL2_resizeVideo(&vid_ctx, w, h, p); +} + +void PLAT_setVideoScaleClip(int x, int y, int width, int height) { + // Not supported +} + +void PLAT_setNearestNeighbor(int enabled) { + // Not supported +} + +void PLAT_setSharpness(int sharpness) { + SDL2_setSharpness(&vid_ctx, sharpness); +} + +void PLAT_setEffect(int effect) { + // Only GL path is used on GLES platforms (SDL2 effect state is unused) + GLVideo_setEffect(effect); +} + +void PLAT_setEffectColor(int color) { + // Only GL path is used on GLES platforms (SDL2 effect state is unused) + GLVideo_setEffectColor(color); +} + +void PLAT_vsync(int remaining) { + SDL2_vsync(remaining); +} + +scaler_t PLAT_getScaler(GFX_Renderer* renderer) { + return SDL2_getScaler(&vid_ctx, renderer); +} + +void PLAT_present(GFX_Renderer* renderer) { + vid_ctx.on_hdmi = GetHDMI(); + SDL2_present(&vid_ctx, renderer); +} + +SDL_Window* PLAT_getWindow(void) { + return SDL2_getWindow(&vid_ctx); +} + +int PLAT_getRotation(void) { + return SDL2_getRotation(&vid_ctx); +} + +int PLAT_supportsOverscan(void) { + // Only Mini V2 with near-square aspect ratio benefits from overscan + return VARIANT_IS(VARIANT_RETROID_MINI_V2); +} + +/////////////////////////////// +// Input - Raw key codes from Retroid Pocket Gamepad driver +/////////////////////////////// + +// Button codes from retroid.c keymap[] (standard Linux BTN_* codes) +#define RAW_UP 544 // BTN_DPAD_UP +#define RAW_DOWN 545 // BTN_DPAD_DOWN +#define RAW_LEFT 546 // BTN_DPAD_LEFT +#define RAW_RIGHT 547 // BTN_DPAD_RIGHT +#define RAW_A 304 // BTN_SOUTH +#define RAW_B 305 // BTN_EAST +#define RAW_X 308 // BTN_WEST +#define RAW_Y 307 // BTN_NORTH +#define RAW_START 315 // BTN_START +#define RAW_SELECT 314 // BTN_SELECT +#define RAW_MENU 316 // BTN_MODE (Home button) +#define RAW_L1 310 // BTN_TL +#define RAW_R1 311 // BTN_TR +#define RAW_L3 317 // BTN_THUMBL +#define RAW_R3 318 // BTN_THUMBR +#define RAW_BACK 158 // BTN_BACK +#define RAW_PLUS 115 // KEY_VOLUMEUP +#define RAW_MINUS 114 // KEY_VOLUMEDOWN +#define RAW_POWER 116 // KEY_POWER + +// Analog axes +#define RAW_LSX 0 // ABS_X +#define RAW_LSY 1 // ABS_Y +#define RAW_RSX 3 // ABS_RX +#define RAW_RSY 4 // ABS_RY +#define RAW_L2 16 // ABS_HAT2X (analog trigger) +#define RAW_R2 17 // ABS_HAT2Y (analog trigger) + +// Treat Home, L3+R3 as menu buttons +#define RAW_MENU1 RAW_L3 +#define RAW_MENU2 RAW_R3 + +#define INPUT_COUNT 2 +static int inputs[INPUT_COUNT]; + +/** + * Find input device by name in sysfs. + * Returns device path (e.g., "/dev/input/event2") or NULL if not found. + */ +static char* findInputDeviceByName(const char* device_name) { + static char device_path[64]; + char name_path[128]; + char name[256]; + + // Search /sys/class/input/event* for matching device name + for (int i = 0; i < 10; i++) { + snprintf(name_path, sizeof(name_path), "/sys/class/input/event%d/device/name", i); + FILE* f = fopen(name_path, "r"); + if (!f) + continue; + + if (fgets(name, sizeof(name), f)) { + // Strip newline + name[strcspn(name, "\n")] = 0; + if (strcmp(name, device_name) == 0) { + snprintf(device_path, sizeof(device_path), "/dev/input/event%d", i); + fclose(f); + LOG_debug("Found '%s' at %s\n", device_name, device_path); + return device_path; + } + } + fclose(f); + } + + LOG_warn("Device '%s' not found in /sys/class/input/\n", device_name); + return NULL; +} + +void PLAT_initInput(void) { + // Find Retroid Pocket Gamepad dynamically by name + char* gamepad_path = findInputDeviceByName("Retroid Pocket Gamepad"); + if (gamepad_path) { + inputs[0] = open(gamepad_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (inputs[0] < 0) + LOG_warn("Failed to open gamepad at %s\n", gamepad_path); + } else { + // Fallback: try event0 (shouldn't happen on Retroid) + inputs[0] = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (inputs[0] < 0) + LOG_warn("Failed to open fallback /dev/input/event0\n"); + } + + // Power button and other system inputs (volume, power) + // Try event1 for power/volume buttons + inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (inputs[1] < 0) + LOG_debug("No secondary input device at event1 (power/volume buttons may not work)\n"); +} + +void PLAT_quitInput(void) { + for (int i = 0; i < INPUT_COUNT; i++) { + if (inputs[i] >= 0) + close(inputs[i]); + } +} + +// from which has BTN_ constants that conflict with platform.h +struct input_event { + struct timeval time; + __u16 type; + __u16 code; + __s32 value; +}; +#define EV_KEY 0x01 +#define EV_ABS 0x03 + +void PLAT_pollInput(void) { + uint32_t tick = SDL_GetTicks(); + PAD_beginPolling(); + PAD_handleRepeat(tick); + + int input; + static struct input_event event; + for (int i = 0; i < INPUT_COUNT; i++) { + input = inputs[i]; + if (input < 0) + continue; + while (read(input, &event, sizeof(event)) == sizeof(event)) { + if (event.type != EV_KEY && event.type != EV_ABS) + continue; + + int btn = BTN_NONE; + int pressed = 0; + int type = event.type; + int code = event.code; + int value = event.value; + + if (type == EV_KEY) { + if (value > 1) + continue; + + pressed = value; + if (code == RAW_UP) { + btn = BTN_DPAD_UP; + } else if (code == RAW_DOWN) { + btn = BTN_DPAD_DOWN; + } else if (code == RAW_LEFT) { + btn = BTN_DPAD_LEFT; + } else if (code == RAW_RIGHT) { + btn = BTN_DPAD_RIGHT; + } else if (code == RAW_A) { + btn = BTN_A; + } else if (code == RAW_B) { + btn = BTN_B; + } else if (code == RAW_X) { + btn = BTN_X; + } else if (code == RAW_Y) { + btn = BTN_Y; + } else if (code == RAW_START) { + btn = BTN_START; + } else if (code == RAW_SELECT) { + btn = BTN_SELECT; + } else if (code == RAW_MENU) { + btn = BTN_MENU; + } else if (code == RAW_MENU1) { + btn = BTN_MENU; + } else if (code == RAW_MENU2) { + btn = BTN_MENU; + } else if (code == RAW_L1) { + btn = BTN_L1; + } else if (code == RAW_R1) { + btn = BTN_R1; + } else if (code == RAW_L3) { + btn = BTN_L3; + } else if (code == RAW_R3) { + btn = BTN_R3; + } else if (code == RAW_PLUS) { + btn = BTN_PLUS; + } else if (code == RAW_MINUS) { + btn = BTN_MINUS; + } else if (code == RAW_POWER) { + btn = BTN_POWER; + } + } else if (type == EV_ABS) { + // Analog sticks - Retroid uses ~-1408 to +1408 range + if (code == RAW_LSX) { + pad.laxis.x = (value * 32767) / 1408; + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, pad.laxis.x, + tick + PAD_REPEAT_DELAY); + } else if (code == RAW_LSY) { + pad.laxis.y = (value * 32767) / 1408; + PAD_setAnalog(BTN_ID_ANALOG_UP, BTN_ID_ANALOG_DOWN, pad.laxis.y, + tick + PAD_REPEAT_DELAY); + } else if (code == RAW_RSX) { + pad.raxis.x = (value * 32767) / 1408; + } else if (code == RAW_RSY) { + pad.raxis.y = (value * 32767) / 1408; + } else if (code == RAW_L2) { + // Analog L2 trigger (0 to ~1552) + pressed = value > 100; // Threshold for digital press + btn = BTN_L2; + } else if (code == RAW_R2) { + // Analog R2 trigger (0 to ~1552) + pressed = value > 100; + btn = BTN_R2; + } + } + + PAD_updateButton(btn, pressed, tick); + } + } +} + +int PLAT_shouldWake(void) { + int input; + static struct input_event event; + for (int i = 0; i < INPUT_COUNT; i++) { + input = inputs[i]; + if (input < 0) + continue; + while (read(input, &event, sizeof(event)) == sizeof(event)) { + if (event.type == EV_KEY && event.code == RAW_POWER && event.value == 0) + return 1; + } + } + return 0; +} + +/////////////////////////////// +// Power Management +/////////////////////////////// + +static int online = 0; + +void PLAT_getBatteryStatus(int* is_charging, int* charge) { + // SM8250 uses standard power_supply class + *is_charging = getInt("/sys/class/power_supply/battery/status") == 2; // Charging status + + int i = getInt("/sys/class/power_supply/battery/capacity"); + if (i > 80) + *charge = 100; + else if (i > 60) + *charge = 80; + else if (i > 40) + *charge = 60; + else if (i > 20) + *charge = 40; + else if (i > 10) + *charge = 20; + else + *charge = 10; + + char status[16]; + getFile("/sys/class/net/wlan0/operstate", status, 16); + online = prefixMatch("up", status); +} + +#define BRIGHTNESS_PATH "/sys/class/backlight/panel0-backlight/brightness" +#define BLANK_PATH "/sys/class/graphics/fb0/blank" + +void PLAT_enableBacklight(int enable) { + if (enable) { + putInt(BLANK_PATH, FB_BLANK_UNBLANK); + SetBrightness(GetBrightness()); + } else { + putInt(BLANK_PATH, FB_BLANK_POWERDOWN); + SetRawBrightness(0); + } +} + +void PLAT_powerOff(void) { + sleep(2); + SetRawVolume(MUTE_VOLUME_RAW); + PLAT_enableBacklight(0); + SND_quit(); + VIB_quit(); + PWR_quit(); + GFX_quit(); + system("poweroff"); + while (1) + pause(); +} + +double PLAT_getDisplayHz(void) { + return SDL2_getDisplayHz(); +} + +uint32_t PLAT_measureVsyncInterval(void) { + return SDL2_measureVsyncInterval(&vid_ctx); +} + +#define GOVERNOR_PATH "/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed" + +/** + * Sets CPU frequency based on performance mode. + * + * Snapdragon 865 frequencies (Cortex-A77 prime core): + * Up to 2.84 GHz (prime), 2.4 GHz (performance), 1.8 GHz (efficiency) + * + * @param speed CPU_SPEED_* constant + */ +void PLAT_setCPUSpeed(int speed) { + int freq = 0; + switch (speed) { + case CPU_SPEED_IDLE: + freq = 300000; // 300 MHz (idle) + break; + case CPU_SPEED_POWERSAVE: + freq = 1056000; // ~1 GHz (powersave) + break; + case CPU_SPEED_NORMAL: + freq = 1804800; // ~1.8 GHz (normal) + break; + case CPU_SPEED_PERFORMANCE: + freq = 2419200; // ~2.4 GHz (performance) + break; + } + putInt(GOVERNOR_PATH, freq); +} + +/** + * Gets available CPU frequencies from sysfs. + */ +int PLAT_getAvailableCPUFrequencies(int* frequencies, int max_count) { + return PWR_getAvailableCPUFrequencies_sysfs(frequencies, max_count); +} + +/** + * Sets CPU frequency directly via sysfs. + */ +int PLAT_setCPUFrequency(int freq_khz) { + return PWR_setCPUFrequency_sysfs(freq_khz); +} + +#define RUMBLE_PATH "/sys/class/leds/vibrator/activate" + +void PLAT_setRumble(int strength) { + if (GetHDMI()) + return; + // pmi8998_haptics uses /sys/class/leds/vibrator interface + putInt(RUMBLE_PATH, strength ? 1 : 0); +} + +int PLAT_pickSampleRate(int requested, int max) { + return MIN(requested, max); +} + +char* PLAT_getModel(void) { + return (char*)PLAT_getDeviceName(); +} + +int PLAT_isOnline(void) { + return online; +} diff --git a/workspace/retroid/platform/platform.h b/workspace/retroid/platform/platform.h new file mode 100644 index 00000000..69da96d4 --- /dev/null +++ b/workspace/retroid/platform/platform.h @@ -0,0 +1,238 @@ +/** + * retroid/platform/platform.h - Platform definitions for Retroid Pocket SM8250 devices + * + * Supports multiple device variants in the Retroid Pocket family: + * - Retroid Pocket 5: 5.5" 1920x1080 AMOLED (16:9) + * - Retroid Pocket Flip 2: 5.5" 1920x1080 AMOLED (16:9, clamshell) + * - Retroid Pocket Mini V2: 3.92" 1240x1080 AMOLED (~31:27, near square) + * + * Hardware features: + * - Qualcomm Snapdragon 865 (SM8250) SoC + * - Adreno 650 GPU with Vulkan/OpenGL ES support + * - Dual analog sticks with hall effect sensors + * - Analog L2/R2 triggers + * - HDMI output support + * - WiFi 6 + Bluetooth 5.1 + * - Active cooling + * + * OS: LessOS (ROCKNIX-based) + * + * LessOS Environment Variables (provided at boot): + * - LESSOS_PLATFORM: SoC family ("SM8250") + * - LESSOS_DEVICE: Device model ("Retroid Pocket 5", "Retroid Pocket Mini V2", etc.) + * - LESSOS_STORAGE: Writable storage path ("/storage") + * + * See workspace/retroid/README.md for LessOS/ROCKNIX upstream references. + */ + +#ifndef PLATFORM_H +#define PLATFORM_H + +/////////////////////////////// +// Platform Identification +/////////////////////////////// + +#define PLATFORM "retroid" + +/////////////////////////////// +// Hardware Capabilities +/////////////////////////////// + +#define HAS_OPENGLES 1 // Adreno 650 GPU supports OpenGL ES 3.2 + +/////////////////////////////// +// Audio Configuration +/////////////////////////////// + +// Uses default SND_RATE_CONTROL_D (0.012f) for standard timing + +/////////////////////////////// +// Video Buffer Scaling +/////////////////////////////// + +// Uses default BUFFER_SCALE_FACTOR (1.0f) - GPU hardware scaler handles all scaling + +/////////////////////////////// +// UI Scaling +/////////////////////////////// + +// Runtime-configurable based on variant detection +// - FHD devices (5.5" screens): 1.0f scale with 10px padding +// - Mini V2 (3.92" screen): 1.0f scale with 10px padding (high DPI) +// Default to standard layout; both variants have good screen real estate +#define SCALE_MODIFIER 1.0f +#define EDGE_PADDING 10 + +/////////////////////////////// +// Dependencies +/////////////////////////////// + +#include "platform_variant.h" +#include "sdl.h" + +/////////////////////////////// +// Platform Variant Detection +/////////////////////////////// + +// Retroid Pocket SM8250 family variants +#define VARIANT_RETROID_FHD (VARIANT_PLATFORM_BASE + 0) // 1920x1080 (Pocket 5, Flip 2) +#define VARIANT_RETROID_MINI_V2 (VARIANT_PLATFORM_BASE + 1) // 1240x1080 (Mini V2) + +/////////////////////////////// +// SDL Keyboard Button Mappings +// Retroid uses evdev input, not SDL keyboard +/////////////////////////////// + +#define BUTTON_UP BUTTON_NA +#define BUTTON_DOWN BUTTON_NA +#define BUTTON_LEFT BUTTON_NA +#define BUTTON_RIGHT BUTTON_NA + +#define BUTTON_SELECT BUTTON_NA +#define BUTTON_START BUTTON_NA + +#define BUTTON_A BUTTON_NA +#define BUTTON_B BUTTON_NA +#define BUTTON_X BUTTON_NA +#define BUTTON_Y BUTTON_NA + +#define BUTTON_L1 BUTTON_NA +#define BUTTON_R1 BUTTON_NA +#define BUTTON_L2 BUTTON_NA +#define BUTTON_R2 BUTTON_NA +#define BUTTON_L3 BUTTON_NA +#define BUTTON_R3 BUTTON_NA + +#define BUTTON_MENU BUTTON_NA +#define BUTTON_MENU_ALT BUTTON_NA +#define BUTTON_POWER BUTTON_NA +#define BUTTON_PLUS BUTTON_NA +#define BUTTON_MINUS BUTTON_NA + +/////////////////////////////// +// Evdev/Keyboard Input Codes +// From Retroid Pocket Gamepad kernel driver (retroid.c) +/////////////////////////////// + +#define CODE_UP CODE_NA +#define CODE_DOWN CODE_NA +#define CODE_LEFT CODE_NA +#define CODE_RIGHT CODE_NA + +#define CODE_SELECT CODE_NA +#define CODE_START CODE_NA + +#define CODE_A CODE_NA +#define CODE_B CODE_NA +#define CODE_X CODE_NA +#define CODE_Y CODE_NA + +#define CODE_L1 CODE_NA +#define CODE_R1 CODE_NA +#define CODE_L2 CODE_NA +#define CODE_R2 CODE_NA +#define CODE_L3 CODE_NA +#define CODE_R3 CODE_NA + +#define CODE_MENU CODE_NA +#define CODE_MENU_ALT CODE_NA +#define CODE_POWER 116 // KEY_POWER + +#define CODE_PLUS 115 // KEY_VOLUMEUP +#define CODE_MINUS 114 // KEY_VOLUMEDOWN + +/////////////////////////////// +// Joystick Button Mappings +// From Retroid Pocket Gamepad kernel driver keymap[] +// Uses standard Linux BTN_* codes +/////////////////////////////// + +#define JOY_UP 13 // BTN_DPAD_UP (bit 0 in keymap) +#define JOY_DOWN 14 // BTN_DPAD_DOWN (bit 1) +#define JOY_LEFT 15 // BTN_DPAD_LEFT (bit 2) +#define JOY_RIGHT 16 // BTN_DPAD_RIGHT (bit 3) + +#define JOY_SELECT 10 // BTN_SELECT (bit 10) +#define JOY_START 11 // BTN_START (bit 11) + +// Retroid uses Xbox-style layout: A=South, B=East, X=West, Y=North +#define JOY_A 7 // BTN_SOUTH (bit 7) +#define JOY_B 6 // BTN_EAST (bit 6) +#define JOY_X 5 // BTN_WEST (bit 5) +#define JOY_Y 4 // BTN_NORTH (bit 4) + +#define JOY_L1 8 // BTN_TL (bit 8) +#define JOY_R1 9 // BTN_TR (bit 9) +#define JOY_L2 JOY_NA // Analog trigger, handled via ABS_HAT2X +#define JOY_R2 JOY_NA // Analog trigger, handled via ABS_HAT2Y +#define JOY_L3 12 // BTN_THUMBL (bit 12) +#define JOY_R3 13 // BTN_THUMBR (bit 13) + +#define JOY_MENU 14 // BTN_MODE (bit 14) - Home button +#define JOY_MENU_ALT JOY_NA +#define JOY_POWER JOY_NA +#define JOY_PLUS JOY_NA +#define JOY_MINUS JOY_NA + +/////////////////////////////// +// Function Button Mappings +// System-level button combinations +/////////////////////////////// + +#define BTN_RESUME BTN_X // Button to resume from save state +#define BTN_SLEEP BTN_POWER // Button to enter sleep mode +#define BTN_WAKE BTN_POWER // Button to wake from sleep +#define BTN_MOD_VOLUME BTN_NONE // Modifier for volume control (none - direct buttons) +#define BTN_MOD_BRIGHTNESS BTN_MENU // Hold MENU for brightness control +#define BTN_MOD_PLUS BTN_PLUS // Increase with PLUS +#define BTN_MOD_MINUS BTN_MINUS // Decrease with MINUS + +/////////////////////////////// +// Display Specifications +// Runtime-configurable for device variants +/////////////////////////////// + +#define SCREEN_DIAGONAL (platform_variant.screen_diagonal) +#define FIXED_WIDTH (platform_variant.screen_width) +#define FIXED_HEIGHT (platform_variant.screen_height) + +/////////////////////////////// +// HDMI Output Specifications +/////////////////////////////// + +#define HAS_HDMI 1 // HDMI output supported via USB-C DisplayPort +#define HDMI_WIDTH 1920 // HDMI width in pixels +#define HDMI_HEIGHT 1080 // HDMI height in pixels (1080p) + +/////////////////////////////// +// Platform-Specific Paths and Settings +/////////////////////////////// + +#define SDCARD_PATH "/storage" // LessOS default, overridden by LESSOS_STORAGE +#define MUTE_VOLUME_RAW 0 // Raw value for muted volume + +/////////////////////////////// +// Keymon Configuration +/////////////////////////////// + +#define KEYMON_BUTTON_MENU 316 // BTN_MODE (Home button) +#define KEYMON_BUTTON_MENU_ALT -1 // Not used +#define KEYMON_BUTTON_PLUS 115 // KEY_VOLUMEUP +#define KEYMON_BUTTON_MINUS 114 // KEY_VOLUMEDOWN + +#define KEYMON_HAS_HDMI 1 +#define KEYMON_HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" + +#define KEYMON_HAS_JACK 0 // Headphone jack detection TBD + +// Retroid gamepad device path is dynamic - search multiple event devices +// The "Retroid Pocket Gamepad" can be on different eventN depending on boot order +#define KEYMON_INPUT_COUNT 4 +#define KEYMON_INPUT_DEVICE_0 "/dev/input/event0" +#define KEYMON_INPUT_DEVICE_1 "/dev/input/event1" +#define KEYMON_INPUT_DEVICE_2 "/dev/input/event2" +#define KEYMON_INPUT_DEVICE_3 "/dev/input/event3" + +/////////////////////////////// + +#endif diff --git a/workspace/retroid/show/Makefile b/workspace/retroid/show/Makefile new file mode 100755 index 00000000..03195e74 --- /dev/null +++ b/workspace/retroid/show/Makefile @@ -0,0 +1,14 @@ +ifeq (,$(CROSS_COMPILE)) +$(error missing CROSS_COMPILE for this toolchain) +endif + +TARGET = show +PRODUCT = $(TARGET).elf + +CC = $(CROSS_COMPILE)gcc +FLAGS = -Os $(CFLAGS) $(LDFLAGS) -lSDL2 -lSDL2_image -lrt -ldl -Wl,--gc-sections -s + +all: + $(CC) $(TARGET).c -o $(PRODUCT) $(FLAGS) +clean: + rm -rf $(PRODUCT) \ No newline at end of file diff --git a/workspace/retroid/show/show.c b/workspace/retroid/show/show.c new file mode 100644 index 00000000..debbecd8 --- /dev/null +++ b/workspace/retroid/show/show.c @@ -0,0 +1,83 @@ +// rgb30 - Display an image on screen during boot/install/update +#include +#include +#include +#include +#include +#include + +SDL_Window* window; +SDL_Surface* screen; + +int main(int argc, char* argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: show.elf image.png [delay]\n"); + return 1; + } + + char path[256]; + snprintf(path, sizeof(path), "%s", argv[1]); + if (access(path, F_OK) != 0) { + fprintf(stderr, "show.elf: Image not found: %s\n", path); + return 1; + } + + int delay = argc > 2 ? atoi(argv[2]) : 2; + + fprintf(stderr, "show.elf: Initializing SDL2...\n"); + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "show.elf: SDL_Init failed: %s\n", SDL_GetError()); + return 1; + } + + SDL_ShowCursor(0); + + // Use 0,0 to let SDL auto-detect display size (like tg5040) + fprintf(stderr, "show.elf: Creating window...\n"); + window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_SHOWN); + if (!window) { + fprintf(stderr, "show.elf: SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + screen = SDL_GetWindowSurface(window); + if (!screen) { + fprintf(stderr, "show.elf: SDL_GetWindowSurface failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + fprintf(stderr, "show.elf: Window size: %dx%d\n", screen->w, screen->h); + SDL_FillRect(screen, NULL, 0); + + fprintf(stderr, "show.elf: Loading image: %s\n", path); + SDL_Surface* img = IMG_Load(path); + if (!img) { + fprintf(stderr, "show.elf: IMG_Load failed: %s\n", IMG_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + fprintf(stderr, "show.elf: Image size: %dx%d\n", img->w, img->h); + + // Center the image on screen + SDL_Rect dst = {(screen->w - img->w) / 2, (screen->h - img->h) / 2, img->w, img->h}; + SDL_BlitSurface(img, NULL, screen, &dst); + + if (SDL_UpdateWindowSurface(window) < 0) { + fprintf(stderr, "show.elf: SDL_UpdateWindowSurface failed: %s\n", SDL_GetError()); + } + + fprintf(stderr, "show.elf: Displaying for %d seconds...\n", delay); + sleep(delay); + + SDL_FreeSurface(img); + SDL_DestroyWindow(window); + SDL_Quit(); + + fprintf(stderr, "show.elf: Done\n"); + return 0; +} From 425c68f339bbdd1f661d8c881f3a9bc0cf16ca6e Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 17:12:00 -0800 Subject: [PATCH 02/10] Update cores to 20260101-2. --- .../BASE/Roms/Nintendo 64 (N64)/about.txt | 8 ++++ .../Roms/PlayStation Portable (PSP)/about.txt | 8 ++++ .../BASE/Roms/Sega Dreamcast (DC)/about.txt | 14 +++++++ workspace/all/paks/Emus/Makefile | 2 +- .../paks/Emus/configs/base/SWAN/default.cfg | 18 +++++++++ workspace/all/paks/Emus/cores.json | 37 +++++++++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 skeleton/BASE/Roms/Nintendo 64 (N64)/about.txt create mode 100644 skeleton/BASE/Roms/PlayStation Portable (PSP)/about.txt create mode 100644 skeleton/BASE/Roms/Sega Dreamcast (DC)/about.txt create mode 100644 workspace/all/paks/Emus/configs/base/SWAN/default.cfg diff --git a/skeleton/BASE/Roms/Nintendo 64 (N64)/about.txt b/skeleton/BASE/Roms/Nintendo 64 (N64)/about.txt new file mode 100644 index 00000000..07bcfed4 --- /dev/null +++ b/skeleton/BASE/Roms/Nintendo 64 (N64)/about.txt @@ -0,0 +1,8 @@ +Nintendo 64 + +Core: mupen64plus_next + +Supported file types: + .z64, .n64, .v64, .zip, .7z + +ARM64 devices only diff --git a/skeleton/BASE/Roms/PlayStation Portable (PSP)/about.txt b/skeleton/BASE/Roms/PlayStation Portable (PSP)/about.txt new file mode 100644 index 00000000..957d1e6d --- /dev/null +++ b/skeleton/BASE/Roms/PlayStation Portable (PSP)/about.txt @@ -0,0 +1,8 @@ +PlayStation Portable + +Core: ppsspp + +Supported file types: + .elf, .iso, .cso, .prx, .pbp + +ARM64 devices only diff --git a/skeleton/BASE/Roms/Sega Dreamcast (DC)/about.txt b/skeleton/BASE/Roms/Sega Dreamcast (DC)/about.txt new file mode 100644 index 00000000..744446a6 --- /dev/null +++ b/skeleton/BASE/Roms/Sega Dreamcast (DC)/about.txt @@ -0,0 +1,14 @@ +Sega Dreamcast + +Core: flycast + +Supported file types: + .chd, .cdi, .iso, .elf, .bin, .cue, .gdi, .lst, .zip, .7z, .dat + +BIOS (required): + dc_boot.bin - Boot ROM (required) + dc_flash.bin - Flash ROM (optional) + +Place BIOS files in: /Bios/dc/ + +ARM64 devices only diff --git a/workspace/all/paks/Emus/Makefile b/workspace/all/paks/Emus/Makefile index fe61d47c..e8a8378c 100644 --- a/workspace/all/paks/Emus/Makefile +++ b/workspace/all/paks/Emus/Makefile @@ -9,7 +9,7 @@ # clean - Remove downloaded cores ########################################################### -PLAYER_CORES_VERSION ?= 20251228-2 +PLAYER_CORES_VERSION ?= 20260101-2 CORES_BASE = https://github.com/lessui-hq/LessUI-Cores/releases/download/$(PLAYER_CORES_VERSION) # All core management happens in cores/ directory diff --git a/workspace/all/paks/Emus/configs/base/SWAN/default.cfg b/workspace/all/paks/Emus/configs/base/SWAN/default.cfg new file mode 100644 index 00000000..a1a8c25a --- /dev/null +++ b/workspace/all/paks/Emus/configs/base/SWAN/default.cfg @@ -0,0 +1,18 @@ +-swanstation_Display_ShowOSDMessages = false +-swanstation_Display_ShowFPS = false + +player_gamepad_type = 1 +bind Up = UP +bind Down = DOWN +bind Left = LEFT +bind Right = RIGHT +bind Select = SELECT +bind Start = START +bind Circle = A +bind Cross = B +bind Triangle = X +bind Square = Y +bind L1 Button = L1 +bind R1 Button = R1 +bind L2 Button = L2 +bind R2 Button = R2 diff --git a/workspace/all/paks/Emus/cores.json b/workspace/all/paks/Emus/cores.json index ad4ea812..8aaa9ccd 100644 --- a/workspace/all/paks/Emus/cores.json +++ b/workspace/all/paks/Emus/cores.json @@ -102,6 +102,43 @@ } ] }, + "SWAN": { + "name": "Sony PlayStation (SwanStation)", + "core": "swanstation_libretro", + "resolution": "320x240", + "extensions": [ + "bin", + "cue", + "img", + "iso", + "pbp", + "chd", + "m3u", + "mdf", + "ccd", + "exe" + ], + "bios": [ + { + "name": "scph5501.bin", + "md5": "490f666e1afb15b7362b406ed1cea246", + "required": true + }, + { "name": "scph5500.bin", "md5": null, "required": false }, + { "name": "scph5502.bin", "md5": null, "required": false }, + { + "name": "scph1001.bin", + "md5": "924e392ed05558ffdb115408c263dccf", + "required": false + }, + { + "name": "scph7001.bin", + "md5": "1e68c231d0896b7eadcad1d7d8e76129", + "required": false + } + ], + "arm64_only": true + }, "GG": { "name": "Sega Game Gear", "core": "picodrive_libretro", From 9ee91af3ffcd820b0825bc9320df1324f97ecfc8 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 18:58:36 -0800 Subject: [PATCH 03/10] Fix Retroid platform: buttons, brightness, and LEDs. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix button mapping to Nintendo layout (swap A/B and X/Y) - Fix brightness control path (panel0-backlight → ae94000.dsi.0) - Fix screen blanking during fake standby - Disable analog stick RGB LEDs on startup - Add logging to msettings via LOG_* macros - Increase keymon input device monitoring (event0-3 → event0-7) - Fix analog axis overflow with clamping - Remove dead L3/R3 button mappings --- workspace/all/utils/keymon/keymon.c | 11 + workspace/all/utils/keymon/keymon.c.bak | 612 +++++++++++++++++++ workspace/retroid/libmsettings/Makefile | 8 +- workspace/retroid/libmsettings/msettings.c | 33 +- workspace/retroid/platform/Makefile.env | 2 +- workspace/retroid/platform/platform.c | 85 ++- workspace/retroid/platform/platform.c.bak | 669 +++++++++++++++++++++ workspace/retroid/platform/platform.h | 23 +- 8 files changed, 1395 insertions(+), 48 deletions(-) create mode 100644 workspace/all/utils/keymon/keymon.c.bak create mode 100644 workspace/retroid/platform/platform.c.bak diff --git a/workspace/all/utils/keymon/keymon.c b/workspace/all/utils/keymon/keymon.c index 8200216f..729e9d04 100644 --- a/workspace/all/utils/keymon/keymon.c +++ b/workspace/all/utils/keymon/keymon.c @@ -345,6 +345,12 @@ int main(int argc, char* argv[]) { #if KEYMON_INPUT_COUNT > 5 && defined(KEYMON_INPUT_DEVICE_5) inputs[5] = open(KEYMON_INPUT_DEVICE_5, O_RDONLY | O_NONBLOCK | O_CLOEXEC); #endif +#if KEYMON_INPUT_COUNT > 6 && defined(KEYMON_INPUT_DEVICE_6) + inputs[6] = open(KEYMON_INPUT_DEVICE_6, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 7 && defined(KEYMON_INPUT_DEVICE_7) + inputs[7] = open(KEYMON_INPUT_DEVICE_7, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif #else // Sequential device paths (default) for (int i = 0; i < KEYMON_INPUT_COUNT; i++) { @@ -444,12 +450,14 @@ int main(int argc, char* argv[]) { case KEYMON_BUTTON_R1: // R1 button (brightness/volume up when combined) up_pressed = up_just_pressed = pressed; + if (pressed) up_repeat_at = now + 300; // 300ms initial delay break; case KEYMON_BUTTON_L1: // L1 button (brightness/volume down when combined) down_pressed = down_just_pressed = pressed; + if (pressed) down_repeat_at = now + 300; // 300ms initial delay break; @@ -470,14 +478,17 @@ int main(int argc, char* argv[]) { case KEYMON_BUTTON_MENU_ALT2: #endif menu_pressed = pressed; + break; case KEYMON_BUTTON_PLUS: up_pressed = up_just_pressed = pressed; + if (pressed) up_repeat_at = now + 300; break; case KEYMON_BUTTON_MINUS: down_pressed = down_just_pressed = pressed; + if (pressed) down_repeat_at = now + 300; break; diff --git a/workspace/all/utils/keymon/keymon.c.bak b/workspace/all/utils/keymon/keymon.c.bak new file mode 100644 index 00000000..d5af22cb --- /dev/null +++ b/workspace/all/utils/keymon/keymon.c.bak @@ -0,0 +1,612 @@ +/** + * keymon.c - Unified hardware button monitoring daemon + * + * Background daemon that monitors physical button presses and handles system-level + * shortcuts across all supported handheld devices. + * + * Features (platform-dependent, configured via keymon_config.h): + * - Volume and brightness control through button combinations + * - HDMI output detection and routing + * - Headphone jack detection and routing + * - Multiple input device support + * + * Button combinations (most platforms): + * - MENU+PLUS/MINUS: Adjust brightness + * - PLUS/MINUS alone: Adjust volume + * + * Alternative (trimuismart): + * - START+R1/L1: Adjust brightness + * - SELECT+R1/L1: Adjust volume + * + * Runs continuously at 60Hz polling input devices for button events. + * Implements repeat functionality (initial 300ms delay, then 100ms interval). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platform.h" +#include "keymon_config.h" +#include "log.h" +#include + +#if KEYMON_HAS_HDMI || KEYMON_HAS_JACK || defined(KEYMON_HAS_MUTE) +#include +#endif + +// Input event values from linux/input.h +#define RELEASED 0 +#define PRESSED 1 +#define REPEAT 2 + +// Shutdown flag for clean exit +static volatile sig_atomic_t running = 1; + +/** + * Signal handler for clean shutdown. + */ +static void handle_signal(int sig) { + (void)sig; + running = 0; +} + +// Input device management +#if KEYMON_INPUT_COUNT > 1 +static int inputs[KEYMON_INPUT_COUNT]; +#else +static int input_fd = 0; +#endif + +static struct input_event ev; + +#if KEYMON_HAS_HDMI || KEYMON_HAS_JACK +static pthread_t ports_pt; +#endif + +#ifdef KEYMON_HAS_MUTE +static pthread_t mute_pt; +#endif + +/** + * Reads an integer value from a sysfs file. + * + * Used for reading hardware state from kernel interfaces. + * + * @param path Path to sysfs file + * @return Integer value read from file, or 0 if file cannot be opened + */ +static int getInt(char* path) { + int i = 0; + FILE* file = fopen(path, "r"); + if (file != NULL) { + fscanf(file, "%i", &i); + fclose(file); + } + return i; +} + +#if KEYMON_HAS_HDMI && defined(KEYMON_HDMI_USE_STRING) +/** + * Reads a text file into a buffer. + * + * Used for HDMI status detection on some platforms. + * + * @param path Path to file + * @param buffer Output buffer + * @param buffer_size Size of output buffer + */ +static void getFile(char* path, char* buffer, size_t buffer_size) { + buffer[0] = '\0'; // Initialize in case fopen fails + FILE* file = fopen(path, "r"); + if (file) { + fseek(file, 0L, SEEK_END); + size_t size = ftell(file); + if (size > buffer_size - 1) + size = buffer_size - 1; + rewind(file); + fread(buffer, sizeof(char), size, file); + fclose(file); + buffer[size] = '\0'; + } +} + +/** + * Checks if two strings are exactly equal. + * + * @param str1 First string + * @param str2 Second string + * @return 1 if strings match, 0 otherwise + */ +static int exactMatch(char* str1, char* str2) { + size_t len1 = strlen(str1); + if (len1 != strlen(str2)) + return 0; + return (strncmp(str1, str2, len1) == 0); +} +#endif + +#if KEYMON_HAS_JACK +/** + * Checks if headphones are plugged in. + * + * Logic depends on platform (some use inverted GPIO values). + * + * @return 1 if headphones connected, 0 otherwise + */ +static int JACK_enabled(void) { +#ifdef KEYMON_JACK_INVERTED + return !getInt(KEYMON_JACK_STATE_PATH); +#else + return getInt(KEYMON_JACK_STATE_PATH); +#endif +} +#endif + +#if KEYMON_HAS_HDMI +/** + * Checks if HDMI is connected. + * + * Implementation varies by platform: + * - extcon interface: Read integer value + * - DRM connector: Read "connected\n" string + * + * @return 1 if HDMI connected, 0 otherwise + */ +static int HDMI_enabled(void) { +#ifdef KEYMON_HDMI_USE_STRING + char value[64]; + getFile(KEYMON_HDMI_STATE_PATH, value, 64); + return exactMatch(value, "connected\n"); +#else + return getInt(KEYMON_HDMI_STATE_PATH); +#endif +} +#endif + +#if KEYMON_HAS_HDMI && KEYMON_HAS_JACK +/** + * Background thread that monitors headphone jack and HDMI state. + * + * Polls the hardware interfaces every second and updates audio/video + * routing when states change. + * + * @param arg Thread argument (unused) + * @return Never returns (runs infinite loop) + */ +static void* watchPorts(void* arg) { + int has_jack, had_jack; + int has_hdmi, had_hdmi; + + // Initialize state + has_jack = had_jack = JACK_enabled(); + has_hdmi = had_hdmi = HDMI_enabled(); + SetJack(has_jack); + SetHDMI(has_hdmi); + + while (1) { + sleep(1); + + // Check for headphone jack state changes + has_jack = JACK_enabled(); + if (had_jack != has_jack) { + had_jack = has_jack; + SetJack(has_jack); + } + + // Check for HDMI state changes + has_hdmi = HDMI_enabled(); + if (had_hdmi != has_hdmi) { + had_hdmi = has_hdmi; + SetHDMI(has_hdmi); + } + } + + return 0; +} +#elif KEYMON_HAS_HDMI +/** + * Background thread that monitors HDMI state only. + * + * @param arg Thread argument (unused) + * @return Never returns (runs infinite loop) + */ +static void* watchHDMI(void* arg) { + int has_hdmi, had_hdmi; + + // Initialize HDMI state + has_hdmi = had_hdmi = HDMI_enabled(); + SetHDMI(has_hdmi); + + while (1) { + sleep(1); + + // Check for HDMI state changes + has_hdmi = HDMI_enabled(); + if (had_hdmi != has_hdmi) { + had_hdmi = has_hdmi; + SetHDMI(has_hdmi); + } + } + + return 0; +} +#elif KEYMON_HAS_JACK +/** + * Background thread that monitors headphone jack state only. + * + * @param arg Thread argument (unused) + * @return Never returns (runs infinite loop) + */ +static void* watchJack(void* arg) { + int has_jack, had_jack; + + // Initialize jack state + has_jack = had_jack = JACK_enabled(); + SetJack(has_jack); + + while (1) { + sleep(1); + + // Check for jack state changes + has_jack = JACK_enabled(); + if (had_jack != has_jack) { + had_jack = has_jack; + SetJack(has_jack); + } + } + + return 0; +} +#endif + +#ifdef KEYMON_HAS_MUTE +/** + * Background thread that monitors mute switch state (tg5040). + * + * Polls the GPIO mute switch every second and updates audio routing. + * + * @param arg Thread argument (unused) + * @return Never returns (runs infinite loop) + */ +static void* watchMute(void* arg) { + int is_muted, was_muted; + + // Initialize mute state + is_muted = was_muted = getInt(KEYMON_MUTE_STATE_PATH); + SetMute(is_muted); + + while (1) { + sleep(1); + + // Check for mute state changes + is_muted = getInt(KEYMON_MUTE_STATE_PATH); + if (was_muted != is_muted) { + was_muted = is_muted; + SetMute(is_muted); + } + } + + return 0; +} +#endif + +/** + * Main event loop for hardware button monitoring. + * + * Continuously polls input device(s) for button events and handles: + * - Volume control (PLUS/MINUS buttons) + * - Brightness control (MENU+PLUS/MINUS or START+R1/L1) + * + * Implements repeat functionality (initial 300ms delay, then 100ms interval) + * and ignores stale input after system sleep (> 1 second gap). + * + * @param argc Argument count (unused) + * @param argv Argument values (unused) + * @return 0 on clean shutdown (SIGTERM/SIGINT) + */ +int main(int argc, char* argv[]) { + // Initialize logging (reads LOG_FILE environment variable) + log_open(NULL); + + // Register signal handlers for clean shutdown + signal(SIGTERM, handle_signal); + signal(SIGINT, handle_signal); + + InitSettings(); + + // Open input device(s) +#if KEYMON_INPUT_COUNT > 1 + // Custom device paths (if platform defines them) +#if defined(KEYMON_INPUT_DEVICE_0) + inputs[0] = open(KEYMON_INPUT_DEVICE_0, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#if KEYMON_INPUT_COUNT > 1 && defined(KEYMON_INPUT_DEVICE_1) + inputs[1] = open(KEYMON_INPUT_DEVICE_1, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 2 && defined(KEYMON_INPUT_DEVICE_2) + inputs[2] = open(KEYMON_INPUT_DEVICE_2, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 3 && defined(KEYMON_INPUT_DEVICE_3) + inputs[3] = open(KEYMON_INPUT_DEVICE_3, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 4 && defined(KEYMON_INPUT_DEVICE_4) + inputs[4] = open(KEYMON_INPUT_DEVICE_4, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 5 && defined(KEYMON_INPUT_DEVICE_5) + inputs[5] = open(KEYMON_INPUT_DEVICE_5, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 6 && defined(KEYMON_INPUT_DEVICE_6) + inputs[6] = open(KEYMON_INPUT_DEVICE_6, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#if KEYMON_INPUT_COUNT > 7 && defined(KEYMON_INPUT_DEVICE_7) + inputs[7] = open(KEYMON_INPUT_DEVICE_7, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif +#else + // Sequential device paths (default) + for (int i = 0; i < KEYMON_INPUT_COUNT; i++) { + char path[32]; + snprintf(path, sizeof(path), "/dev/input/event%d", i); + inputs[i] = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + } +#endif +#else + input_fd = open(KEYMON_INPUT_DEVICE, O_RDONLY | O_NONBLOCK | O_CLOEXEC); +#endif + + // Start hardware monitoring threads if enabled +#if KEYMON_HAS_HDMI && KEYMON_HAS_JACK + pthread_create(&ports_pt, NULL, watchPorts, NULL); +#elif KEYMON_HAS_HDMI + pthread_create(&ports_pt, NULL, watchHDMI, NULL); +#elif KEYMON_HAS_JACK + pthread_create(&ports_pt, NULL, watchJack, NULL); +#endif + +#ifdef KEYMON_HAS_MUTE + pthread_create(&mute_pt, NULL, watchMute, NULL); +#endif + + uint32_t pressed; // Button state: PRESSED (1) or RELEASED (0) + + // Button state tracking +#if KEYMON_USE_SELECT_START + uint32_t start_pressed = 0; + uint32_t select_pressed = 0; +#else + uint32_t menu_pressed = 0; +#endif + + // Track PLUS button state for repeat handling + uint32_t up_pressed = 0; + uint32_t up_just_pressed = 0; + uint32_t up_repeat_at = 0; + + // Track MINUS button state for repeat handling + uint32_t down_pressed = 0; + uint32_t down_just_pressed = 0; + uint32_t down_repeat_at = 0; + + uint32_t now; + uint32_t then = 0; + uint8_t ignore_stale = 0; + struct timeval tod; + + gettimeofday(&tod, NULL); + then = tod.tv_sec * 1000 + tod.tv_usec / 1000; + + while (running) { + gettimeofday(&tod, NULL); + now = tod.tv_sec * 1000 + tod.tv_usec / 1000; + + // Detect stale input after system sleep (> 1 second gap) + if (now - then > 1000) + ignore_stale = 1; + + // Read and process all available input events +#if KEYMON_INPUT_COUNT > 1 + for (int i = 0; i < KEYMON_INPUT_COUNT; i++) { + while (read(inputs[i], &ev, sizeof(ev)) == sizeof(ev)) { +#else + while (read(input_fd, &ev, sizeof(ev)) == sizeof(ev)) { +#endif + // Skip stale input after system sleep + if (ignore_stale) + continue; + + // Process key events + if (ev.type == EV_KEY) { + pressed = ev.value; + } +#ifdef KEYMON_HAS_JACK_SWITCH + // Process switch events for headphone jack (tg5040, zero28) + else if (ev.type == EV_SW && ev.code == 2) { // SW_HEADPHONE_INSERT + SetJack(ev.value); + continue; + } +#endif + else { + continue; // Skip non-key/non-switch events + } + + // Process hardware button events + switch (ev.code) { +#if KEYMON_USE_SELECT_START + case KEYMON_BUTTON_START: + start_pressed = pressed; + break; + case KEYMON_BUTTON_SELECT: + select_pressed = pressed; + break; + case KEYMON_BUTTON_R1: + // R1 button (brightness/volume up when combined) + up_pressed = up_just_pressed = pressed; + LOG_debug("PLUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); + if (pressed) + up_repeat_at = now + 300; // 300ms initial delay + break; + case KEYMON_BUTTON_L1: + // L1 button (brightness/volume down when combined) + down_pressed = down_just_pressed = pressed; + LOG_debug("MINUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); + if (pressed) + down_repeat_at = now + 300; // 300ms initial delay + break; +#ifdef KEYMON_HAS_VOLUME_QUIRK + // m17: Hardware volume buttons trigger quirk (re-apply volume) + case KEYMON_BUTTON_PLUS: + case KEYMON_BUTTON_MINUS: + system("echo 0 > /sys/devices/platform/0gpio-keys/scaled"); + SetVolume(GetVolume()); + break; +#endif +#else + case KEYMON_BUTTON_MENU: +#if KEYMON_BUTTON_MENU_ALT != -1 + case KEYMON_BUTTON_MENU_ALT: +#endif +#ifdef KEYMON_BUTTON_MENU_ALT2 + case KEYMON_BUTTON_MENU_ALT2: +#endif + menu_pressed = pressed; + LOG_debug("MENU button: %s", pressed ? "pressed" : "released"); + break; + case KEYMON_BUTTON_PLUS: + up_pressed = up_just_pressed = pressed; + LOG_debug("PLUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); + if (pressed) + up_repeat_at = now + 300; + break; + case KEYMON_BUTTON_MINUS: + down_pressed = down_just_pressed = pressed; + LOG_debug("MINUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); + if (pressed) + down_repeat_at = now + 300; + break; +#endif + default: + break; + } +#if KEYMON_INPUT_COUNT > 1 + } +#endif + } + + // Reset button state after ignoring stale input + if (ignore_stale) { +#if KEYMON_USE_SELECT_START + start_pressed = 0; + select_pressed = 0; +#else + menu_pressed = 0; +#endif + up_pressed = up_just_pressed = 0; + down_pressed = down_just_pressed = 0; + up_repeat_at = 0; + down_repeat_at = 0; + } + + // Update timing for next iteration + then = now; + ignore_stale = 0; + + // Handle PLUS/R1 button (initial press or repeat after delay) + if (up_just_pressed || (up_pressed && now >= up_repeat_at)) { + uint32_t level; +#if KEYMON_USE_SELECT_START + if (start_pressed) { + // START+R1: Brightness up + level = GetBrightness(); + if (level < KEYMON_BRIGHTNESS_MAX) + SetBrightness(level + 1); + } else if (select_pressed) { + // SELECT+R1: Volume up + level = GetVolume(); + if (level < KEYMON_VOLUME_MAX) + SetVolume(level + 1); + } +#else + if (menu_pressed) { + // MENU+PLUS: Brightness up + level = GetBrightness(); + if (level < KEYMON_BRIGHTNESS_MAX) + SetBrightness(level + 1); + } else { + // PLUS alone: Volume up +#ifdef KEYMON_HAS_VOLUME_QUIRK + // m17 hardware volume button quirk + system("echo 0 > /sys/devices/platform/0gpio-keys/scaled"); + SetVolume(GetVolume()); +#else + level = GetVolume(); + if (level < KEYMON_VOLUME_MAX) + SetVolume(level + 1); +#endif + } +#endif + + if (up_just_pressed) + up_just_pressed = 0; + else + up_repeat_at += 100; // 100ms repeat interval + } + + // Handle MINUS/L1 button (initial press or repeat after delay) + if (down_just_pressed || (down_pressed && now >= down_repeat_at)) { + uint32_t level; +#if KEYMON_USE_SELECT_START + if (start_pressed) { + // START+L1: Brightness down + level = GetBrightness(); + if (level > KEYMON_BRIGHTNESS_MIN) + SetBrightness(level - 1); + } else if (select_pressed) { + // SELECT+L1: Volume down + level = GetVolume(); + if (level > KEYMON_VOLUME_MIN) + SetVolume(level - 1); + } +#else + if (menu_pressed) { + // MENU+MINUS: Brightness down + level = GetBrightness(); + if (level > KEYMON_BRIGHTNESS_MIN) + SetBrightness(level - 1); + } else { + // MINUS alone: Volume down +#ifdef KEYMON_HAS_VOLUME_QUIRK + // m17 hardware volume button quirk + system("echo 0 > /sys/devices/platform/0gpio-keys/scaled"); + SetVolume(GetVolume()); +#else + level = GetVolume(); + if (level > KEYMON_VOLUME_MIN) + SetVolume(level - 1); +#endif + } +#endif + + if (down_just_pressed) + down_just_pressed = 0; + else + down_repeat_at += 100; // 100ms repeat interval + } + + usleep(16666); // 60Hz polling rate + } + + // Clean shutdown + QuitSettings(); + log_close(); + + return 0; +} diff --git a/workspace/retroid/libmsettings/Makefile b/workspace/retroid/libmsettings/Makefile index bc879d7b..eea09baa 100644 --- a/workspace/retroid/libmsettings/Makefile +++ b/workspace/retroid/libmsettings/Makefile @@ -15,14 +15,16 @@ CC = $(CROSS_COMPILE)gcc SYSROOT := $(shell $(CC) --print-sysroot) INCLUDEDIR = $(SYSROOT)/usr/include -# OPT_FLAGS from parent makefile (-O3 for release, -O0 -g for debug) +# OPT_FLAGS and LOG_FLAGS from parent makefile OPT_FLAGS ?= -O3 -CFLAGS = -I$(INCLUDEDIR) $(OPT_FLAGS) +LOG_FLAGS ?= +CFLAGS = -I$(INCLUDEDIR) -I../../all/common $(OPT_FLAGS) $(LOG_FLAGS) LDFLAGS = -ldl -lrt -s build: @$(CC) -c -Werror -fpic "$(TARGET).c" -Wl,--no-as-needed $(CFLAGS) - @$(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" $(LDFLAGS) + @$(CC) -c -fpic "../../all/common/log.c" $(CFLAGS) + @$(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" "log.o" $(LDFLAGS) @cp "$(TARGET).h" "$(PREFIX)/include" @cp "lib$(TARGET).so" "$(PREFIX)/lib" clean: diff --git a/workspace/retroid/libmsettings/msettings.c b/workspace/retroid/libmsettings/msettings.c index 180f5d57..ce425617 100644 --- a/workspace/retroid/libmsettings/msettings.c +++ b/workspace/retroid/libmsettings/msettings.c @@ -9,6 +9,7 @@ #include #include +#include "log.h" #include "msettings.h" /////////////////////////////////////// @@ -40,9 +41,9 @@ static int shm_fd = -1; static int is_host = 0; static int shm_size = sizeof(Settings); -// SM8250 uses panel0-backlight for AMOLED displays -#define BRIGHTNESS_PATH "/sys/class/backlight/panel0-backlight/brightness" -#define BRIGHTNESS_MAX_PATH "/sys/class/backlight/panel0-backlight/max_brightness" +// SM8250 uses DSI panel backlight (device name: ae94000.dsi.0) +#define BRIGHTNESS_PATH "/sys/class/backlight/ae94000.dsi.0/brightness" +#define BRIGHTNESS_MAX_PATH "/sys/class/backlight/ae94000.dsi.0/max_brightness" #define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" static int max_brightness = 255; @@ -58,7 +59,10 @@ int getInt(char* path) { } void InitSettings(void) { - sprintf(SettingsPath, "%s/msettings.bin", getenv("USERDATA_PATH")); + const char* userdata = getenv("USERDATA_PATH"); + if (!userdata) + userdata = "/storage/.userdata/retroid"; // Fallback if env not set + snprintf(SettingsPath, sizeof(SettingsPath), "%s/msettings.bin", userdata); // Read max brightness from sysfs max_brightness = getInt(BRIGHTNESS_MAX_PATH); @@ -67,11 +71,11 @@ void InitSettings(void) { shm_fd = shm_open(SHM_KEY, O_RDWR | O_CREAT | O_EXCL, 0644); // see if it exists if (shm_fd == -1 && errno == EEXIST) { // already exists - puts("Settings client"); + LOG_debug("Settings client (connecting to existing shared memory)"); shm_fd = shm_open(SHM_KEY, O_RDWR, 0644); settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); } else { // host - puts("Settings host"); // should always be keymon + LOG_debug("Settings host (creating new shared memory)"); is_host = 1; // we created it so set initial size and populate ftruncate(shm_fd, shm_size); @@ -88,9 +92,8 @@ void InitSettings(void) { } int hdmi = getInt(HDMI_STATE_PATH); - printf("brightness: %i (hdmi: %i)\nspeaker: %i\n", settings->brightness, hdmi, - settings->speaker); - fflush(stdout); + LOG_debug("Loaded settings: brightness=%i hdmi=%i speaker=%i", settings->brightness, hdmi, + settings->speaker); SetHDMI(hdmi); SetBrightness(GetBrightness()); @@ -186,16 +189,18 @@ void SetRawBrightness(int val) { if (settings->hdmi) return; - int fd = open(BRIGHTNESS_PATH, O_WRONLY); - if (fd >= 0) { - dprintf(fd, "%d", val); - close(fd); + FILE* file = fopen(BRIGHTNESS_PATH, "w"); + if (file) { + fprintf(file, "%d\n", val); + fclose(file); + } else { + LOG_warn("Failed to set brightness to %d: %s", val, strerror(errno)); } } void SetRawVolume(int val) { // 0 - 100 char cmd[256]; - sprintf(cmd, "amixer sset -M 'Master' %i%% &> /dev/null", val); + snprintf(cmd, sizeof(cmd), "amixer sset -M 'Master' %i%% &> /dev/null", val); system(cmd); } diff --git a/workspace/retroid/platform/Makefile.env b/workspace/retroid/platform/Makefile.env index 26d90cad..0e530353 100644 --- a/workspace/retroid/platform/Makefile.env +++ b/workspace/retroid/platform/Makefile.env @@ -2,5 +2,5 @@ # TODO: When sm8250 toolchain is available, update to: # ARCH = -mtune=cortex-a77.cortex-a55 -march=armv8.2-a+crypto -O3 ARCH = -mtune=cortex-a55 -march=armv8.2-a -O3 -LIBS = -flto -lrga -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 +LIBS = -flto -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 SDL = SDL2 \ No newline at end of file diff --git a/workspace/retroid/platform/platform.c b/workspace/retroid/platform/platform.c index d5d340c8..d6bf5932 100644 --- a/workspace/retroid/platform/platform.c +++ b/workspace/retroid/platform/platform.c @@ -304,11 +304,11 @@ int PLAT_supportsOverscan(void) { #define RAW_L2 16 // ABS_HAT2X (analog trigger) #define RAW_R2 17 // ABS_HAT2Y (analog trigger) -// Treat Home, L3+R3 as menu buttons +// Treat Home, L3, R3 as menu buttons (L3/R3 repurposed - no analog stick click support) #define RAW_MENU1 RAW_L3 #define RAW_MENU2 RAW_R3 -#define INPUT_COUNT 2 +#define INPUT_COUNT 4 static int inputs[INPUT_COUNT]; /** @@ -344,7 +344,37 @@ static char* findInputDeviceByName(const char* device_name) { return NULL; } +/** + * Disable RGB LEDs on analog sticks. + * Retroid devices have HTR3212 LED controllers for stick lighting. + * LEDs are exposed as /sys/devices/platform/multi-led{l,r}{1-4}/leds/rgb:{l,r}{1-4}/brightness + */ +static void disableStickLEDs(void) { + // LED device paths for left and right sticks (4 LEDs per stick) + const char* led_paths[] = { + // Left stick LEDs + "/sys/devices/platform/multi-ledl1/leds/rgb:l1/brightness", + "/sys/devices/platform/multi-ledl2/leds/rgb:l2/brightness", + "/sys/devices/platform/multi-ledl3/leds/rgb:l3/brightness", + "/sys/devices/platform/multi-ledl4/leds/rgb:l4/brightness", + // Right stick LEDs + "/sys/devices/platform/multi-ledr1/leds/rgb:r1/brightness", + "/sys/devices/platform/multi-ledr2/leds/rgb:r2/brightness", + "/sys/devices/platform/multi-ledr3/leds/rgb:r3/brightness", + "/sys/devices/platform/multi-ledr4/leds/rgb:r4/brightness", + }; + + for (int i = 0; i < (int)(sizeof(led_paths) / sizeof(led_paths[0])); i++) { + putInt(led_paths[i], 0); + } + + LOG_debug("Disabled analog stick RGB LEDs\n"); +} + void PLAT_initInput(void) { + // Disable stick LEDs on startup + disableStickLEDs(); + // Find Retroid Pocket Gamepad dynamically by name char* gamepad_path = findInputDeviceByName("Retroid Pocket Gamepad"); if (gamepad_path) { @@ -358,11 +388,16 @@ void PLAT_initInput(void) { LOG_warn("Failed to open fallback /dev/input/event0\n"); } - // Power button and other system inputs (volume, power) - // Try event1 for power/volume buttons + // Open additional event devices for power button, volume, etc. + // Power button location varies, so open multiple devices like RGB30 inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (inputs[1] < 0) - LOG_debug("No secondary input device at event1 (power/volume buttons may not work)\n"); + inputs[2] = open("/dev/input/event2", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + inputs[3] = open("/dev/input/event3", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + + for (int i = 1; i < INPUT_COUNT; i++) { + if (inputs[i] < 0) + LOG_debug("Could not open /dev/input/event%d\n", i); + } } void PLAT_quitInput(void) { @@ -417,13 +452,13 @@ void PLAT_pollInput(void) { } else if (code == RAW_RIGHT) { btn = BTN_DPAD_RIGHT; } else if (code == RAW_A) { - btn = BTN_A; + btn = BTN_B; // Nintendo: bottom is B (RAW_A = BTN_SOUTH) } else if (code == RAW_B) { - btn = BTN_B; + btn = BTN_A; // Nintendo: right is A (RAW_B = BTN_EAST) } else if (code == RAW_X) { - btn = BTN_X; + btn = BTN_Y; // Nintendo: left is Y (RAW_X = BTN_WEST) } else if (code == RAW_Y) { - btn = BTN_Y; + btn = BTN_X; // Nintendo: top is X (RAW_Y = BTN_NORTH) } else if (code == RAW_START) { btn = BTN_START; } else if (code == RAW_SELECT) { @@ -438,10 +473,6 @@ void PLAT_pollInput(void) { btn = BTN_L1; } else if (code == RAW_R1) { btn = BTN_R1; - } else if (code == RAW_L3) { - btn = BTN_L3; - } else if (code == RAW_R3) { - btn = BTN_R3; } else if (code == RAW_PLUS) { btn = BTN_PLUS; } else if (code == RAW_MINUS) { @@ -451,18 +482,23 @@ void PLAT_pollInput(void) { } } else if (type == EV_ABS) { // Analog sticks - Retroid uses ~-1408 to +1408 range + // Clamp to prevent overflow in scaling calculation if (code == RAW_LSX) { - pad.laxis.x = (value * 32767) / 1408; + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.laxis.x = (clamped * 32767) / 1408; PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, pad.laxis.x, tick + PAD_REPEAT_DELAY); } else if (code == RAW_LSY) { - pad.laxis.y = (value * 32767) / 1408; + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.laxis.y = (clamped * 32767) / 1408; PAD_setAnalog(BTN_ID_ANALOG_UP, BTN_ID_ANALOG_DOWN, pad.laxis.y, tick + PAD_REPEAT_DELAY); } else if (code == RAW_RSX) { - pad.raxis.x = (value * 32767) / 1408; + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.raxis.x = (clamped * 32767) / 1408; } else if (code == RAW_RSY) { - pad.raxis.y = (value * 32767) / 1408; + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.raxis.y = (clamped * 32767) / 1408; } else if (code == RAW_L2) { // Analog L2 trigger (0 to ~1552) pressed = value > 100; // Threshold for digital press @@ -523,16 +559,23 @@ void PLAT_getBatteryStatus(int* is_charging, int* charge) { online = prefixMatch("up", status); } -#define BRIGHTNESS_PATH "/sys/class/backlight/panel0-backlight/brightness" +#define BRIGHTNESS_PATH "/sys/class/backlight/ae94000.dsi.0/brightness" #define BLANK_PATH "/sys/class/graphics/fb0/blank" +// Minimum time for DSI panel brightness register to take effect before blanking. +// Empirically determined for AMOLED panels; may need adjustment for different panel revisions. +#define BRIGHTNESS_SETTLE_US 20000 + void PLAT_enableBacklight(int enable) { if (enable) { - putInt(BLANK_PATH, FB_BLANK_UNBLANK); + // Restore brightness first, then unblank SetBrightness(GetBrightness()); + putInt(BLANK_PATH, FB_BLANK_UNBLANK); } else { - putInt(BLANK_PATH, FB_BLANK_POWERDOWN); + // Set brightness to 0 before blanking to ensure screen turns off SetRawBrightness(0); + usleep(BRIGHTNESS_SETTLE_US); + putInt(BLANK_PATH, FB_BLANK_POWERDOWN); } } diff --git a/workspace/retroid/platform/platform.c.bak b/workspace/retroid/platform/platform.c.bak new file mode 100644 index 00000000..7b634bd8 --- /dev/null +++ b/workspace/retroid/platform/platform.c.bak @@ -0,0 +1,669 @@ +/** + * platform.c - Retroid Pocket SM8250 platform implementation + * + * REFACTORED VERSION - Uses shared render_sdl2 backend + * + * Supports multiple device variants in the Retroid Pocket family: + * - Retroid Pocket 5 (1920x1080) + * - Retroid Pocket Flip 2 (1920x1080) + * - Retroid Pocket Mini V2 (1240x1080) + * + * Hardware features: + * - SDL2-based video with HDMI support (via render_sdl2) + * - Retroid Pocket Gamepad via serdev kernel driver + * - Haptic feedback via pmi8998_haptics + * - Display effects (scanlines, grid) + * + * Device detection via LESSOS_DEVICE environment variable. + * + * LessOS/ROCKNIX upstream references (in LessOS repository): + * - Retroid gamepad kernel driver: devices/SM8250/patches/linux/0008-retroid-gamepad.patch + * - Input discovery pattern: packages/sysutils/system-utils/sources/scripts/input_sense + * - Button mappings: packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/Retroid Pocket Gamepad.cfg + * - UDEV rules: devices/SM8250/filesystem/usr/lib/udev/rules.d/99-retroid-pocket.rules + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "api.h" +#include "defines.h" +#include "platform.h" +#include "utils.h" + +#include "gl_video.h" +#include "render_sdl2.h" +#include "scaler.h" + +// Paths for HDMI detection +#define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" + +/////////////////////////////// +// Device Registry and Variant Configuration +/////////////////////////////// + +// Device registry - all known devices that work with this platform +static const DeviceInfo retroid_devices[] = { + // 1920x1080 FHD devices + {.device_id = "pocket5", .display_name = "Pocket 5", .manufacturer = "Retroid"}, + {.device_id = "flip2", .display_name = "Pocket Flip 2", .manufacturer = "Retroid"}, + + // 1240x1080 Mini V2 + {.device_id = "miniv2", .display_name = "Pocket Mini V2", .manufacturer = "Retroid"}, + + // Sentinel + {NULL, NULL, NULL}}; + +// Variant configuration table +typedef struct { + VariantType variant; + int screen_width; + int screen_height; + float screen_diagonal_default; + uint32_t hw_features; +} VariantConfig; + +static const VariantConfig retroid_variants[] = { + {.variant = VARIANT_RETROID_FHD, + .screen_width = 1920, + .screen_height = 1080, + .screen_diagonal_default = 5.5f, + .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, + {.variant = VARIANT_RETROID_MINI_V2, + .screen_width = 1240, + .screen_height = 1080, + .screen_diagonal_default = 3.92f, + .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, + {.variant = VARIANT_NONE} // Sentinel +}; + +// Device-to-variant mapping +typedef struct { + const char* device_string; // What to look for in LESSOS_DEVICE + VariantType variant; // Which variant config to use + const DeviceInfo* device; // Which device info to use + float screen_diagonal; // Override if different from variant default (0 = use default) +} DeviceVariantMap; + +static const DeviceVariantMap retroid_device_map[] = { + // 1920x1080 FHD devices - VARIANT_RETROID_FHD + {"Retroid Pocket 5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, + {"Pocket 5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, + {"RP5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, + + {"Retroid Pocket Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + {"Pocket Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + {"Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + {"RPF2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + + // 1240x1080 Mini V2 - VARIANT_RETROID_MINI_V2 + {"Retroid Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"RPMV2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + + // Sentinel + {NULL, VARIANT_NONE, NULL, 0.0f}}; + +static const VariantConfig* getVariantConfig(VariantType variant) { + for (int i = 0; retroid_variants[i].variant != VARIANT_NONE; i++) { + if (retroid_variants[i].variant == variant) + return &retroid_variants[i]; + } + return NULL; +} + +void PLAT_detectVariant(PlatformVariant* v) { + v->platform = PLATFORM; + v->has_hdmi = 1; + + // Read device from LessOS environment variable + char* device = getenv("LESSOS_DEVICE"); + if (!device) { + LOG_debug("LESSOS_DEVICE not set, defaulting to Pocket 5\n"); + device = "Retroid Pocket 5"; // Fallback to default + } else { + LOG_debug("LESSOS_DEVICE=%s\n", device); + } + + // Look up device in mapping table (substring match) + const DeviceVariantMap* map = NULL; + for (int i = 0; retroid_device_map[i].device_string != NULL; i++) { + if (containsString((char*)device, (char*)retroid_device_map[i].device_string)) { + LOG_debug("Matched device: %s (table entry: %s)\n", device, + retroid_device_map[i].device_string); + map = &retroid_device_map[i]; + break; + } + } + + // Fallback to default if not found + if (!map) { + LOG_warn("Unknown device '%s', defaulting to Pocket 5\n", device); + map = &retroid_device_map[0]; + } + + // Set device info + v->device = map->device; + v->variant = map->variant; + + // Apply variant configuration + const VariantConfig* config = getVariantConfig(map->variant); + if (config) { + v->screen_width = config->screen_width; + v->screen_height = config->screen_height; + v->screen_diagonal = + map->screen_diagonal > 0 ? map->screen_diagonal : config->screen_diagonal_default; + v->hw_features = config->hw_features; + } + + // Check for HDMI connection (runtime override) + v->hdmi_active = getInt(HDMI_STATE_PATH); + if (v->hdmi_active) { + v->screen_width = HDMI_WIDTH; + v->screen_height = HDMI_HEIGHT; + } + + LOG_info("Detected device: %s %s (%s variant, %dx%d, %.1f\")\n", v->device->manufacturer, + v->device->display_name, v->variant == VARIANT_RETROID_MINI_V2 ? "Mini V2" : "FHD", + v->screen_width, v->screen_height, v->screen_diagonal); +} + +/////////////////////////////// +// Video - Using shared SDL2 backend +/////////////////////////////// + +static SDL2_RenderContext vid_ctx; + +static SDL2_Config vid_config = { + // No rotation needed (landscape display) + .auto_rotate = 0, + .rotate_cw = 0, + .rotate_null_center = 0, + // Display features + .has_hdmi = 1, + .default_sharpness = SHARPNESS_SOFT, +}; + +SDL_Surface* PLAT_initVideo(void) { + // Detect device variant + PLAT_detectVariant(&platform_variant); + + // Use detected resolution (may be overridden by HDMI) + int w = FIXED_WIDTH; + int h = FIXED_HEIGHT; + vid_ctx.on_hdmi = platform_variant.hdmi_active; + + return SDL2_initVideo(&vid_ctx, w, h, &vid_config); +} + +void PLAT_quitVideo(void) { + SDL2_quitVideo(&vid_ctx); +} + +void PLAT_clearVideo(SDL_Surface* screen) { + SDL2_clearVideo(&vid_ctx); +} + +void PLAT_clearAll(void) { + SDL2_clearAll(&vid_ctx); +} + +SDL_Surface* PLAT_resizeVideo(int w, int h, int p) { + return SDL2_resizeVideo(&vid_ctx, w, h, p); +} + +void PLAT_setVideoScaleClip(int x, int y, int width, int height) { + // Not supported +} + +void PLAT_setNearestNeighbor(int enabled) { + // Not supported +} + +void PLAT_setSharpness(int sharpness) { + SDL2_setSharpness(&vid_ctx, sharpness); +} + +void PLAT_setEffect(int effect) { + // Only GL path is used on GLES platforms (SDL2 effect state is unused) + GLVideo_setEffect(effect); +} + +void PLAT_setEffectColor(int color) { + // Only GL path is used on GLES platforms (SDL2 effect state is unused) + GLVideo_setEffectColor(color); +} + +void PLAT_vsync(int remaining) { + SDL2_vsync(remaining); +} + +scaler_t PLAT_getScaler(GFX_Renderer* renderer) { + return SDL2_getScaler(&vid_ctx, renderer); +} + +void PLAT_present(GFX_Renderer* renderer) { + vid_ctx.on_hdmi = GetHDMI(); + SDL2_present(&vid_ctx, renderer); +} + +SDL_Window* PLAT_getWindow(void) { + return SDL2_getWindow(&vid_ctx); +} + +int PLAT_getRotation(void) { + return SDL2_getRotation(&vid_ctx); +} + +int PLAT_supportsOverscan(void) { + // Only Mini V2 with near-square aspect ratio benefits from overscan + return VARIANT_IS(VARIANT_RETROID_MINI_V2); +} + +/////////////////////////////// +// Input - Raw key codes from Retroid Pocket Gamepad driver +/////////////////////////////// + +// Button codes from retroid.c keymap[] (standard Linux BTN_* codes) +#define RAW_UP 544 // BTN_DPAD_UP +#define RAW_DOWN 545 // BTN_DPAD_DOWN +#define RAW_LEFT 546 // BTN_DPAD_LEFT +#define RAW_RIGHT 547 // BTN_DPAD_RIGHT +#define RAW_A 304 // BTN_SOUTH +#define RAW_B 305 // BTN_EAST +#define RAW_X 308 // BTN_WEST +#define RAW_Y 307 // BTN_NORTH +#define RAW_START 315 // BTN_START +#define RAW_SELECT 314 // BTN_SELECT +#define RAW_MENU 316 // BTN_MODE (Home button) +#define RAW_L1 310 // BTN_TL +#define RAW_R1 311 // BTN_TR +#define RAW_L3 317 // BTN_THUMBL +#define RAW_R3 318 // BTN_THUMBR +#define RAW_BACK 158 // BTN_BACK +#define RAW_PLUS 115 // KEY_VOLUMEUP +#define RAW_MINUS 114 // KEY_VOLUMEDOWN +#define RAW_POWER 116 // KEY_POWER + +// Analog axes +#define RAW_LSX 0 // ABS_X +#define RAW_LSY 1 // ABS_Y +#define RAW_RSX 3 // ABS_RX +#define RAW_RSY 4 // ABS_RY +#define RAW_L2 16 // ABS_HAT2X (analog trigger) +#define RAW_R2 17 // ABS_HAT2Y (analog trigger) + +// Treat Home, L3, R3 as menu buttons (L3/R3 repurposed - no analog stick click support) +#define RAW_MENU1 RAW_L3 +#define RAW_MENU2 RAW_R3 + +#define INPUT_COUNT 4 +static int inputs[INPUT_COUNT]; + +/** + * Find input device by name in sysfs. + * Returns device path (e.g., "/dev/input/event2") or NULL if not found. + */ +static char* findInputDeviceByName(const char* device_name) { + static char device_path[64]; + char name_path[128]; + char name[256]; + + // Search /sys/class/input/event* for matching device name + for (int i = 0; i < 10; i++) { + snprintf(name_path, sizeof(name_path), "/sys/class/input/event%d/device/name", i); + FILE* f = fopen(name_path, "r"); + if (!f) + continue; + + if (fgets(name, sizeof(name), f)) { + // Strip newline + name[strcspn(name, "\n")] = 0; + if (strcmp(name, device_name) == 0) { + snprintf(device_path, sizeof(device_path), "/dev/input/event%d", i); + fclose(f); + LOG_debug("Found '%s' at %s\n", device_name, device_path); + return device_path; + } + } + fclose(f); + } + + LOG_warn("Device '%s' not found in /sys/class/input/\n", device_name); + return NULL; +} + +/** + * Disable RGB LEDs on analog sticks. + * Retroid devices have HTR3212 LED controllers for stick lighting. + * LEDs are exposed as /sys/devices/platform/multi-led{l,r}{1-4}/leds/rgb:{l,r}{1-4}/brightness + */ +static void disableStickLEDs(void) { + // LED device paths for left and right sticks (4 LEDs per stick) + const char* led_paths[] = { + // Left stick LEDs + "/sys/devices/platform/multi-ledl1/leds/rgb:l1/brightness", + "/sys/devices/platform/multi-ledl2/leds/rgb:l2/brightness", + "/sys/devices/platform/multi-ledl3/leds/rgb:l3/brightness", + "/sys/devices/platform/multi-ledl4/leds/rgb:l4/brightness", + // Right stick LEDs + "/sys/devices/platform/multi-ledr1/leds/rgb:r1/brightness", + "/sys/devices/platform/multi-ledr2/leds/rgb:r2/brightness", + "/sys/devices/platform/multi-ledr3/leds/rgb:r3/brightness", + "/sys/devices/platform/multi-ledr4/leds/rgb:r4/brightness", + }; + + for (int i = 0; i < (int)(sizeof(led_paths) / sizeof(led_paths[0])); i++) { + putInt(led_paths[i], 0); + } + + LOG_debug("Disabled analog stick RGB LEDs\n"); +} + +void PLAT_initInput(void) { + // Disable stick LEDs on startup + disableStickLEDs(); + + // Find Retroid Pocket Gamepad dynamically by name + char* gamepad_path = findInputDeviceByName("Retroid Pocket Gamepad"); + if (gamepad_path) { + inputs[0] = open(gamepad_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (inputs[0] < 0) + LOG_warn("Failed to open gamepad at %s\n", gamepad_path); + } else { + // Fallback: try event0 (shouldn't happen on Retroid) + inputs[0] = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (inputs[0] < 0) + LOG_warn("Failed to open fallback /dev/input/event0\n"); + } + + // Open additional event devices for power button, volume, etc. + // Power button location varies, so open multiple devices like RGB30 + inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + inputs[2] = open("/dev/input/event2", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + inputs[3] = open("/dev/input/event3", O_RDONLY | O_NONBLOCK | O_CLOEXEC); + + for (int i = 1; i < INPUT_COUNT; i++) { + if (inputs[i] < 0) + LOG_debug("Could not open /dev/input/event%d\n", i); + } +} + +void PLAT_quitInput(void) { + for (int i = 0; i < INPUT_COUNT; i++) { + if (inputs[i] >= 0) + close(inputs[i]); + } +} + +// from which has BTN_ constants that conflict with platform.h +struct input_event { + struct timeval time; + __u16 type; + __u16 code; + __s32 value; +}; +#define EV_KEY 0x01 +#define EV_ABS 0x03 + +void PLAT_pollInput(void) { + uint32_t tick = SDL_GetTicks(); + PAD_beginPolling(); + PAD_handleRepeat(tick); + + int input; + static struct input_event event; + for (int i = 0; i < INPUT_COUNT; i++) { + input = inputs[i]; + if (input < 0) + continue; + while (read(input, &event, sizeof(event)) == sizeof(event)) { + if (event.type != EV_KEY && event.type != EV_ABS) + continue; + + int btn = BTN_NONE; + int pressed = 0; + int type = event.type; + int code = event.code; + int value = event.value; + + if (type == EV_KEY) { + if (value > 1) + continue; + + pressed = value; + if (code == RAW_UP) { + btn = BTN_DPAD_UP; + } else if (code == RAW_DOWN) { + btn = BTN_DPAD_DOWN; + } else if (code == RAW_LEFT) { + btn = BTN_DPAD_LEFT; + } else if (code == RAW_RIGHT) { + btn = BTN_DPAD_RIGHT; + } else if (code == RAW_A) { + btn = BTN_B; // Nintendo: bottom is B (RAW_A = BTN_SOUTH) + } else if (code == RAW_B) { + btn = BTN_A; // Nintendo: right is A (RAW_B = BTN_EAST) + } else if (code == RAW_X) { + btn = BTN_Y; // Nintendo: left is Y (RAW_X = BTN_WEST) + } else if (code == RAW_Y) { + btn = BTN_X; // Nintendo: top is X (RAW_Y = BTN_NORTH) + } else if (code == RAW_START) { + btn = BTN_START; + } else if (code == RAW_SELECT) { + btn = BTN_SELECT; + } else if (code == RAW_MENU) { + btn = BTN_MENU; + } else if (code == RAW_MENU1) { + btn = BTN_MENU; + } else if (code == RAW_MENU2) { + btn = BTN_MENU; + } else if (code == RAW_L1) { + btn = BTN_L1; + } else if (code == RAW_R1) { + btn = BTN_R1; + + + + + } else if (code == RAW_PLUS) { + btn = BTN_PLUS; + } else if (code == RAW_MINUS) { + btn = BTN_MINUS; + } else if (code == RAW_POWER) { + btn = BTN_POWER; + } + } else if (type == EV_ABS) { + // Analog sticks - Retroid uses ~-1408 to +1408 range + // Clamp to prevent overflow in scaling calculation + if (code == RAW_LSX) { + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.laxis.x = (clamped * 32767) / 1408; + PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, pad.laxis.x, + tick + PAD_REPEAT_DELAY); + } else if (code == RAW_LSY) { + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.laxis.y = (clamped * 32767) / 1408; + PAD_setAnalog(BTN_ID_ANALOG_UP, BTN_ID_ANALOG_DOWN, pad.laxis.y, + tick + PAD_REPEAT_DELAY); + } else if (code == RAW_RSX) { + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.raxis.x = (clamped * 32767) / 1408; + } else if (code == RAW_RSY) { + int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); + pad.raxis.y = (clamped * 32767) / 1408; + } else if (code == RAW_L2) { + // Analog L2 trigger (0 to ~1552) + pressed = value > 100; // Threshold for digital press + btn = BTN_L2; + } else if (code == RAW_R2) { + // Analog R2 trigger (0 to ~1552) + pressed = value > 100; + btn = BTN_R2; + } + } + + PAD_updateButton(btn, pressed, tick); + } + } +} + +int PLAT_shouldWake(void) { + int input; + static struct input_event event; + for (int i = 0; i < INPUT_COUNT; i++) { + input = inputs[i]; + if (input < 0) + continue; + while (read(input, &event, sizeof(event)) == sizeof(event)) { + if (event.type == EV_KEY && event.code == RAW_POWER && event.value == 0) + return 1; + } + } + return 0; +} + +/////////////////////////////// +// Power Management +/////////////////////////////// + +static int online = 0; + +void PLAT_getBatteryStatus(int* is_charging, int* charge) { + // SM8250 uses standard power_supply class + *is_charging = getInt("/sys/class/power_supply/battery/status") == 2; // Charging status + + int i = getInt("/sys/class/power_supply/battery/capacity"); + if (i > 80) + *charge = 100; + else if (i > 60) + *charge = 80; + else if (i > 40) + *charge = 60; + else if (i > 20) + *charge = 40; + else if (i > 10) + *charge = 20; + else + *charge = 10; + + char status[16]; + getFile("/sys/class/net/wlan0/operstate", status, 16); + online = prefixMatch("up", status); +} + +#define BRIGHTNESS_PATH "/sys/class/backlight/ae94000.dsi.0/brightness" +#define BLANK_PATH "/sys/class/graphics/fb0/blank" + +// Minimum time for DSI panel brightness register to take effect before blanking. +// Empirically determined for AMOLED panels; may need adjustment for different panel revisions. +#define BRIGHTNESS_SETTLE_US 20000 + +void PLAT_enableBacklight(int enable) { + if (enable) { + // Restore brightness first, then unblank + SetBrightness(GetBrightness()); + putInt(BLANK_PATH, FB_BLANK_UNBLANK); + } else { + // Set brightness to 0 before blanking to ensure screen turns off + SetRawBrightness(0); + usleep(BRIGHTNESS_SETTLE_US); + putInt(BLANK_PATH, FB_BLANK_POWERDOWN); + } +} + +void PLAT_powerOff(void) { + sleep(2); + SetRawVolume(MUTE_VOLUME_RAW); + PLAT_enableBacklight(0); + SND_quit(); + VIB_quit(); + PWR_quit(); + GFX_quit(); + system("poweroff"); + while (1) + pause(); +} + +double PLAT_getDisplayHz(void) { + return SDL2_getDisplayHz(); +} + +uint32_t PLAT_measureVsyncInterval(void) { + return SDL2_measureVsyncInterval(&vid_ctx); +} + +#define GOVERNOR_PATH "/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed" + +/** + * Sets CPU frequency based on performance mode. + * + * Snapdragon 865 frequencies (Cortex-A77 prime core): + * Up to 2.84 GHz (prime), 2.4 GHz (performance), 1.8 GHz (efficiency) + * + * @param speed CPU_SPEED_* constant + */ +void PLAT_setCPUSpeed(int speed) { + int freq = 0; + switch (speed) { + case CPU_SPEED_IDLE: + freq = 300000; // 300 MHz (idle) + break; + case CPU_SPEED_POWERSAVE: + freq = 1056000; // ~1 GHz (powersave) + break; + case CPU_SPEED_NORMAL: + freq = 1804800; // ~1.8 GHz (normal) + break; + case CPU_SPEED_PERFORMANCE: + freq = 2419200; // ~2.4 GHz (performance) + break; + } + putInt(GOVERNOR_PATH, freq); +} + +/** + * Gets available CPU frequencies from sysfs. + */ +int PLAT_getAvailableCPUFrequencies(int* frequencies, int max_count) { + return PWR_getAvailableCPUFrequencies_sysfs(frequencies, max_count); +} + +/** + * Sets CPU frequency directly via sysfs. + */ +int PLAT_setCPUFrequency(int freq_khz) { + return PWR_setCPUFrequency_sysfs(freq_khz); +} + +#define RUMBLE_PATH "/sys/class/leds/vibrator/activate" + +void PLAT_setRumble(int strength) { + if (GetHDMI()) + return; + // pmi8998_haptics uses /sys/class/leds/vibrator interface + putInt(RUMBLE_PATH, strength ? 1 : 0); +} + +int PLAT_pickSampleRate(int requested, int max) { + return MIN(requested, max); +} + +char* PLAT_getModel(void) { + return (char*)PLAT_getDeviceName(); +} + +int PLAT_isOnline(void) { + return online; +} diff --git a/workspace/retroid/platform/platform.h b/workspace/retroid/platform/platform.h index 69da96d4..dff870f6 100644 --- a/workspace/retroid/platform/platform.h +++ b/workspace/retroid/platform/platform.h @@ -155,18 +155,18 @@ #define JOY_SELECT 10 // BTN_SELECT (bit 10) #define JOY_START 11 // BTN_START (bit 11) -// Retroid uses Xbox-style layout: A=South, B=East, X=West, Y=North -#define JOY_A 7 // BTN_SOUTH (bit 7) -#define JOY_B 6 // BTN_EAST (bit 6) -#define JOY_X 5 // BTN_WEST (bit 5) -#define JOY_Y 4 // BTN_NORTH (bit 4) +// Retroid uses Nintendo-style layout: A=East, B=South, Y=West, X=North +#define JOY_A 6 // BTN_EAST (bit 6) +#define JOY_B 7 // BTN_SOUTH (bit 7) +#define JOY_X 4 // BTN_NORTH (bit 4) +#define JOY_Y 5 // BTN_WEST (bit 5) #define JOY_L1 8 // BTN_TL (bit 8) #define JOY_R1 9 // BTN_TR (bit 9) #define JOY_L2 JOY_NA // Analog trigger, handled via ABS_HAT2X #define JOY_R2 JOY_NA // Analog trigger, handled via ABS_HAT2Y -#define JOY_L3 12 // BTN_THUMBL (bit 12) -#define JOY_R3 13 // BTN_THUMBR (bit 13) +#define JOY_L3 JOY_NA // Repurposed as menu button (see RAW_MENU1) +#define JOY_R3 JOY_NA // Repurposed as menu button (see RAW_MENU2) #define JOY_MENU 14 // BTN_MODE (bit 14) - Home button #define JOY_MENU_ALT JOY_NA @@ -208,7 +208,7 @@ // Platform-Specific Paths and Settings /////////////////////////////// -#define SDCARD_PATH "/storage" // LessOS default, overridden by LESSOS_STORAGE +#define SDCARD_PATH "/storage/lessui" // LessOS default, overridden by LESSOS_STORAGE #define MUTE_VOLUME_RAW 0 // Raw value for muted volume /////////////////////////////// @@ -227,11 +227,16 @@ // Retroid gamepad device path is dynamic - search multiple event devices // The "Retroid Pocket Gamepad" can be on different eventN depending on boot order -#define KEYMON_INPUT_COUNT 4 +// Volume buttons may be on separate device, so check more devices +#define KEYMON_INPUT_COUNT 8 #define KEYMON_INPUT_DEVICE_0 "/dev/input/event0" #define KEYMON_INPUT_DEVICE_1 "/dev/input/event1" #define KEYMON_INPUT_DEVICE_2 "/dev/input/event2" #define KEYMON_INPUT_DEVICE_3 "/dev/input/event3" +#define KEYMON_INPUT_DEVICE_4 "/dev/input/event4" +#define KEYMON_INPUT_DEVICE_5 "/dev/input/event5" +#define KEYMON_INPUT_DEVICE_6 "/dev/input/event6" +#define KEYMON_INPUT_DEVICE_7 "/dev/input/event7" /////////////////////////////// From f7f5e12f532b193e29665fce6c712520daec64ba Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 21:21:29 -0800 Subject: [PATCH 04/10] Fix show.elf for GLES platforms. Use SDL_Renderer instead of SDL_GetWindowSurface which doesn't work with OpenGL ES. Get display mode before window creation and use SDL_WINDOW_FULLSCREEN_DESKTOP for proper fullscreen rendering. Adds rotation detection for portrait panels. Unifies implementation across retroid, rgb30, tg5040, and rg35xxplus --- workspace/retroid/show/show.c | 88 ++++++++++++++--- workspace/rg35xxplus/show/show.c | 158 +++++++++++++++++++++++-------- workspace/rgb30/show/show.c | 88 ++++++++++++++--- workspace/tg5040/show/show.c | 153 +++++++++++++++++++++++++----- 4 files changed, 393 insertions(+), 94 deletions(-) diff --git a/workspace/retroid/show/show.c b/workspace/retroid/show/show.c index debbecd8..0562eeb0 100644 --- a/workspace/retroid/show/show.c +++ b/workspace/retroid/show/show.c @@ -1,13 +1,15 @@ -// rgb30 - Display an image on screen during boot/install/update +// show - Display an image on screen during boot/install/update +// Uses SDL_Renderer for GLES compatibility +#include +#include #include #include #include #include -#include -#include -SDL_Window* window; -SDL_Surface* screen; +#define FIXED_BPP 2 +#define FIXED_DEPTH (FIXED_BPP * 8) +#define RGBA_MASK_565 0xF800, 0x07E0, 0x001F, 0x0000 int main(int argc, char* argv[]) { if (argc < 2) { @@ -32,30 +34,77 @@ int main(int argc, char* argv[]) { SDL_ShowCursor(0); - // Use 0,0 to let SDL auto-detect display size (like tg5040) + // Get display mode for dimensions and rotation detection + SDL_DisplayMode mode; + if (SDL_GetCurrentDisplayMode(0, &mode) < 0) { + fprintf(stderr, "show.elf: SDL_GetCurrentDisplayMode failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + int w = mode.w; + int h = mode.h; + int p = w * FIXED_BPP; + + // Detect if rotation is needed (portrait panel showing landscape content) + int rotate = (h > w) ? 3 : 0; // 3 = 270 degrees CCW + fprintf(stderr, "show.elf: Display mode: %dx%d, rotate=%d\n", w, h, rotate); + + // Create fullscreen window fprintf(stderr, "show.elf: Creating window...\n"); - window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_SHOWN); + SDL_Window* window = + SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, + SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP); if (!window) { fprintf(stderr, "show.elf: SDL_CreateWindow failed: %s\n", SDL_GetError()); SDL_Quit(); return 1; } - screen = SDL_GetWindowSurface(window); + // Create renderer with hardware acceleration + SDL_Renderer* renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) { + fprintf(stderr, "show.elf: SDL_CreateRenderer failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create streaming texture for our content + SDL_Texture* texture = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, w, h); + if (!texture) { + fprintf(stderr, "show.elf: SDL_CreateTexture failed: %s\n", SDL_GetError()); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create surface to draw on (will be backed by texture pixels) + SDL_Surface* screen = SDL_CreateRGBSurfaceFrom(NULL, w, h, FIXED_DEPTH, p, RGBA_MASK_565); if (!screen) { - fprintf(stderr, "show.elf: SDL_GetWindowSurface failed: %s\n", SDL_GetError()); + fprintf(stderr, "show.elf: SDL_CreateRGBSurfaceFrom failed: %s\n", SDL_GetError()); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 1; } - fprintf(stderr, "show.elf: Window size: %dx%d\n", screen->w, screen->h); + // Lock texture and draw to it + SDL_LockTexture(texture, NULL, &screen->pixels, &screen->pitch); SDL_FillRect(screen, NULL, 0); fprintf(stderr, "show.elf: Loading image: %s\n", path); SDL_Surface* img = IMG_Load(path); if (!img) { fprintf(stderr, "show.elf: IMG_Load failed: %s\n", IMG_GetError()); + SDL_UnlockTexture(texture); + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 1; @@ -66,15 +115,26 @@ int main(int argc, char* argv[]) { // Center the image on screen SDL_Rect dst = {(screen->w - img->w) / 2, (screen->h - img->h) / 2, img->w, img->h}; SDL_BlitSurface(img, NULL, screen, &dst); - - if (SDL_UpdateWindowSurface(window) < 0) { - fprintf(stderr, "show.elf: SDL_UpdateWindowSurface failed: %s\n", SDL_GetError()); + SDL_FreeSurface(img); + SDL_UnlockTexture(texture); + + // Render with rotation if needed + SDL_RenderClear(renderer); + if (rotate) { + SDL_RenderCopyEx(renderer, texture, NULL, &(SDL_Rect){0, w, w, h}, rotate * 90, + &(SDL_Point){0, 0}, SDL_FLIP_NONE); + } else { + SDL_RenderCopy(renderer, texture, NULL, NULL); } + SDL_RenderPresent(renderer); fprintf(stderr, "show.elf: Displaying for %d seconds...\n", delay); sleep(delay); - SDL_FreeSurface(img); + // Cleanup + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); diff --git a/workspace/rg35xxplus/show/show.c b/workspace/rg35xxplus/show/show.c index 36832ae1..0562eeb0 100644 --- a/workspace/rg35xxplus/show/show.c +++ b/workspace/rg35xxplus/show/show.c @@ -1,65 +1,143 @@ -// rg35xxplus - Display an image on screen during boot/install/update +// show - Display an image on screen during boot/install/update +// Uses SDL_Renderer for GLES compatibility +#include +#include #include #include #include #include -#include -#include -#define FIXED_BPP 2 -#define FIXED_DEPTH (FIXED_BPP * 8) +#define FIXED_BPP 2 +#define FIXED_DEPTH (FIXED_BPP * 8) +#define RGBA_MASK_565 0xF800, 0x07E0, 0x001F, 0x0000 -#define RGBA_MASK_565 0xF800, 0x07E0, 0x001F, 0x0000 - -int main(int argc , char* argv[]) { - if (argc<2) { - puts("Usage: show.elf image.png delay"); - return 0; +int main(int argc, char* argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: show.elf image.png [delay]\n"); + return 1; } - + char path[256]; snprintf(path, sizeof(path), "%s", argv[1]); - if (access(path, F_OK)!=0) return 0; // nothing to show :( - - int delay = argc>2 ? atoi(argv[2]) : 2; - - SDL_Init(SDL_INIT_VIDEO); + if (access(path, F_OK) != 0) { + fprintf(stderr, "show.elf: Image not found: %s\n", path); + return 1; + } + + int delay = argc > 2 ? atoi(argv[2]) : 2; + + fprintf(stderr, "show.elf: Initializing SDL2...\n"); + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "show.elf: SDL_Init failed: %s\n", SDL_GetError()); + return 1; + } + SDL_ShowCursor(0); - - int w = 0; - int h = 0; - int p = 0; - SDL_Window* window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w,h, SDL_WINDOW_SHOWN); - - int rotate = 0; + + // Get display mode for dimensions and rotation detection SDL_DisplayMode mode; - SDL_GetCurrentDisplayMode(0, &mode); - if (mode.h>mode.w) rotate = 3; - w = mode.w; - h = mode.h; - p = mode.w * FIXED_BPP; - - SDL_Renderer* renderer = SDL_CreateRenderer(window,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC); - SDL_Texture* texture = SDL_CreateTexture(renderer,SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, w,h); - SDL_Surface* screen = SDL_CreateRGBSurfaceFrom(NULL, w,h, FIXED_DEPTH, p, RGBA_MASK_565); - - SDL_LockTexture(texture,NULL,&screen->pixels,&screen->pitch); + if (SDL_GetCurrentDisplayMode(0, &mode) < 0) { + fprintf(stderr, "show.elf: SDL_GetCurrentDisplayMode failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + int w = mode.w; + int h = mode.h; + int p = w * FIXED_BPP; + + // Detect if rotation is needed (portrait panel showing landscape content) + int rotate = (h > w) ? 3 : 0; // 3 = 270 degrees CCW + fprintf(stderr, "show.elf: Display mode: %dx%d, rotate=%d\n", w, h, rotate); + + // Create fullscreen window + fprintf(stderr, "show.elf: Creating window...\n"); + SDL_Window* window = + SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, + SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP); + if (!window) { + fprintf(stderr, "show.elf: SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + // Create renderer with hardware acceleration + SDL_Renderer* renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) { + fprintf(stderr, "show.elf: SDL_CreateRenderer failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create streaming texture for our content + SDL_Texture* texture = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, w, h); + if (!texture) { + fprintf(stderr, "show.elf: SDL_CreateTexture failed: %s\n", SDL_GetError()); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create surface to draw on (will be backed by texture pixels) + SDL_Surface* screen = SDL_CreateRGBSurfaceFrom(NULL, w, h, FIXED_DEPTH, p, RGBA_MASK_565); + if (!screen) { + fprintf(stderr, "show.elf: SDL_CreateRGBSurfaceFrom failed: %s\n", SDL_GetError()); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Lock texture and draw to it + SDL_LockTexture(texture, NULL, &screen->pixels, &screen->pitch); SDL_FillRect(screen, NULL, 0); + + fprintf(stderr, "show.elf: Loading image: %s\n", path); SDL_Surface* img = IMG_Load(path); - SDL_BlitSurface(img, NULL, screen, &(SDL_Rect){(screen->w-img->w)/2,(screen->h-img->h)/2}); + if (!img) { + fprintf(stderr, "show.elf: IMG_Load failed: %s\n", IMG_GetError()); + SDL_UnlockTexture(texture); + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + fprintf(stderr, "show.elf: Image size: %dx%d\n", img->w, img->h); + + // Center the image on screen + SDL_Rect dst = {(screen->w - img->w) / 2, (screen->h - img->h) / 2, img->w, img->h}; + SDL_BlitSurface(img, NULL, screen, &dst); SDL_FreeSurface(img); SDL_UnlockTexture(texture); - if (rotate) SDL_RenderCopyEx(renderer,texture,NULL,&(SDL_Rect){0,w,w,h},rotate*90,&(SDL_Point){0,0},SDL_FLIP_NONE); - else SDL_RenderCopy(renderer, texture, NULL,NULL); + // Render with rotation if needed + SDL_RenderClear(renderer); + if (rotate) { + SDL_RenderCopyEx(renderer, texture, NULL, &(SDL_Rect){0, w, w, h}, rotate * 90, + &(SDL_Point){0, 0}, SDL_FLIP_NONE); + } else { + SDL_RenderCopy(renderer, texture, NULL, NULL); + } SDL_RenderPresent(renderer); - + + fprintf(stderr, "show.elf: Displaying for %d seconds...\n", delay); sleep(delay); - + + // Cleanup SDL_FreeSurface(screen); SDL_DestroyTexture(texture); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); + + fprintf(stderr, "show.elf: Done\n"); return 0; } diff --git a/workspace/rgb30/show/show.c b/workspace/rgb30/show/show.c index debbecd8..0562eeb0 100644 --- a/workspace/rgb30/show/show.c +++ b/workspace/rgb30/show/show.c @@ -1,13 +1,15 @@ -// rgb30 - Display an image on screen during boot/install/update +// show - Display an image on screen during boot/install/update +// Uses SDL_Renderer for GLES compatibility +#include +#include #include #include #include #include -#include -#include -SDL_Window* window; -SDL_Surface* screen; +#define FIXED_BPP 2 +#define FIXED_DEPTH (FIXED_BPP * 8) +#define RGBA_MASK_565 0xF800, 0x07E0, 0x001F, 0x0000 int main(int argc, char* argv[]) { if (argc < 2) { @@ -32,30 +34,77 @@ int main(int argc, char* argv[]) { SDL_ShowCursor(0); - // Use 0,0 to let SDL auto-detect display size (like tg5040) + // Get display mode for dimensions and rotation detection + SDL_DisplayMode mode; + if (SDL_GetCurrentDisplayMode(0, &mode) < 0) { + fprintf(stderr, "show.elf: SDL_GetCurrentDisplayMode failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + int w = mode.w; + int h = mode.h; + int p = w * FIXED_BPP; + + // Detect if rotation is needed (portrait panel showing landscape content) + int rotate = (h > w) ? 3 : 0; // 3 = 270 degrees CCW + fprintf(stderr, "show.elf: Display mode: %dx%d, rotate=%d\n", w, h, rotate); + + // Create fullscreen window fprintf(stderr, "show.elf: Creating window...\n"); - window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, SDL_WINDOW_SHOWN); + SDL_Window* window = + SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, + SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP); if (!window) { fprintf(stderr, "show.elf: SDL_CreateWindow failed: %s\n", SDL_GetError()); SDL_Quit(); return 1; } - screen = SDL_GetWindowSurface(window); + // Create renderer with hardware acceleration + SDL_Renderer* renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) { + fprintf(stderr, "show.elf: SDL_CreateRenderer failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create streaming texture for our content + SDL_Texture* texture = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, w, h); + if (!texture) { + fprintf(stderr, "show.elf: SDL_CreateTexture failed: %s\n", SDL_GetError()); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create surface to draw on (will be backed by texture pixels) + SDL_Surface* screen = SDL_CreateRGBSurfaceFrom(NULL, w, h, FIXED_DEPTH, p, RGBA_MASK_565); if (!screen) { - fprintf(stderr, "show.elf: SDL_GetWindowSurface failed: %s\n", SDL_GetError()); + fprintf(stderr, "show.elf: SDL_CreateRGBSurfaceFrom failed: %s\n", SDL_GetError()); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 1; } - fprintf(stderr, "show.elf: Window size: %dx%d\n", screen->w, screen->h); + // Lock texture and draw to it + SDL_LockTexture(texture, NULL, &screen->pixels, &screen->pitch); SDL_FillRect(screen, NULL, 0); fprintf(stderr, "show.elf: Loading image: %s\n", path); SDL_Surface* img = IMG_Load(path); if (!img) { fprintf(stderr, "show.elf: IMG_Load failed: %s\n", IMG_GetError()); + SDL_UnlockTexture(texture); + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); return 1; @@ -66,15 +115,26 @@ int main(int argc, char* argv[]) { // Center the image on screen SDL_Rect dst = {(screen->w - img->w) / 2, (screen->h - img->h) / 2, img->w, img->h}; SDL_BlitSurface(img, NULL, screen, &dst); - - if (SDL_UpdateWindowSurface(window) < 0) { - fprintf(stderr, "show.elf: SDL_UpdateWindowSurface failed: %s\n", SDL_GetError()); + SDL_FreeSurface(img); + SDL_UnlockTexture(texture); + + // Render with rotation if needed + SDL_RenderClear(renderer); + if (rotate) { + SDL_RenderCopyEx(renderer, texture, NULL, &(SDL_Rect){0, w, w, h}, rotate * 90, + &(SDL_Point){0, 0}, SDL_FLIP_NONE); + } else { + SDL_RenderCopy(renderer, texture, NULL, NULL); } + SDL_RenderPresent(renderer); fprintf(stderr, "show.elf: Displaying for %d seconds...\n", delay); sleep(delay); - SDL_FreeSurface(img); + // Cleanup + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); diff --git a/workspace/tg5040/show/show.c b/workspace/tg5040/show/show.c index 71b94dea..0562eeb0 100644 --- a/workspace/tg5040/show/show.c +++ b/workspace/tg5040/show/show.c @@ -1,42 +1,143 @@ -// tg5040 - Display an image on screen during boot/install/update +// show - Display an image on screen during boot/install/update +// Uses SDL_Renderer for GLES compatibility +#include +#include #include #include #include #include -#include -#include -SDL_Window* window; -SDL_Surface* screen; +#define FIXED_BPP 2 +#define FIXED_DEPTH (FIXED_BPP * 8) +#define RGBA_MASK_565 0xF800, 0x07E0, 0x001F, 0x0000 -int main(int argc , char* argv[]) { - if (argc<2) { - puts("Usage: show.elf image.png delay"); - return 0; +int main(int argc, char* argv[]) { + if (argc < 2) { + fprintf(stderr, "Usage: show.elf image.png [delay]\n"); + return 1; } - + char path[256]; snprintf(path, sizeof(path), "%s", argv[1]); - if (access(path, F_OK)!=0) return 0; // nothing to show :( - - int delay = argc>2 ? atoi(argv[2]) : 2; - - SDL_Init(SDL_INIT_VIDEO); + if (access(path, F_OK) != 0) { + fprintf(stderr, "show.elf: Image not found: %s\n", path); + return 1; + } + + int delay = argc > 2 ? atoi(argv[2]) : 2; + + fprintf(stderr, "show.elf: Initializing SDL2...\n"); + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "show.elf: SDL_Init failed: %s\n", SDL_GetError()); + return 1; + } + SDL_ShowCursor(0); - - window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0,0, SDL_WINDOW_SHOWN); - - screen = SDL_GetWindowSurface(window); + + // Get display mode for dimensions and rotation detection + SDL_DisplayMode mode; + if (SDL_GetCurrentDisplayMode(0, &mode) < 0) { + fprintf(stderr, "show.elf: SDL_GetCurrentDisplayMode failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + int w = mode.w; + int h = mode.h; + int p = w * FIXED_BPP; + + // Detect if rotation is needed (portrait panel showing landscape content) + int rotate = (h > w) ? 3 : 0; // 3 = 270 degrees CCW + fprintf(stderr, "show.elf: Display mode: %dx%d, rotate=%d\n", w, h, rotate); + + // Create fullscreen window + fprintf(stderr, "show.elf: Creating window...\n"); + SDL_Window* window = + SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, + SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN_DESKTOP); + if (!window) { + fprintf(stderr, "show.elf: SDL_CreateWindow failed: %s\n", SDL_GetError()); + SDL_Quit(); + return 1; + } + + // Create renderer with hardware acceleration + SDL_Renderer* renderer = + SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!renderer) { + fprintf(stderr, "show.elf: SDL_CreateRenderer failed: %s\n", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create streaming texture for our content + SDL_Texture* texture = + SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB565, SDL_TEXTUREACCESS_STREAMING, w, h); + if (!texture) { + fprintf(stderr, "show.elf: SDL_CreateTexture failed: %s\n", SDL_GetError()); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Create surface to draw on (will be backed by texture pixels) + SDL_Surface* screen = SDL_CreateRGBSurfaceFrom(NULL, w, h, FIXED_DEPTH, p, RGBA_MASK_565); + if (!screen) { + fprintf(stderr, "show.elf: SDL_CreateRGBSurfaceFrom failed: %s\n", SDL_GetError()); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + // Lock texture and draw to it + SDL_LockTexture(texture, NULL, &screen->pixels, &screen->pitch); SDL_FillRect(screen, NULL, 0); - - SDL_Surface* img = IMG_Load(path); - SDL_BlitSurface(img, NULL, screen, &(SDL_Rect){(screen->w-img->w)/2,(screen->h-img->h)/2}); - - SDL_UpdateWindowSurface(window); - sleep(delay); - + + fprintf(stderr, "show.elf: Loading image: %s\n", path); + SDL_Surface* img = IMG_Load(path); + if (!img) { + fprintf(stderr, "show.elf: IMG_Load failed: %s\n", IMG_GetError()); + SDL_UnlockTexture(texture); + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + return 1; + } + + fprintf(stderr, "show.elf: Image size: %dx%d\n", img->w, img->h); + + // Center the image on screen + SDL_Rect dst = {(screen->w - img->w) / 2, (screen->h - img->h) / 2, img->w, img->h}; + SDL_BlitSurface(img, NULL, screen, &dst); SDL_FreeSurface(img); + SDL_UnlockTexture(texture); + + // Render with rotation if needed + SDL_RenderClear(renderer); + if (rotate) { + SDL_RenderCopyEx(renderer, texture, NULL, &(SDL_Rect){0, w, w, h}, rotate * 90, + &(SDL_Point){0, 0}, SDL_FLIP_NONE); + } else { + SDL_RenderCopy(renderer, texture, NULL, NULL); + } + SDL_RenderPresent(renderer); + + fprintf(stderr, "show.elf: Displaying for %d seconds...\n", delay); + sleep(delay); + + // Cleanup + SDL_FreeSurface(screen); + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); + + fprintf(stderr, "show.elf: Done\n"); return 0; } From 44d4bc904c86886ae16fee85504306d9cc310bae Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 21:26:50 -0800 Subject: [PATCH 05/10] Generate proper scaling configs for retroid devices. --- scripts/generate-scaling-configs.py | 8 +++++++- .../all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg | 2 ++ .../all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg | 2 ++ workspace/all/paks/Emus/configs/retroid/LYNX/default.cfg | 2 ++ .../paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg | 2 ++ workspace/all/paks/Emus/configs/retroid/NGP/default.cfg | 2 ++ workspace/all/paks/Emus/configs/retroid/NGPC/default.cfg | 2 ++ .../paks/Emus/configs/retroid/PICO/default-mini_v2.cfg | 2 ++ workspace/all/paks/Emus/configs/retroid/PICO/default.cfg | 2 ++ workspace/all/paks/Emus/configs/retroid/PKM/default.cfg | 2 ++ .../all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg | 2 ++ .../all/paks/Emus/configs/tg5040/SGX/default-brick.cfg | 2 -- 12 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 workspace/all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/LYNX/default.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/NGP/default.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/NGPC/default.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/PICO/default-mini_v2.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/PICO/default.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/PKM/default.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg delete mode 100644 workspace/all/paks/Emus/configs/tg5040/SGX/default-brick.cfg diff --git a/scripts/generate-scaling-configs.py b/scripts/generate-scaling-configs.py index 33114ed5..576d6bbe 100755 --- a/scripts/generate-scaling-configs.py +++ b/scripts/generate-scaling-configs.py @@ -39,6 +39,7 @@ "m17": (480, 272, 4.3), # Miyoo A30 / similar "trimuismart": (640, 480, 4.95), "tg5040": (1280, 720, 4.96), # Trimui Smart Pro + "retroid": (1920, 1080, 5.5), # Pocket 5, Flip 2 (FHD) } # Device variants (platform -> variant -> screen info) @@ -50,10 +51,13 @@ "tg5040": { "brick": (1024, 768, 3.2), }, + "retroid": { + "mini_v2": (1240, 1080, 3.92), # Nearly square (~31:27) + }, } # Square screens get Sharp sharpness, others get Sharp too when Native -SQUARE_SCREENS = {"cube", "rgb30"} +SQUARE_SCREENS = {"cube", "rgb30", "mini_v2"} # ============================================================================= # CORE DATA @@ -119,6 +123,8 @@ ("rg35xxplus", "SFC", "cube"): "Cropped", ("rgb30", "FC", None): "Cropped", ("rgb30", "SFC", None): "Cropped", + ("retroid", "FC", "mini_v2"): "Cropped", + ("retroid", "SFC", "mini_v2"): "Cropped", } # ============================================================================= diff --git a/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg new file mode 100644 index 00000000..402c6a09 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg @@ -0,0 +1,2 @@ +# Auto-generated: Cropped fills screen by scaling up and trimming edges +player_screen_scaling = Cropped diff --git a/workspace/all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg new file mode 100644 index 00000000..616efae0 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 97% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/LYNX/default.cfg b/workspace/all/paks/Emus/configs/retroid/LYNX/default.cfg new file mode 100644 index 00000000..98a6143c --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/LYNX/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 94% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg new file mode 100644 index 00000000..616efae0 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 97% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/NGP/default.cfg b/workspace/all/paks/Emus/configs/retroid/NGP/default.cfg new file mode 100644 index 00000000..c19c6012 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/NGP/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 99% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/NGPC/default.cfg b/workspace/all/paks/Emus/configs/retroid/NGPC/default.cfg new file mode 100644 index 00000000..c19c6012 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/NGPC/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 99% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/PICO/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/PICO/default-mini_v2.cfg new file mode 100644 index 00000000..69478637 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/PICO/default-mini_v2.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/PICO/default.cfg b/workspace/all/paks/Emus/configs/retroid/PICO/default.cfg new file mode 100644 index 00000000..69478637 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/PICO/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/PKM/default.cfg b/workspace/all/paks/Emus/configs/retroid/PKM/default.cfg new file mode 100644 index 00000000..69478637 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/PKM/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg new file mode 100644 index 00000000..402c6a09 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg @@ -0,0 +1,2 @@ +# Auto-generated: Cropped fills screen by scaling up and trimming edges +player_screen_scaling = Cropped diff --git a/workspace/all/paks/Emus/configs/tg5040/SGX/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/SGX/default-brick.cfg deleted file mode 100644 index 9b5cf93d..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/SGX/default-brick.cfg +++ /dev/null @@ -1,2 +0,0 @@ -# Auto-generated: 100% fill with integer scaling -player_screen_scaling = Native From c0d6988a713e20a6e70d17ca21e31289da9b94ce Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 21:33:23 -0800 Subject: [PATCH 06/10] Add support for Retroid Pocket Mini v1. --- scripts/generate-scaling-configs.py | 1 + .../configs/retroid/A2600/default-mini_v1.cfg | 2 ++ .../configs/retroid/A7800/default-mini_v1.cfg | 2 ++ .../configs/retroid/FC/default-mini_v1.cfg | 2 ++ .../configs/retroid/LYNX/default-mini_v1.cfg | 2 ++ .../configs/retroid/MD/default-mini_v1.cfg | 2 ++ .../configs/retroid/NEOCD/default-mini_v1.cfg | 2 ++ .../configs/retroid/NG/default-mini_v1.cfg | 2 ++ .../configs/retroid/NGP/default-mini_v1.cfg | 2 ++ .../configs/retroid/NGPC/default-mini_v1.cfg | 2 ++ .../configs/retroid/PKM/default-mini_v1.cfg | 2 ++ .../configs/retroid/SMS/default-mini_v1.cfg | 2 ++ workspace/retroid/README.md | 4 ++- workspace/retroid/platform/platform.c | 32 +++++++++++++++---- workspace/retroid/platform/platform.h | 4 ++- 15 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 workspace/all/paks/Emus/configs/retroid/A2600/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/A7800/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/FC/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/LYNX/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/MD/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/NEOCD/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/NG/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/NGP/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/NGPC/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/PKM/default-mini_v1.cfg create mode 100644 workspace/all/paks/Emus/configs/retroid/SMS/default-mini_v1.cfg diff --git a/scripts/generate-scaling-configs.py b/scripts/generate-scaling-configs.py index 576d6bbe..c23467f6 100755 --- a/scripts/generate-scaling-configs.py +++ b/scripts/generate-scaling-configs.py @@ -52,6 +52,7 @@ "brick": (1024, 768, 3.2), }, "retroid": { + "mini_v1": (1280, 960, 3.7), # 4:3 aspect ratio "mini_v2": (1240, 1080, 3.92), # Nearly square (~31:27) }, } diff --git a/workspace/all/paks/Emus/configs/retroid/A2600/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/A2600/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/A2600/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/A7800/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/A7800/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/A7800/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/LYNX/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/LYNX/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/LYNX/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/MD/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/MD/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/MD/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/NEOCD/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NEOCD/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/NEOCD/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/NG/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NG/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/NG/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/NGP/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NGP/default-mini_v1.cfg new file mode 100644 index 00000000..69478637 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/NGP/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/NGPC/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NGPC/default-mini_v1.cfg new file mode 100644 index 00000000..69478637 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/NGPC/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/PKM/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/PKM/default-mini_v1.cfg new file mode 100644 index 00000000..65ecb2c3 --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/PKM/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 98% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/retroid/SMS/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/SMS/default-mini_v1.cfg new file mode 100644 index 00000000..9b5cf93d --- /dev/null +++ b/workspace/all/paks/Emus/configs/retroid/SMS/default-mini_v1.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +player_screen_scaling = Native diff --git a/workspace/retroid/README.md b/workspace/retroid/README.md index d0efdfe9..3d3734c2 100644 --- a/workspace/retroid/README.md +++ b/workspace/retroid/README.md @@ -8,7 +8,8 @@ Platform implementation for Retroid Pocket devices running LessOS. |--------|---------|------------|-------| | **Pocket 5** | 5.5" AMOLED | 1920x1080 | Flagship model | | **Pocket Flip 2** | 5.5" AMOLED | 1920x1080 | Clamshell design | -| **Pocket Mini V2** | 3.92" AMOLED | 1240x1080 | Compact form factor | +| **Pocket Mini V1** | 3.7" AMOLED | 1280x960 | 4:3 compact | +| **Pocket Mini V2** | 3.92" AMOLED | 1240x1080 | Near-square compact | **OS**: LessOS (ROCKNIX-based) @@ -95,6 +96,7 @@ The platform auto-detects device variant via `LESSOS_DEVICE`: | Variant | Devices | Resolution | Aspect | |---------|---------|------------|--------| | `VARIANT_RETROID_FHD` | Pocket 5, Flip 2 | 1920x1080 | 16:9 | +| `VARIANT_RETROID_MINI_V1` | Pocket Mini V1 | 1280x960 | 4:3 | | `VARIANT_RETROID_MINI_V2` | Pocket Mini V2 | 1240x1080 | ~31:27 | ## LessOS/ROCKNIX References diff --git a/workspace/retroid/platform/platform.c b/workspace/retroid/platform/platform.c index d6bf5932..45ec4546 100644 --- a/workspace/retroid/platform/platform.c +++ b/workspace/retroid/platform/platform.c @@ -6,6 +6,7 @@ * Supports multiple device variants in the Retroid Pocket family: * - Retroid Pocket 5 (1920x1080) * - Retroid Pocket Flip 2 (1920x1080) + * - Retroid Pocket Mini V1 (1280x960) * - Retroid Pocket Mini V2 (1240x1080) * * Hardware features: @@ -58,6 +59,9 @@ static const DeviceInfo retroid_devices[] = { {.device_id = "pocket5", .display_name = "Pocket 5", .manufacturer = "Retroid"}, {.device_id = "flip2", .display_name = "Pocket Flip 2", .manufacturer = "Retroid"}, + // 1280x960 Mini V1 + {.device_id = "miniv1", .display_name = "Pocket Mini V1", .manufacturer = "Retroid"}, + // 1240x1080 Mini V2 {.device_id = "miniv2", .display_name = "Pocket Mini V2", .manufacturer = "Retroid"}, @@ -79,6 +83,11 @@ static const VariantConfig retroid_variants[] = { .screen_height = 1080, .screen_diagonal_default = 5.5f, .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, + {.variant = VARIANT_RETROID_MINI_V1, + .screen_width = 1280, + .screen_height = 960, + .screen_diagonal_default = 3.7f, + .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, {.variant = VARIANT_RETROID_MINI_V2, .screen_width = 1240, .screen_height = 1080, @@ -106,11 +115,17 @@ static const DeviceVariantMap retroid_device_map[] = { {"Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, {"RPF2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, + // 1280x960 Mini V1 - VARIANT_RETROID_MINI_V1 + {"Retroid Pocket Mini V1", VARIANT_RETROID_MINI_V1, &retroid_devices[2], 3.7f}, + {"Pocket Mini V1", VARIANT_RETROID_MINI_V1, &retroid_devices[2], 3.7f}, + {"Mini V1", VARIANT_RETROID_MINI_V1, &retroid_devices[2], 3.7f}, + {"RPMV1", VARIANT_RETROID_MINI_V1, &retroid_devices[2], 3.7f}, + // 1240x1080 Mini V2 - VARIANT_RETROID_MINI_V2 - {"Retroid Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - {"Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - {"Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - {"RPMV2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, + {"Retroid Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[3], 3.92f}, + {"Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[3], 3.92f}, + {"Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[3], 3.92f}, + {"RPMV2", VARIANT_RETROID_MINI_V2, &retroid_devices[3], 3.92f}, // Sentinel {NULL, VARIANT_NONE, NULL, 0.0f}}; @@ -174,9 +189,14 @@ void PLAT_detectVariant(PlatformVariant* v) { v->screen_height = HDMI_HEIGHT; } + const char* variant_name = "FHD"; + if (v->variant == VARIANT_RETROID_MINI_V1) + variant_name = "Mini V1"; + else if (v->variant == VARIANT_RETROID_MINI_V2) + variant_name = "Mini V2"; LOG_info("Detected device: %s %s (%s variant, %dx%d, %.1f\")\n", v->device->manufacturer, - v->device->display_name, v->variant == VARIANT_RETROID_MINI_V2 ? "Mini V2" : "FHD", - v->screen_width, v->screen_height, v->screen_diagonal); + v->device->display_name, variant_name, v->screen_width, v->screen_height, + v->screen_diagonal); } /////////////////////////////// diff --git a/workspace/retroid/platform/platform.h b/workspace/retroid/platform/platform.h index dff870f6..9af24d4c 100644 --- a/workspace/retroid/platform/platform.h +++ b/workspace/retroid/platform/platform.h @@ -4,6 +4,7 @@ * Supports multiple device variants in the Retroid Pocket family: * - Retroid Pocket 5: 5.5" 1920x1080 AMOLED (16:9) * - Retroid Pocket Flip 2: 5.5" 1920x1080 AMOLED (16:9, clamshell) + * - Retroid Pocket Mini V1: 3.7" 1280x960 AMOLED (4:3) * - Retroid Pocket Mini V2: 3.92" 1240x1080 AMOLED (~31:27, near square) * * Hardware features: @@ -76,7 +77,8 @@ // Retroid Pocket SM8250 family variants #define VARIANT_RETROID_FHD (VARIANT_PLATFORM_BASE + 0) // 1920x1080 (Pocket 5, Flip 2) -#define VARIANT_RETROID_MINI_V2 (VARIANT_PLATFORM_BASE + 1) // 1240x1080 (Mini V2) +#define VARIANT_RETROID_MINI_V1 (VARIANT_PLATFORM_BASE + 1) // 1280x960 (Mini V1) +#define VARIANT_RETROID_MINI_V2 (VARIANT_PLATFORM_BASE + 2) // 1240x1080 (Mini V2) /////////////////////////////// // SDL Keyboard Button Mappings From fa971c76fea479424595358d5271d195b6d0d883 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 21:48:46 -0800 Subject: [PATCH 07/10] Fix cfg mapping for retroid. --- scripts/generate-scaling-configs.py | 10 +++++----- .../A2600/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../A7800/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../FC/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../FC/{default-mini_v2.cfg => default-miniv2.cfg} | 0 .../GBA/{default-mini_v2.cfg => default-miniv2.cfg} | 0 .../LYNX/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../MD/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../MGBA/{default-mini_v2.cfg => default-miniv2.cfg} | 0 .../NEOCD/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../NG/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../NGP/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../NGPC/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../PICO/{default-mini_v2.cfg => default-miniv2.cfg} | 0 .../PKM/{default-mini_v1.cfg => default-miniv1.cfg} | 0 .../SFC/{default-mini_v2.cfg => default-miniv2.cfg} | 0 .../SMS/{default-mini_v1.cfg => default-miniv1.cfg} | 0 workspace/all/paks/LessUI/platforms/retroid/init.sh | 8 +++++++- 18 files changed, 12 insertions(+), 6 deletions(-) rename workspace/all/paks/Emus/configs/retroid/A2600/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/A7800/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/FC/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/FC/{default-mini_v2.cfg => default-miniv2.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/GBA/{default-mini_v2.cfg => default-miniv2.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/LYNX/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/MD/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/MGBA/{default-mini_v2.cfg => default-miniv2.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/NEOCD/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/NG/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/NGP/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/NGPC/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/PICO/{default-mini_v2.cfg => default-miniv2.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/PKM/{default-mini_v1.cfg => default-miniv1.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/SFC/{default-mini_v2.cfg => default-miniv2.cfg} (100%) rename workspace/all/paks/Emus/configs/retroid/SMS/{default-mini_v1.cfg => default-miniv1.cfg} (100%) diff --git a/scripts/generate-scaling-configs.py b/scripts/generate-scaling-configs.py index c23467f6..5d234e19 100755 --- a/scripts/generate-scaling-configs.py +++ b/scripts/generate-scaling-configs.py @@ -52,13 +52,13 @@ "brick": (1024, 768, 3.2), }, "retroid": { - "mini_v1": (1280, 960, 3.7), # 4:3 aspect ratio - "mini_v2": (1240, 1080, 3.92), # Nearly square (~31:27) + "miniv1": (1280, 960, 3.7), # 4:3 aspect ratio + "miniv2": (1240, 1080, 3.92), # Nearly square (~31:27) }, } # Square screens get Sharp sharpness, others get Sharp too when Native -SQUARE_SCREENS = {"cube", "rgb30", "mini_v2"} +SQUARE_SCREENS = {"cube", "rgb30", "miniv2"} # ============================================================================= # CORE DATA @@ -124,8 +124,8 @@ ("rg35xxplus", "SFC", "cube"): "Cropped", ("rgb30", "FC", None): "Cropped", ("rgb30", "SFC", None): "Cropped", - ("retroid", "FC", "mini_v2"): "Cropped", - ("retroid", "SFC", "mini_v2"): "Cropped", + ("retroid", "FC", "miniv2"): "Cropped", + ("retroid", "SFC", "miniv2"): "Cropped", } # ============================================================================= diff --git a/workspace/all/paks/Emus/configs/retroid/A2600/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/A2600/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/A2600/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/A2600/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/A7800/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/A7800/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/A7800/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/A7800/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/FC/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/FC/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/FC/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/FC/default-miniv2.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/FC/default-mini_v2.cfg rename to workspace/all/paks/Emus/configs/retroid/FC/default-miniv2.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/GBA/default-miniv2.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/GBA/default-mini_v2.cfg rename to workspace/all/paks/Emus/configs/retroid/GBA/default-miniv2.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/LYNX/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/LYNX/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/LYNX/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/LYNX/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/MD/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/MD/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/MD/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/MD/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/MGBA/default-miniv2.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/MGBA/default-mini_v2.cfg rename to workspace/all/paks/Emus/configs/retroid/MGBA/default-miniv2.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/NEOCD/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NEOCD/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/NEOCD/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/NEOCD/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/NG/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NG/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/NG/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/NG/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/NGP/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NGP/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/NGP/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/NGP/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/NGPC/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/NGPC/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/NGPC/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/NGPC/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/PICO/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/PICO/default-miniv2.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/PICO/default-mini_v2.cfg rename to workspace/all/paks/Emus/configs/retroid/PICO/default-miniv2.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/PKM/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/PKM/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/PKM/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/PKM/default-miniv1.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg b/workspace/all/paks/Emus/configs/retroid/SFC/default-miniv2.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/SFC/default-mini_v2.cfg rename to workspace/all/paks/Emus/configs/retroid/SFC/default-miniv2.cfg diff --git a/workspace/all/paks/Emus/configs/retroid/SMS/default-mini_v1.cfg b/workspace/all/paks/Emus/configs/retroid/SMS/default-miniv1.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/retroid/SMS/default-mini_v1.cfg rename to workspace/all/paks/Emus/configs/retroid/SMS/default-miniv1.cfg diff --git a/workspace/all/paks/LessUI/platforms/retroid/init.sh b/workspace/all/paks/LessUI/platforms/retroid/init.sh index 83c926ab..31d3808b 100644 --- a/workspace/all/paks/LessUI/platforms/retroid/init.sh +++ b/workspace/all/paks/LessUI/platforms/retroid/init.sh @@ -1,8 +1,14 @@ #!/bin/sh # Retroid Pocket SM8250 platform initialization (LessOS) -# Devices: Pocket 5, Pocket Flip 2, Pocket Mini V2 +# Devices: Pocket 5, Pocket Flip 2, Pocket Mini V1, Pocket Mini V2 # OS: LessOS (ROCKNIX-based) +# Detect device variant from LESSOS_DEVICE (set by LessOS) +case "$LESSOS_DEVICE" in + *"Mini V1"*) export DEVICE="miniv1" ;; + *"Mini V2"*) export DEVICE="miniv2" ;; +esac + # CPU governor (speed controlled by frontend) echo userspace >/sys/devices/system/cpu/cpufreq/policy0/scaling_governor 2>/dev/null From a9c14e0e15f0c0e5d22c8db11cba32a5f00c90c4 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 22:45:16 -0800 Subject: [PATCH 08/10] Add libudev input discovery for retroid and rgb30. Replace hardcoded /dev/input/event* paths with dynamic device enumeration using libudev. Devices are discovered at runtime via udev properties (ID_INPUT_JOYSTICK for joysticks, input subsystem for all inputs). Fixes #109 --- workspace/all/common/build.mk | 6 + workspace/all/common/udev_input.c | 262 ++++++++++++++++++++++++ workspace/all/common/udev_input.h | 64 ++++++ workspace/all/launcher/Makefile | 6 + workspace/all/player/Makefile | 6 + workspace/all/utils/keymon/Makefile | 8 + workspace/all/utils/keymon/keymon.c | 29 ++- workspace/retroid/platform/Makefile.env | 5 +- workspace/retroid/platform/platform.c | 81 ++------ workspace/retroid/platform/platform.h | 16 +- workspace/rgb30/platform/Makefile.env | 5 +- workspace/rgb30/platform/platform.c | 31 +-- workspace/rgb30/platform/platform.h | 12 +- 13 files changed, 424 insertions(+), 107 deletions(-) create mode 100644 workspace/all/common/udev_input.c create mode 100644 workspace/all/common/udev_input.h diff --git a/workspace/all/common/build.mk b/workspace/all/common/build.mk index b78f4383..f086ba47 100644 --- a/workspace/all/common/build.mk +++ b/workspace/all/common/build.mk @@ -93,6 +93,12 @@ else COMMON_SOURCE += $(COMMON_DIR)/effect_surface.c endif +# Add libudev support for dynamic input device discovery (retroid, rgb30) +ifeq ($(HAS_LIBUDEV),1) +COMMON_SOURCE += $(COMMON_DIR)/udev_input.c +EXTRA_LDFLAGS += -ludev +endif + SOURCE ?= $(TARGET).c $(COMMON_SOURCE) $(EXTRA_SOURCE) HEADERS = $(wildcard $(COMMON_DIR)/*.h) $(wildcard $(PLATFORM_DIR)/*.h) diff --git a/workspace/all/common/udev_input.c b/workspace/all/common/udev_input.c new file mode 100644 index 00000000..4500b3c9 --- /dev/null +++ b/workspace/all/common/udev_input.c @@ -0,0 +1,262 @@ +/** + * udev_input.c - Dynamic input device discovery using libudev + * + * Implementation of libudev-based input device enumeration. + * Provides robust device discovery without hardcoded event paths. + */ + +#include "udev_input.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" + +/** + * Compare function for sorting device paths. + * Ensures consistent ordering (event0, event1, event2, ...). + */ +static int compare_device_paths(const void* a, const void* b) { + return strcmp(*(const char**)a, *(const char**)b); +} + +int udev_open_joysticks(int* fds) { + struct udev* udev = NULL; + struct udev_enumerate* enumerate = NULL; + struct udev_list_entry* devices = NULL; + struct udev_list_entry* entry = NULL; + const char* device_paths[UDEV_MAX_DEVICES]; + int path_count = 0; + int opened = 0; + + // Initialize all fds to -1 + for (int i = 0; i < UDEV_MAX_DEVICES; i++) { + fds[i] = -1; + } + + udev = udev_new(); + if (!udev) { + LOG_error("Failed to create udev context"); + return 0; + } + + enumerate = udev_enumerate_new(udev); + if (!enumerate) { + LOG_error("Failed to create udev enumerate"); + udev_unref(udev); + return 0; + } + + // Filter for input subsystem with joystick property + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_add_match_property(enumerate, "ID_INPUT_JOYSTICK", "1"); + udev_enumerate_scan_devices(enumerate); + + devices = udev_enumerate_get_list_entry(enumerate); + + // Collect device paths + udev_list_entry_foreach(entry, devices) { + const char* syspath = udev_list_entry_get_name(entry); + struct udev_device* dev = udev_device_new_from_syspath(udev, syspath); + if (!dev) + continue; + + const char* devnode = udev_device_get_devnode(dev); + if (devnode && path_count < UDEV_MAX_DEVICES) { + // Only accept /dev/input/event* devices (not js* or others) + if (strstr(devnode, "/dev/input/event") != NULL) { + char* dup = strdup(devnode); + if (dup) { + device_paths[path_count++] = dup; + LOG_debug("Found joystick: %s", devnode); + } else { + LOG_warn("Failed to allocate memory for device path"); + } + } + } + udev_device_unref(dev); + } + + // Sort paths for consistent ordering + if (path_count > 1) { + qsort(device_paths, path_count, sizeof(char*), compare_device_paths); + } + + // Open devices + for (int i = 0; i < path_count; i++) { + int fd = open(device_paths[i], O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + fds[opened++] = fd; + LOG_info("Opened joystick: %s (fd=%d)", device_paths[i], fd); + } else { + LOG_warn("Failed to open %s", device_paths[i]); + } + free((void*)device_paths[i]); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + if (opened == 0) { + LOG_warn("No joystick devices found"); + } + + return opened; +} + +int udev_open_all_inputs(int* fds) { + struct udev* udev = NULL; + struct udev_enumerate* enumerate = NULL; + struct udev_list_entry* devices = NULL; + struct udev_list_entry* entry = NULL; + const char* device_paths[UDEV_MAX_DEVICES]; + int path_count = 0; + int opened = 0; + + // Initialize all fds to -1 + for (int i = 0; i < UDEV_MAX_DEVICES; i++) { + fds[i] = -1; + } + + udev = udev_new(); + if (!udev) { + LOG_error("Failed to create udev context"); + return 0; + } + + enumerate = udev_enumerate_new(udev); + if (!enumerate) { + LOG_error("Failed to create udev enumerate"); + udev_unref(udev); + return 0; + } + + // Filter for input subsystem only (gets all input devices) + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + + devices = udev_enumerate_get_list_entry(enumerate); + + // Collect device paths + udev_list_entry_foreach(entry, devices) { + const char* syspath = udev_list_entry_get_name(entry); + struct udev_device* dev = udev_device_new_from_syspath(udev, syspath); + if (!dev) + continue; + + const char* devnode = udev_device_get_devnode(dev); + if (devnode && path_count < UDEV_MAX_DEVICES) { + // Only accept /dev/input/event* devices + if (strstr(devnode, "/dev/input/event") != NULL) { + char* dup = strdup(devnode); + if (dup) { + device_paths[path_count++] = dup; + LOG_debug("Found input device: %s", devnode); + } else { + LOG_warn("Failed to allocate memory for device path"); + } + } + } + udev_device_unref(dev); + } + + // Sort paths for consistent ordering + if (path_count > 1) { + qsort(device_paths, path_count, sizeof(char*), compare_device_paths); + } + + // Open devices + for (int i = 0; i < path_count; i++) { + int fd = open(device_paths[i], O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0) { + fds[opened++] = fd; + LOG_debug("Opened input: %s (fd=%d)", device_paths[i], fd); + } else { + LOG_warn("Failed to open %s", device_paths[i]); + } + free((void*)device_paths[i]); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + return opened; +} + +const char* udev_find_device_by_name(const char* device_name) { + static char result_path[128]; + struct udev* udev = NULL; + struct udev_enumerate* enumerate = NULL; + struct udev_list_entry* devices = NULL; + struct udev_list_entry* entry = NULL; + const char* found = NULL; + + if (!device_name) { + return NULL; + } + + udev = udev_new(); + if (!udev) { + LOG_error("Failed to create udev context"); + return NULL; + } + + enumerate = udev_enumerate_new(udev); + if (!enumerate) { + LOG_error("Failed to create udev enumerate"); + udev_unref(udev); + return NULL; + } + + udev_enumerate_add_match_subsystem(enumerate, "input"); + udev_enumerate_scan_devices(enumerate); + + devices = udev_enumerate_get_list_entry(enumerate); + + udev_list_entry_foreach(entry, devices) { + const char* syspath = udev_list_entry_get_name(entry); + struct udev_device* dev = udev_device_new_from_syspath(udev, syspath); + if (!dev) + continue; + + const char* devnode = udev_device_get_devnode(dev); + if (!devnode || strstr(devnode, "/dev/input/event") == NULL) { + udev_device_unref(dev); + continue; + } + + // Get the device name from sysattr + const char* name = udev_device_get_sysattr_value(dev, "name"); + if (name && strcmp(name, device_name) == 0) { + (void)snprintf(result_path, sizeof(result_path), "%s", devnode); + found = result_path; + LOG_debug("Found '%s' at %s", device_name, result_path); + udev_device_unref(dev); + break; + } + + udev_device_unref(dev); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); + + if (!found) { + LOG_warn("Device '%s' not found", device_name); + } + + return found; +} + +void udev_close_all(int* fds, int count) { + for (int i = 0; i < count; i++) { + if (fds[i] >= 0) { + close(fds[i]); + fds[i] = -1; + } + } +} diff --git a/workspace/all/common/udev_input.h b/workspace/all/common/udev_input.h new file mode 100644 index 00000000..ec99348e --- /dev/null +++ b/workspace/all/common/udev_input.h @@ -0,0 +1,64 @@ +/** + * udev_input.h - Dynamic input device discovery using libudev + * + * Provides robust input device enumeration for LessOS platforms. + * Uses libudev to dynamically discover joystick and input devices, + * replacing hardcoded /dev/input/event* paths. + * + * This approach is more reliable than sysfs parsing because: + * - No assumptions about event device numbering + * - Handles devices appearing in any order + * - Matches ROCKNIX upstream pattern + */ + +#ifndef UDEV_INPUT_H +#define UDEV_INPUT_H + +// Maximum number of input devices to enumerate +#define UDEV_MAX_DEVICES 16 + +/** + * Open all joystick input devices. + * + * Uses libudev to find devices with ID_INPUT_JOYSTICK=1 property. + * Opens each device with O_RDONLY | O_NONBLOCK | O_CLOEXEC. + * + * @param fds Array to store file descriptors (must have UDEV_MAX_DEVICES entries) + * @return Number of devices successfully opened (0 if none found or error) + */ +int udev_open_joysticks(int* fds); + +/** + * Open all input event devices (joystick + keyboard + keys). + * + * Discovers all /dev/input/event* devices and opens them. + * Useful for keymon which needs to monitor power button, volume keys, etc. + * + * @param fds Array to store file descriptors (must have UDEV_MAX_DEVICES entries) + * @return Number of devices successfully opened + */ +int udev_open_all_inputs(int* fds); + +/** + * Find device path by device name. + * + * Searches udev for an input device matching the specified name. + * This is useful for finding specific devices like "Retroid Pocket Gamepad". + * + * @param device_name Exact device name to search for + * @return Device path (e.g., "/dev/input/event2") or NULL if not found. + * Returns pointer to static buffer - copy if you need to keep it. + */ +const char* udev_find_device_by_name(const char* device_name); + +/** + * Close all open file descriptors in an array. + * + * Closes any fd >= 0 and sets it to -1. + * + * @param fds Array of file descriptors + * @param count Number of entries in the array + */ +void udev_close_all(int* fds, int count); + +#endif // UDEV_INPUT_H diff --git a/workspace/all/launcher/Makefile b/workspace/all/launcher/Makefile index adef077b..a8911cd2 100644 --- a/workspace/all/launcher/Makefile +++ b/workspace/all/launcher/Makefile @@ -40,6 +40,12 @@ SOURCE += ../common/render_sdl2.c ../common/effect_utils.c ../common/gl_video.c else SOURCE += ../common/effect_surface.c endif + +# Add libudev support for dynamic input device discovery (retroid, rgb30) +ifeq ($(HAS_LIBUDEV),1) +SOURCE += ../common/udev_input.c +LIBS += -ludev +endif HEADERS = $(wildcard *.h) $(wildcard ../common/*.h) $(wildcard ../../$(PLATFORM)/platform/*.h) CC = $(CROSS_COMPILE)gcc diff --git a/workspace/all/player/Makefile b/workspace/all/player/Makefile index 3c51aedd..eb41a018 100644 --- a/workspace/all/player/Makefile +++ b/workspace/all/player/Makefile @@ -43,6 +43,12 @@ SOURCE += ../common/render_sdl2.c ../common/effect_utils.c else SOURCE += ../common/effect_surface.c endif + +# Add libudev support for dynamic input device discovery (retroid, rgb30) +ifeq ($(HAS_LIBUDEV),1) +SOURCE += ../common/udev_input.c +LIBS += -ludev +endif HEADERS = $(wildcard *.h) $(wildcard ../common/*.h) $(wildcard ../launcher/*.h) $(wildcard ../../$(PLATFORM)/platform/*.h) CC = $(CROSS_COMPILE)gcc diff --git a/workspace/all/utils/keymon/Makefile b/workspace/all/utils/keymon/Makefile index 41ade00c..08eb9c82 100644 --- a/workspace/all/utils/keymon/Makefile +++ b/workspace/all/utils/keymon/Makefile @@ -31,6 +31,14 @@ EXTRA_LDFLAGS = -lpthread # Skip build for miyoomini (uses platform-specific implementation in workspace/miyoomini/keymon) ifneq ($(PLATFORM),miyoomini) include ../../common/build.mk + +# Add libudev support for dynamic input device discovery (retroid, rgb30) +# NOTE: This must be after include to access HAS_LIBUDEV from Makefile.env +ifeq ($(HAS_LIBUDEV),1) +SOURCE += ../../../all/common/udev_input.c +EXTRA_LDFLAGS += -ludev +endif + else # Dummy targets for miyoomini .PHONY: setup build install clean diff --git a/workspace/all/utils/keymon/keymon.c b/workspace/all/utils/keymon/keymon.c index 729e9d04..488d17d2 100644 --- a/workspace/all/utils/keymon/keymon.c +++ b/workspace/all/utils/keymon/keymon.c @@ -45,6 +45,10 @@ #include #endif +#ifdef KEYMON_USE_LIBUDEV +#include "udev_input.h" +#endif + // Input event values from linux/input.h #define RELEASED 0 #define PRESSED 1 @@ -62,7 +66,10 @@ static void handle_signal(int sig) { } // Input device management -#if KEYMON_INPUT_COUNT > 1 +#ifdef KEYMON_USE_LIBUDEV +static int inputs[UDEV_MAX_DEVICES]; +static int input_count = 0; +#elif KEYMON_INPUT_COUNT > 1 static int inputs[KEYMON_INPUT_COUNT]; #else static int input_fd = 0; @@ -326,7 +333,15 @@ int main(int argc, char* argv[]) { InitSettings(); // Open input device(s) -#if KEYMON_INPUT_COUNT > 1 +#ifdef KEYMON_USE_LIBUDEV + // Use libudev for dynamic input device discovery + input_count = udev_open_all_inputs(inputs); + if (input_count == 0) { + LOG_warn("No input devices found via udev\n"); + } else { + LOG_info("Opened %d input devices via udev\n", input_count); + } +#elif KEYMON_INPUT_COUNT > 1 // Custom device paths (if platform defines them) #if defined(KEYMON_INPUT_DEVICE_0) inputs[0] = open(KEYMON_INPUT_DEVICE_0, O_RDONLY | O_NONBLOCK | O_CLOEXEC); @@ -413,7 +428,10 @@ int main(int argc, char* argv[]) { ignore_stale = 1; // Read and process all available input events -#if KEYMON_INPUT_COUNT > 1 +#ifdef KEYMON_USE_LIBUDEV + for (int i = 0; i < input_count; i++) { + while (read(inputs[i], &ev, sizeof(ev)) == sizeof(ev)) { +#elif KEYMON_INPUT_COUNT > 1 for (int i = 0; i < KEYMON_INPUT_COUNT; i++) { while (read(inputs[i], &ev, sizeof(ev)) == sizeof(ev)) { #else @@ -496,7 +514,7 @@ int main(int argc, char* argv[]) { default: break; } -#if KEYMON_INPUT_COUNT > 1 +#if defined(KEYMON_USE_LIBUDEV) || KEYMON_INPUT_COUNT > 1 } #endif } @@ -605,6 +623,9 @@ int main(int argc, char* argv[]) { } // Clean shutdown +#ifdef KEYMON_USE_LIBUDEV + udev_close_all(inputs, UDEV_MAX_DEVICES); +#endif QuitSettings(); log_close(); diff --git a/workspace/retroid/platform/Makefile.env b/workspace/retroid/platform/Makefile.env index 0e530353..a281fb89 100644 --- a/workspace/retroid/platform/Makefile.env +++ b/workspace/retroid/platform/Makefile.env @@ -3,4 +3,7 @@ # ARCH = -mtune=cortex-a77.cortex-a55 -march=armv8.2-a+crypto -O3 ARCH = -mtune=cortex-a55 -march=armv8.2-a -O3 LIBS = -flto -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 -SDL = SDL2 \ No newline at end of file +SDL = SDL2 + +# Dynamic input device discovery via libudev +HAS_LIBUDEV = 1 \ No newline at end of file diff --git a/workspace/retroid/platform/platform.c b/workspace/retroid/platform/platform.c index 45ec4546..902267ab 100644 --- a/workspace/retroid/platform/platform.c +++ b/workspace/retroid/platform/platform.c @@ -45,6 +45,7 @@ #include "gl_video.h" #include "render_sdl2.h" #include "scaler.h" +#include "udev_input.h" // Paths for HDMI detection #define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" @@ -328,41 +329,9 @@ int PLAT_supportsOverscan(void) { #define RAW_MENU1 RAW_L3 #define RAW_MENU2 RAW_R3 -#define INPUT_COUNT 4 -static int inputs[INPUT_COUNT]; - -/** - * Find input device by name in sysfs. - * Returns device path (e.g., "/dev/input/event2") or NULL if not found. - */ -static char* findInputDeviceByName(const char* device_name) { - static char device_path[64]; - char name_path[128]; - char name[256]; - - // Search /sys/class/input/event* for matching device name - for (int i = 0; i < 10; i++) { - snprintf(name_path, sizeof(name_path), "/sys/class/input/event%d/device/name", i); - FILE* f = fopen(name_path, "r"); - if (!f) - continue; - - if (fgets(name, sizeof(name), f)) { - // Strip newline - name[strcspn(name, "\n")] = 0; - if (strcmp(name, device_name) == 0) { - snprintf(device_path, sizeof(device_path), "/dev/input/event%d", i); - fclose(f); - LOG_debug("Found '%s' at %s\n", device_name, device_path); - return device_path; - } - } - fclose(f); - } - - LOG_warn("Device '%s' not found in /sys/class/input/\n", device_name); - return NULL; -} +// Input devices discovered via libudev +static int inputs[UDEV_MAX_DEVICES]; +static int input_count = 0; /** * Disable RGB LEDs on analog sticks. @@ -395,36 +364,20 @@ void PLAT_initInput(void) { // Disable stick LEDs on startup disableStickLEDs(); - // Find Retroid Pocket Gamepad dynamically by name - char* gamepad_path = findInputDeviceByName("Retroid Pocket Gamepad"); - if (gamepad_path) { - inputs[0] = open(gamepad_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (inputs[0] < 0) - LOG_warn("Failed to open gamepad at %s\n", gamepad_path); + // Use libudev to discover all input devices dynamically. + // This is more robust than hardcoding event device paths and handles + // devices appearing in any order during boot. + input_count = udev_open_all_inputs(inputs); + if (input_count == 0) { + LOG_warn("No input devices found via udev\n"); } else { - // Fallback: try event0 (shouldn't happen on Retroid) - inputs[0] = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (inputs[0] < 0) - LOG_warn("Failed to open fallback /dev/input/event0\n"); - } - - // Open additional event devices for power button, volume, etc. - // Power button location varies, so open multiple devices like RGB30 - inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[2] = open("/dev/input/event2", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[3] = open("/dev/input/event3", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - - for (int i = 1; i < INPUT_COUNT; i++) { - if (inputs[i] < 0) - LOG_debug("Could not open /dev/input/event%d\n", i); + LOG_info("Opened %d input devices via udev\n", input_count); } } void PLAT_quitInput(void) { - for (int i = 0; i < INPUT_COUNT; i++) { - if (inputs[i] >= 0) - close(inputs[i]); - } + udev_close_all(inputs, UDEV_MAX_DEVICES); + input_count = 0; } // from which has BTN_ constants that conflict with platform.h @@ -444,10 +397,8 @@ void PLAT_pollInput(void) { int input; static struct input_event event; - for (int i = 0; i < INPUT_COUNT; i++) { + for (int i = 0; i < input_count; i++) { input = inputs[i]; - if (input < 0) - continue; while (read(input, &event, sizeof(event)) == sizeof(event)) { if (event.type != EV_KEY && event.type != EV_ABS) continue; @@ -538,10 +489,8 @@ void PLAT_pollInput(void) { int PLAT_shouldWake(void) { int input; static struct input_event event; - for (int i = 0; i < INPUT_COUNT; i++) { + for (int i = 0; i < input_count; i++) { input = inputs[i]; - if (input < 0) - continue; while (read(input, &event, sizeof(event)) == sizeof(event)) { if (event.type == EV_KEY && event.code == RAW_POWER && event.value == 0) return 1; diff --git a/workspace/retroid/platform/platform.h b/workspace/retroid/platform/platform.h index 9af24d4c..fba2f00e 100644 --- a/workspace/retroid/platform/platform.h +++ b/workspace/retroid/platform/platform.h @@ -227,18 +227,10 @@ #define KEYMON_HAS_JACK 0 // Headphone jack detection TBD -// Retroid gamepad device path is dynamic - search multiple event devices -// The "Retroid Pocket Gamepad" can be on different eventN depending on boot order -// Volume buttons may be on separate device, so check more devices -#define KEYMON_INPUT_COUNT 8 -#define KEYMON_INPUT_DEVICE_0 "/dev/input/event0" -#define KEYMON_INPUT_DEVICE_1 "/dev/input/event1" -#define KEYMON_INPUT_DEVICE_2 "/dev/input/event2" -#define KEYMON_INPUT_DEVICE_3 "/dev/input/event3" -#define KEYMON_INPUT_DEVICE_4 "/dev/input/event4" -#define KEYMON_INPUT_DEVICE_5 "/dev/input/event5" -#define KEYMON_INPUT_DEVICE_6 "/dev/input/event6" -#define KEYMON_INPUT_DEVICE_7 "/dev/input/event7" +// Use libudev for dynamic input device discovery +// This is more robust than hardcoding event paths and handles +// devices appearing in any order during boot +#define KEYMON_USE_LIBUDEV 1 /////////////////////////////// diff --git a/workspace/rgb30/platform/Makefile.env b/workspace/rgb30/platform/Makefile.env index 70ba8f64..e8b18e9d 100644 --- a/workspace/rgb30/platform/Makefile.env +++ b/workspace/rgb30/platform/Makefile.env @@ -1,4 +1,7 @@ # rgb30 - LessOS toolchain ARCH = -mtune=cortex-a55 -march=armv8.2-a -O3 LIBS = -flto -lrga -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 -SDL = SDL2 \ No newline at end of file +SDL = SDL2 + +# Dynamic input device discovery via libudev +HAS_LIBUDEV = 1 \ No newline at end of file diff --git a/workspace/rgb30/platform/platform.c b/workspace/rgb30/platform/platform.c index d05a2dcf..dcaea5f7 100644 --- a/workspace/rgb30/platform/platform.c +++ b/workspace/rgb30/platform/platform.c @@ -39,6 +39,7 @@ #include "gl_video.h" #include "render_sdl2.h" #include "scaler.h" +#include "udev_input.h" /////////////////////////////// // Video - Using shared SDL2 backend @@ -151,25 +152,25 @@ int PLAT_supportsOverscan(void) { #define RAW_MENU1 RAW_L3 #define RAW_MENU2 RAW_R3 -#define INPUT_COUNT 4 -static int inputs[INPUT_COUNT]; +// Input devices discovered via libudev +static int inputs[UDEV_MAX_DEVICES]; +static int input_count = 0; void PLAT_initInput(void) { - inputs[0] = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[2] = open("/dev/input/event2", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[3] = open("/dev/input/event3", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - - for (int i = 0; i < INPUT_COUNT; i++) { - if (inputs[i] < 0) - LOG_warn("Failed to open /dev/input/event%d\n", i); + // Use libudev to discover all input devices dynamically. + // This is more robust than hardcoding event device paths and handles + // devices appearing in any order during boot. + input_count = udev_open_all_inputs(inputs); + if (input_count == 0) { + LOG_warn("No input devices found via udev\n"); + } else { + LOG_info("Opened %d input devices via udev\n", input_count); } } void PLAT_quitInput(void) { - for (int i = 0; i < INPUT_COUNT; i++) { - close(inputs[i]); - } + udev_close_all(inputs, UDEV_MAX_DEVICES); + input_count = 0; } struct input_event { @@ -188,7 +189,7 @@ void PLAT_pollInput(void) { int input; static struct input_event event; - for (int i = 0; i < INPUT_COUNT; i++) { + for (int i = 0; i < input_count; i++) { input = inputs[i]; while (read(input, &event, sizeof(event)) == sizeof(event)) { if (event.type != EV_KEY && event.type != EV_ABS) @@ -275,7 +276,7 @@ void PLAT_pollInput(void) { int PLAT_shouldWake(void) { int input; static struct input_event event; - for (int i = 0; i < INPUT_COUNT; i++) { + for (int i = 0; i < input_count; i++) { input = inputs[i]; while (read(input, &event, sizeof(event)) == sizeof(event)) { if (event.type == EV_KEY && event.code == RAW_POWER && event.value == 0) diff --git a/workspace/rgb30/platform/platform.h b/workspace/rgb30/platform/platform.h index 189df017..1ae37dbc 100644 --- a/workspace/rgb30/platform/platform.h +++ b/workspace/rgb30/platform/platform.h @@ -206,14 +206,10 @@ #define KEYMON_HAS_JACK 1 #define KEYMON_JACK_STATE_PATH "/sys/bus/platform/devices/singleadc-joypad/hp" -// Uses event0-4 plus js0 (joystick) for menu button detection -#define KEYMON_INPUT_COUNT 6 -#define KEYMON_INPUT_DEVICE_0 "/dev/input/event0" -#define KEYMON_INPUT_DEVICE_1 "/dev/input/event1" -#define KEYMON_INPUT_DEVICE_2 "/dev/input/event2" -#define KEYMON_INPUT_DEVICE_3 "/dev/input/event3" -#define KEYMON_INPUT_DEVICE_4 "/dev/input/event4" -#define KEYMON_INPUT_DEVICE_5 "/dev/input/js0" +// Use libudev for dynamic input device discovery +// This is more robust than hardcoding event paths and handles +// devices appearing in any order during boot +#define KEYMON_USE_LIBUDEV 1 /////////////////////////////// From d9bca90dae7afd5f65bee319e78fb16dded95b13 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Thu, 1 Jan 2026 23:21:34 -0800 Subject: [PATCH 09/10] Clean up retroid branch: rename SWAN to SWST, remove .bak files. - Rename SwanStation core from SWAN to SWST to avoid WonderSwan confusion - Remove backup files (keymon.c.bak, platform.c.bak) - Add missing trailing newlines to Makefiles --- .../configs/base/{SWAN => SWST}/default.cfg | 0 workspace/all/paks/Emus/cores.json | 2 +- workspace/all/utils/keymon/keymon.c.bak | 612 ---------------- workspace/retroid/platform/platform.c.bak | 669 ------------------ 4 files changed, 1 insertion(+), 1282 deletions(-) rename workspace/all/paks/Emus/configs/base/{SWAN => SWST}/default.cfg (100%) delete mode 100644 workspace/all/utils/keymon/keymon.c.bak delete mode 100644 workspace/retroid/platform/platform.c.bak diff --git a/workspace/all/paks/Emus/configs/base/SWAN/default.cfg b/workspace/all/paks/Emus/configs/base/SWST/default.cfg similarity index 100% rename from workspace/all/paks/Emus/configs/base/SWAN/default.cfg rename to workspace/all/paks/Emus/configs/base/SWST/default.cfg diff --git a/workspace/all/paks/Emus/cores.json b/workspace/all/paks/Emus/cores.json index 8aaa9ccd..5f30a3a8 100644 --- a/workspace/all/paks/Emus/cores.json +++ b/workspace/all/paks/Emus/cores.json @@ -102,7 +102,7 @@ } ] }, - "SWAN": { + "SWST": { "name": "Sony PlayStation (SwanStation)", "core": "swanstation_libretro", "resolution": "320x240", diff --git a/workspace/all/utils/keymon/keymon.c.bak b/workspace/all/utils/keymon/keymon.c.bak deleted file mode 100644 index d5af22cb..00000000 --- a/workspace/all/utils/keymon/keymon.c.bak +++ /dev/null @@ -1,612 +0,0 @@ -/** - * keymon.c - Unified hardware button monitoring daemon - * - * Background daemon that monitors physical button presses and handles system-level - * shortcuts across all supported handheld devices. - * - * Features (platform-dependent, configured via keymon_config.h): - * - Volume and brightness control through button combinations - * - HDMI output detection and routing - * - Headphone jack detection and routing - * - Multiple input device support - * - * Button combinations (most platforms): - * - MENU+PLUS/MINUS: Adjust brightness - * - PLUS/MINUS alone: Adjust volume - * - * Alternative (trimuismart): - * - START+R1/L1: Adjust brightness - * - SELECT+R1/L1: Adjust volume - * - * Runs continuously at 60Hz polling input devices for button events. - * Implements repeat functionality (initial 300ms delay, then 100ms interval). - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "platform.h" -#include "keymon_config.h" -#include "log.h" -#include - -#if KEYMON_HAS_HDMI || KEYMON_HAS_JACK || defined(KEYMON_HAS_MUTE) -#include -#endif - -// Input event values from linux/input.h -#define RELEASED 0 -#define PRESSED 1 -#define REPEAT 2 - -// Shutdown flag for clean exit -static volatile sig_atomic_t running = 1; - -/** - * Signal handler for clean shutdown. - */ -static void handle_signal(int sig) { - (void)sig; - running = 0; -} - -// Input device management -#if KEYMON_INPUT_COUNT > 1 -static int inputs[KEYMON_INPUT_COUNT]; -#else -static int input_fd = 0; -#endif - -static struct input_event ev; - -#if KEYMON_HAS_HDMI || KEYMON_HAS_JACK -static pthread_t ports_pt; -#endif - -#ifdef KEYMON_HAS_MUTE -static pthread_t mute_pt; -#endif - -/** - * Reads an integer value from a sysfs file. - * - * Used for reading hardware state from kernel interfaces. - * - * @param path Path to sysfs file - * @return Integer value read from file, or 0 if file cannot be opened - */ -static int getInt(char* path) { - int i = 0; - FILE* file = fopen(path, "r"); - if (file != NULL) { - fscanf(file, "%i", &i); - fclose(file); - } - return i; -} - -#if KEYMON_HAS_HDMI && defined(KEYMON_HDMI_USE_STRING) -/** - * Reads a text file into a buffer. - * - * Used for HDMI status detection on some platforms. - * - * @param path Path to file - * @param buffer Output buffer - * @param buffer_size Size of output buffer - */ -static void getFile(char* path, char* buffer, size_t buffer_size) { - buffer[0] = '\0'; // Initialize in case fopen fails - FILE* file = fopen(path, "r"); - if (file) { - fseek(file, 0L, SEEK_END); - size_t size = ftell(file); - if (size > buffer_size - 1) - size = buffer_size - 1; - rewind(file); - fread(buffer, sizeof(char), size, file); - fclose(file); - buffer[size] = '\0'; - } -} - -/** - * Checks if two strings are exactly equal. - * - * @param str1 First string - * @param str2 Second string - * @return 1 if strings match, 0 otherwise - */ -static int exactMatch(char* str1, char* str2) { - size_t len1 = strlen(str1); - if (len1 != strlen(str2)) - return 0; - return (strncmp(str1, str2, len1) == 0); -} -#endif - -#if KEYMON_HAS_JACK -/** - * Checks if headphones are plugged in. - * - * Logic depends on platform (some use inverted GPIO values). - * - * @return 1 if headphones connected, 0 otherwise - */ -static int JACK_enabled(void) { -#ifdef KEYMON_JACK_INVERTED - return !getInt(KEYMON_JACK_STATE_PATH); -#else - return getInt(KEYMON_JACK_STATE_PATH); -#endif -} -#endif - -#if KEYMON_HAS_HDMI -/** - * Checks if HDMI is connected. - * - * Implementation varies by platform: - * - extcon interface: Read integer value - * - DRM connector: Read "connected\n" string - * - * @return 1 if HDMI connected, 0 otherwise - */ -static int HDMI_enabled(void) { -#ifdef KEYMON_HDMI_USE_STRING - char value[64]; - getFile(KEYMON_HDMI_STATE_PATH, value, 64); - return exactMatch(value, "connected\n"); -#else - return getInt(KEYMON_HDMI_STATE_PATH); -#endif -} -#endif - -#if KEYMON_HAS_HDMI && KEYMON_HAS_JACK -/** - * Background thread that monitors headphone jack and HDMI state. - * - * Polls the hardware interfaces every second and updates audio/video - * routing when states change. - * - * @param arg Thread argument (unused) - * @return Never returns (runs infinite loop) - */ -static void* watchPorts(void* arg) { - int has_jack, had_jack; - int has_hdmi, had_hdmi; - - // Initialize state - has_jack = had_jack = JACK_enabled(); - has_hdmi = had_hdmi = HDMI_enabled(); - SetJack(has_jack); - SetHDMI(has_hdmi); - - while (1) { - sleep(1); - - // Check for headphone jack state changes - has_jack = JACK_enabled(); - if (had_jack != has_jack) { - had_jack = has_jack; - SetJack(has_jack); - } - - // Check for HDMI state changes - has_hdmi = HDMI_enabled(); - if (had_hdmi != has_hdmi) { - had_hdmi = has_hdmi; - SetHDMI(has_hdmi); - } - } - - return 0; -} -#elif KEYMON_HAS_HDMI -/** - * Background thread that monitors HDMI state only. - * - * @param arg Thread argument (unused) - * @return Never returns (runs infinite loop) - */ -static void* watchHDMI(void* arg) { - int has_hdmi, had_hdmi; - - // Initialize HDMI state - has_hdmi = had_hdmi = HDMI_enabled(); - SetHDMI(has_hdmi); - - while (1) { - sleep(1); - - // Check for HDMI state changes - has_hdmi = HDMI_enabled(); - if (had_hdmi != has_hdmi) { - had_hdmi = has_hdmi; - SetHDMI(has_hdmi); - } - } - - return 0; -} -#elif KEYMON_HAS_JACK -/** - * Background thread that monitors headphone jack state only. - * - * @param arg Thread argument (unused) - * @return Never returns (runs infinite loop) - */ -static void* watchJack(void* arg) { - int has_jack, had_jack; - - // Initialize jack state - has_jack = had_jack = JACK_enabled(); - SetJack(has_jack); - - while (1) { - sleep(1); - - // Check for jack state changes - has_jack = JACK_enabled(); - if (had_jack != has_jack) { - had_jack = has_jack; - SetJack(has_jack); - } - } - - return 0; -} -#endif - -#ifdef KEYMON_HAS_MUTE -/** - * Background thread that monitors mute switch state (tg5040). - * - * Polls the GPIO mute switch every second and updates audio routing. - * - * @param arg Thread argument (unused) - * @return Never returns (runs infinite loop) - */ -static void* watchMute(void* arg) { - int is_muted, was_muted; - - // Initialize mute state - is_muted = was_muted = getInt(KEYMON_MUTE_STATE_PATH); - SetMute(is_muted); - - while (1) { - sleep(1); - - // Check for mute state changes - is_muted = getInt(KEYMON_MUTE_STATE_PATH); - if (was_muted != is_muted) { - was_muted = is_muted; - SetMute(is_muted); - } - } - - return 0; -} -#endif - -/** - * Main event loop for hardware button monitoring. - * - * Continuously polls input device(s) for button events and handles: - * - Volume control (PLUS/MINUS buttons) - * - Brightness control (MENU+PLUS/MINUS or START+R1/L1) - * - * Implements repeat functionality (initial 300ms delay, then 100ms interval) - * and ignores stale input after system sleep (> 1 second gap). - * - * @param argc Argument count (unused) - * @param argv Argument values (unused) - * @return 0 on clean shutdown (SIGTERM/SIGINT) - */ -int main(int argc, char* argv[]) { - // Initialize logging (reads LOG_FILE environment variable) - log_open(NULL); - - // Register signal handlers for clean shutdown - signal(SIGTERM, handle_signal); - signal(SIGINT, handle_signal); - - InitSettings(); - - // Open input device(s) -#if KEYMON_INPUT_COUNT > 1 - // Custom device paths (if platform defines them) -#if defined(KEYMON_INPUT_DEVICE_0) - inputs[0] = open(KEYMON_INPUT_DEVICE_0, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#if KEYMON_INPUT_COUNT > 1 && defined(KEYMON_INPUT_DEVICE_1) - inputs[1] = open(KEYMON_INPUT_DEVICE_1, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#if KEYMON_INPUT_COUNT > 2 && defined(KEYMON_INPUT_DEVICE_2) - inputs[2] = open(KEYMON_INPUT_DEVICE_2, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#if KEYMON_INPUT_COUNT > 3 && defined(KEYMON_INPUT_DEVICE_3) - inputs[3] = open(KEYMON_INPUT_DEVICE_3, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#if KEYMON_INPUT_COUNT > 4 && defined(KEYMON_INPUT_DEVICE_4) - inputs[4] = open(KEYMON_INPUT_DEVICE_4, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#if KEYMON_INPUT_COUNT > 5 && defined(KEYMON_INPUT_DEVICE_5) - inputs[5] = open(KEYMON_INPUT_DEVICE_5, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#if KEYMON_INPUT_COUNT > 6 && defined(KEYMON_INPUT_DEVICE_6) - inputs[6] = open(KEYMON_INPUT_DEVICE_6, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#if KEYMON_INPUT_COUNT > 7 && defined(KEYMON_INPUT_DEVICE_7) - inputs[7] = open(KEYMON_INPUT_DEVICE_7, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif -#else - // Sequential device paths (default) - for (int i = 0; i < KEYMON_INPUT_COUNT; i++) { - char path[32]; - snprintf(path, sizeof(path), "/dev/input/event%d", i); - inputs[i] = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - } -#endif -#else - input_fd = open(KEYMON_INPUT_DEVICE, O_RDONLY | O_NONBLOCK | O_CLOEXEC); -#endif - - // Start hardware monitoring threads if enabled -#if KEYMON_HAS_HDMI && KEYMON_HAS_JACK - pthread_create(&ports_pt, NULL, watchPorts, NULL); -#elif KEYMON_HAS_HDMI - pthread_create(&ports_pt, NULL, watchHDMI, NULL); -#elif KEYMON_HAS_JACK - pthread_create(&ports_pt, NULL, watchJack, NULL); -#endif - -#ifdef KEYMON_HAS_MUTE - pthread_create(&mute_pt, NULL, watchMute, NULL); -#endif - - uint32_t pressed; // Button state: PRESSED (1) or RELEASED (0) - - // Button state tracking -#if KEYMON_USE_SELECT_START - uint32_t start_pressed = 0; - uint32_t select_pressed = 0; -#else - uint32_t menu_pressed = 0; -#endif - - // Track PLUS button state for repeat handling - uint32_t up_pressed = 0; - uint32_t up_just_pressed = 0; - uint32_t up_repeat_at = 0; - - // Track MINUS button state for repeat handling - uint32_t down_pressed = 0; - uint32_t down_just_pressed = 0; - uint32_t down_repeat_at = 0; - - uint32_t now; - uint32_t then = 0; - uint8_t ignore_stale = 0; - struct timeval tod; - - gettimeofday(&tod, NULL); - then = tod.tv_sec * 1000 + tod.tv_usec / 1000; - - while (running) { - gettimeofday(&tod, NULL); - now = tod.tv_sec * 1000 + tod.tv_usec / 1000; - - // Detect stale input after system sleep (> 1 second gap) - if (now - then > 1000) - ignore_stale = 1; - - // Read and process all available input events -#if KEYMON_INPUT_COUNT > 1 - for (int i = 0; i < KEYMON_INPUT_COUNT; i++) { - while (read(inputs[i], &ev, sizeof(ev)) == sizeof(ev)) { -#else - while (read(input_fd, &ev, sizeof(ev)) == sizeof(ev)) { -#endif - // Skip stale input after system sleep - if (ignore_stale) - continue; - - // Process key events - if (ev.type == EV_KEY) { - pressed = ev.value; - } -#ifdef KEYMON_HAS_JACK_SWITCH - // Process switch events for headphone jack (tg5040, zero28) - else if (ev.type == EV_SW && ev.code == 2) { // SW_HEADPHONE_INSERT - SetJack(ev.value); - continue; - } -#endif - else { - continue; // Skip non-key/non-switch events - } - - // Process hardware button events - switch (ev.code) { -#if KEYMON_USE_SELECT_START - case KEYMON_BUTTON_START: - start_pressed = pressed; - break; - case KEYMON_BUTTON_SELECT: - select_pressed = pressed; - break; - case KEYMON_BUTTON_R1: - // R1 button (brightness/volume up when combined) - up_pressed = up_just_pressed = pressed; - LOG_debug("PLUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); - if (pressed) - up_repeat_at = now + 300; // 300ms initial delay - break; - case KEYMON_BUTTON_L1: - // L1 button (brightness/volume down when combined) - down_pressed = down_just_pressed = pressed; - LOG_debug("MINUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); - if (pressed) - down_repeat_at = now + 300; // 300ms initial delay - break; -#ifdef KEYMON_HAS_VOLUME_QUIRK - // m17: Hardware volume buttons trigger quirk (re-apply volume) - case KEYMON_BUTTON_PLUS: - case KEYMON_BUTTON_MINUS: - system("echo 0 > /sys/devices/platform/0gpio-keys/scaled"); - SetVolume(GetVolume()); - break; -#endif -#else - case KEYMON_BUTTON_MENU: -#if KEYMON_BUTTON_MENU_ALT != -1 - case KEYMON_BUTTON_MENU_ALT: -#endif -#ifdef KEYMON_BUTTON_MENU_ALT2 - case KEYMON_BUTTON_MENU_ALT2: -#endif - menu_pressed = pressed; - LOG_debug("MENU button: %s", pressed ? "pressed" : "released"); - break; - case KEYMON_BUTTON_PLUS: - up_pressed = up_just_pressed = pressed; - LOG_debug("PLUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); - if (pressed) - up_repeat_at = now + 300; - break; - case KEYMON_BUTTON_MINUS: - down_pressed = down_just_pressed = pressed; - LOG_debug("MINUS button: %s (code=%d)", pressed ? "pressed" : "released", ev.code); - if (pressed) - down_repeat_at = now + 300; - break; -#endif - default: - break; - } -#if KEYMON_INPUT_COUNT > 1 - } -#endif - } - - // Reset button state after ignoring stale input - if (ignore_stale) { -#if KEYMON_USE_SELECT_START - start_pressed = 0; - select_pressed = 0; -#else - menu_pressed = 0; -#endif - up_pressed = up_just_pressed = 0; - down_pressed = down_just_pressed = 0; - up_repeat_at = 0; - down_repeat_at = 0; - } - - // Update timing for next iteration - then = now; - ignore_stale = 0; - - // Handle PLUS/R1 button (initial press or repeat after delay) - if (up_just_pressed || (up_pressed && now >= up_repeat_at)) { - uint32_t level; -#if KEYMON_USE_SELECT_START - if (start_pressed) { - // START+R1: Brightness up - level = GetBrightness(); - if (level < KEYMON_BRIGHTNESS_MAX) - SetBrightness(level + 1); - } else if (select_pressed) { - // SELECT+R1: Volume up - level = GetVolume(); - if (level < KEYMON_VOLUME_MAX) - SetVolume(level + 1); - } -#else - if (menu_pressed) { - // MENU+PLUS: Brightness up - level = GetBrightness(); - if (level < KEYMON_BRIGHTNESS_MAX) - SetBrightness(level + 1); - } else { - // PLUS alone: Volume up -#ifdef KEYMON_HAS_VOLUME_QUIRK - // m17 hardware volume button quirk - system("echo 0 > /sys/devices/platform/0gpio-keys/scaled"); - SetVolume(GetVolume()); -#else - level = GetVolume(); - if (level < KEYMON_VOLUME_MAX) - SetVolume(level + 1); -#endif - } -#endif - - if (up_just_pressed) - up_just_pressed = 0; - else - up_repeat_at += 100; // 100ms repeat interval - } - - // Handle MINUS/L1 button (initial press or repeat after delay) - if (down_just_pressed || (down_pressed && now >= down_repeat_at)) { - uint32_t level; -#if KEYMON_USE_SELECT_START - if (start_pressed) { - // START+L1: Brightness down - level = GetBrightness(); - if (level > KEYMON_BRIGHTNESS_MIN) - SetBrightness(level - 1); - } else if (select_pressed) { - // SELECT+L1: Volume down - level = GetVolume(); - if (level > KEYMON_VOLUME_MIN) - SetVolume(level - 1); - } -#else - if (menu_pressed) { - // MENU+MINUS: Brightness down - level = GetBrightness(); - if (level > KEYMON_BRIGHTNESS_MIN) - SetBrightness(level - 1); - } else { - // MINUS alone: Volume down -#ifdef KEYMON_HAS_VOLUME_QUIRK - // m17 hardware volume button quirk - system("echo 0 > /sys/devices/platform/0gpio-keys/scaled"); - SetVolume(GetVolume()); -#else - level = GetVolume(); - if (level > KEYMON_VOLUME_MIN) - SetVolume(level - 1); -#endif - } -#endif - - if (down_just_pressed) - down_just_pressed = 0; - else - down_repeat_at += 100; // 100ms repeat interval - } - - usleep(16666); // 60Hz polling rate - } - - // Clean shutdown - QuitSettings(); - log_close(); - - return 0; -} diff --git a/workspace/retroid/platform/platform.c.bak b/workspace/retroid/platform/platform.c.bak deleted file mode 100644 index 7b634bd8..00000000 --- a/workspace/retroid/platform/platform.c.bak +++ /dev/null @@ -1,669 +0,0 @@ -/** - * platform.c - Retroid Pocket SM8250 platform implementation - * - * REFACTORED VERSION - Uses shared render_sdl2 backend - * - * Supports multiple device variants in the Retroid Pocket family: - * - Retroid Pocket 5 (1920x1080) - * - Retroid Pocket Flip 2 (1920x1080) - * - Retroid Pocket Mini V2 (1240x1080) - * - * Hardware features: - * - SDL2-based video with HDMI support (via render_sdl2) - * - Retroid Pocket Gamepad via serdev kernel driver - * - Haptic feedback via pmi8998_haptics - * - Display effects (scanlines, grid) - * - * Device detection via LESSOS_DEVICE environment variable. - * - * LessOS/ROCKNIX upstream references (in LessOS repository): - * - Retroid gamepad kernel driver: devices/SM8250/patches/linux/0008-retroid-gamepad.patch - * - Input discovery pattern: packages/sysutils/system-utils/sources/scripts/input_sense - * - Button mappings: packages/emulators/libretro/retroarch/retroarch-joypads/gamepads/Retroid Pocket Gamepad.cfg - * - UDEV rules: devices/SM8250/filesystem/usr/lib/udev/rules.d/99-retroid-pocket.rules - */ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include "api.h" -#include "defines.h" -#include "platform.h" -#include "utils.h" - -#include "gl_video.h" -#include "render_sdl2.h" -#include "scaler.h" - -// Paths for HDMI detection -#define HDMI_STATE_PATH "/sys/class/extcon/hdmi/cable.0/state" - -/////////////////////////////// -// Device Registry and Variant Configuration -/////////////////////////////// - -// Device registry - all known devices that work with this platform -static const DeviceInfo retroid_devices[] = { - // 1920x1080 FHD devices - {.device_id = "pocket5", .display_name = "Pocket 5", .manufacturer = "Retroid"}, - {.device_id = "flip2", .display_name = "Pocket Flip 2", .manufacturer = "Retroid"}, - - // 1240x1080 Mini V2 - {.device_id = "miniv2", .display_name = "Pocket Mini V2", .manufacturer = "Retroid"}, - - // Sentinel - {NULL, NULL, NULL}}; - -// Variant configuration table -typedef struct { - VariantType variant; - int screen_width; - int screen_height; - float screen_diagonal_default; - uint32_t hw_features; -} VariantConfig; - -static const VariantConfig retroid_variants[] = { - {.variant = VARIANT_RETROID_FHD, - .screen_width = 1920, - .screen_height = 1080, - .screen_diagonal_default = 5.5f, - .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, - {.variant = VARIANT_RETROID_MINI_V2, - .screen_width = 1240, - .screen_height = 1080, - .screen_diagonal_default = 3.92f, - .hw_features = HW_FEATURE_NEON | HW_FEATURE_ANALOG | HW_FEATURE_RUMBLE}, - {.variant = VARIANT_NONE} // Sentinel -}; - -// Device-to-variant mapping -typedef struct { - const char* device_string; // What to look for in LESSOS_DEVICE - VariantType variant; // Which variant config to use - const DeviceInfo* device; // Which device info to use - float screen_diagonal; // Override if different from variant default (0 = use default) -} DeviceVariantMap; - -static const DeviceVariantMap retroid_device_map[] = { - // 1920x1080 FHD devices - VARIANT_RETROID_FHD - {"Retroid Pocket 5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, - {"Pocket 5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, - {"RP5", VARIANT_RETROID_FHD, &retroid_devices[0], 5.5f}, - - {"Retroid Pocket Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, - {"Pocket Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, - {"Flip 2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, - {"RPF2", VARIANT_RETROID_FHD, &retroid_devices[1], 5.5f}, - - // 1240x1080 Mini V2 - VARIANT_RETROID_MINI_V2 - {"Retroid Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - {"Pocket Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - {"Mini V2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - {"RPMV2", VARIANT_RETROID_MINI_V2, &retroid_devices[2], 3.92f}, - - // Sentinel - {NULL, VARIANT_NONE, NULL, 0.0f}}; - -static const VariantConfig* getVariantConfig(VariantType variant) { - for (int i = 0; retroid_variants[i].variant != VARIANT_NONE; i++) { - if (retroid_variants[i].variant == variant) - return &retroid_variants[i]; - } - return NULL; -} - -void PLAT_detectVariant(PlatformVariant* v) { - v->platform = PLATFORM; - v->has_hdmi = 1; - - // Read device from LessOS environment variable - char* device = getenv("LESSOS_DEVICE"); - if (!device) { - LOG_debug("LESSOS_DEVICE not set, defaulting to Pocket 5\n"); - device = "Retroid Pocket 5"; // Fallback to default - } else { - LOG_debug("LESSOS_DEVICE=%s\n", device); - } - - // Look up device in mapping table (substring match) - const DeviceVariantMap* map = NULL; - for (int i = 0; retroid_device_map[i].device_string != NULL; i++) { - if (containsString((char*)device, (char*)retroid_device_map[i].device_string)) { - LOG_debug("Matched device: %s (table entry: %s)\n", device, - retroid_device_map[i].device_string); - map = &retroid_device_map[i]; - break; - } - } - - // Fallback to default if not found - if (!map) { - LOG_warn("Unknown device '%s', defaulting to Pocket 5\n", device); - map = &retroid_device_map[0]; - } - - // Set device info - v->device = map->device; - v->variant = map->variant; - - // Apply variant configuration - const VariantConfig* config = getVariantConfig(map->variant); - if (config) { - v->screen_width = config->screen_width; - v->screen_height = config->screen_height; - v->screen_diagonal = - map->screen_diagonal > 0 ? map->screen_diagonal : config->screen_diagonal_default; - v->hw_features = config->hw_features; - } - - // Check for HDMI connection (runtime override) - v->hdmi_active = getInt(HDMI_STATE_PATH); - if (v->hdmi_active) { - v->screen_width = HDMI_WIDTH; - v->screen_height = HDMI_HEIGHT; - } - - LOG_info("Detected device: %s %s (%s variant, %dx%d, %.1f\")\n", v->device->manufacturer, - v->device->display_name, v->variant == VARIANT_RETROID_MINI_V2 ? "Mini V2" : "FHD", - v->screen_width, v->screen_height, v->screen_diagonal); -} - -/////////////////////////////// -// Video - Using shared SDL2 backend -/////////////////////////////// - -static SDL2_RenderContext vid_ctx; - -static SDL2_Config vid_config = { - // No rotation needed (landscape display) - .auto_rotate = 0, - .rotate_cw = 0, - .rotate_null_center = 0, - // Display features - .has_hdmi = 1, - .default_sharpness = SHARPNESS_SOFT, -}; - -SDL_Surface* PLAT_initVideo(void) { - // Detect device variant - PLAT_detectVariant(&platform_variant); - - // Use detected resolution (may be overridden by HDMI) - int w = FIXED_WIDTH; - int h = FIXED_HEIGHT; - vid_ctx.on_hdmi = platform_variant.hdmi_active; - - return SDL2_initVideo(&vid_ctx, w, h, &vid_config); -} - -void PLAT_quitVideo(void) { - SDL2_quitVideo(&vid_ctx); -} - -void PLAT_clearVideo(SDL_Surface* screen) { - SDL2_clearVideo(&vid_ctx); -} - -void PLAT_clearAll(void) { - SDL2_clearAll(&vid_ctx); -} - -SDL_Surface* PLAT_resizeVideo(int w, int h, int p) { - return SDL2_resizeVideo(&vid_ctx, w, h, p); -} - -void PLAT_setVideoScaleClip(int x, int y, int width, int height) { - // Not supported -} - -void PLAT_setNearestNeighbor(int enabled) { - // Not supported -} - -void PLAT_setSharpness(int sharpness) { - SDL2_setSharpness(&vid_ctx, sharpness); -} - -void PLAT_setEffect(int effect) { - // Only GL path is used on GLES platforms (SDL2 effect state is unused) - GLVideo_setEffect(effect); -} - -void PLAT_setEffectColor(int color) { - // Only GL path is used on GLES platforms (SDL2 effect state is unused) - GLVideo_setEffectColor(color); -} - -void PLAT_vsync(int remaining) { - SDL2_vsync(remaining); -} - -scaler_t PLAT_getScaler(GFX_Renderer* renderer) { - return SDL2_getScaler(&vid_ctx, renderer); -} - -void PLAT_present(GFX_Renderer* renderer) { - vid_ctx.on_hdmi = GetHDMI(); - SDL2_present(&vid_ctx, renderer); -} - -SDL_Window* PLAT_getWindow(void) { - return SDL2_getWindow(&vid_ctx); -} - -int PLAT_getRotation(void) { - return SDL2_getRotation(&vid_ctx); -} - -int PLAT_supportsOverscan(void) { - // Only Mini V2 with near-square aspect ratio benefits from overscan - return VARIANT_IS(VARIANT_RETROID_MINI_V2); -} - -/////////////////////////////// -// Input - Raw key codes from Retroid Pocket Gamepad driver -/////////////////////////////// - -// Button codes from retroid.c keymap[] (standard Linux BTN_* codes) -#define RAW_UP 544 // BTN_DPAD_UP -#define RAW_DOWN 545 // BTN_DPAD_DOWN -#define RAW_LEFT 546 // BTN_DPAD_LEFT -#define RAW_RIGHT 547 // BTN_DPAD_RIGHT -#define RAW_A 304 // BTN_SOUTH -#define RAW_B 305 // BTN_EAST -#define RAW_X 308 // BTN_WEST -#define RAW_Y 307 // BTN_NORTH -#define RAW_START 315 // BTN_START -#define RAW_SELECT 314 // BTN_SELECT -#define RAW_MENU 316 // BTN_MODE (Home button) -#define RAW_L1 310 // BTN_TL -#define RAW_R1 311 // BTN_TR -#define RAW_L3 317 // BTN_THUMBL -#define RAW_R3 318 // BTN_THUMBR -#define RAW_BACK 158 // BTN_BACK -#define RAW_PLUS 115 // KEY_VOLUMEUP -#define RAW_MINUS 114 // KEY_VOLUMEDOWN -#define RAW_POWER 116 // KEY_POWER - -// Analog axes -#define RAW_LSX 0 // ABS_X -#define RAW_LSY 1 // ABS_Y -#define RAW_RSX 3 // ABS_RX -#define RAW_RSY 4 // ABS_RY -#define RAW_L2 16 // ABS_HAT2X (analog trigger) -#define RAW_R2 17 // ABS_HAT2Y (analog trigger) - -// Treat Home, L3, R3 as menu buttons (L3/R3 repurposed - no analog stick click support) -#define RAW_MENU1 RAW_L3 -#define RAW_MENU2 RAW_R3 - -#define INPUT_COUNT 4 -static int inputs[INPUT_COUNT]; - -/** - * Find input device by name in sysfs. - * Returns device path (e.g., "/dev/input/event2") or NULL if not found. - */ -static char* findInputDeviceByName(const char* device_name) { - static char device_path[64]; - char name_path[128]; - char name[256]; - - // Search /sys/class/input/event* for matching device name - for (int i = 0; i < 10; i++) { - snprintf(name_path, sizeof(name_path), "/sys/class/input/event%d/device/name", i); - FILE* f = fopen(name_path, "r"); - if (!f) - continue; - - if (fgets(name, sizeof(name), f)) { - // Strip newline - name[strcspn(name, "\n")] = 0; - if (strcmp(name, device_name) == 0) { - snprintf(device_path, sizeof(device_path), "/dev/input/event%d", i); - fclose(f); - LOG_debug("Found '%s' at %s\n", device_name, device_path); - return device_path; - } - } - fclose(f); - } - - LOG_warn("Device '%s' not found in /sys/class/input/\n", device_name); - return NULL; -} - -/** - * Disable RGB LEDs on analog sticks. - * Retroid devices have HTR3212 LED controllers for stick lighting. - * LEDs are exposed as /sys/devices/platform/multi-led{l,r}{1-4}/leds/rgb:{l,r}{1-4}/brightness - */ -static void disableStickLEDs(void) { - // LED device paths for left and right sticks (4 LEDs per stick) - const char* led_paths[] = { - // Left stick LEDs - "/sys/devices/platform/multi-ledl1/leds/rgb:l1/brightness", - "/sys/devices/platform/multi-ledl2/leds/rgb:l2/brightness", - "/sys/devices/platform/multi-ledl3/leds/rgb:l3/brightness", - "/sys/devices/platform/multi-ledl4/leds/rgb:l4/brightness", - // Right stick LEDs - "/sys/devices/platform/multi-ledr1/leds/rgb:r1/brightness", - "/sys/devices/platform/multi-ledr2/leds/rgb:r2/brightness", - "/sys/devices/platform/multi-ledr3/leds/rgb:r3/brightness", - "/sys/devices/platform/multi-ledr4/leds/rgb:r4/brightness", - }; - - for (int i = 0; i < (int)(sizeof(led_paths) / sizeof(led_paths[0])); i++) { - putInt(led_paths[i], 0); - } - - LOG_debug("Disabled analog stick RGB LEDs\n"); -} - -void PLAT_initInput(void) { - // Disable stick LEDs on startup - disableStickLEDs(); - - // Find Retroid Pocket Gamepad dynamically by name - char* gamepad_path = findInputDeviceByName("Retroid Pocket Gamepad"); - if (gamepad_path) { - inputs[0] = open(gamepad_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (inputs[0] < 0) - LOG_warn("Failed to open gamepad at %s\n", gamepad_path); - } else { - // Fallback: try event0 (shouldn't happen on Retroid) - inputs[0] = open("/dev/input/event0", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (inputs[0] < 0) - LOG_warn("Failed to open fallback /dev/input/event0\n"); - } - - // Open additional event devices for power button, volume, etc. - // Power button location varies, so open multiple devices like RGB30 - inputs[1] = open("/dev/input/event1", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[2] = open("/dev/input/event2", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - inputs[3] = open("/dev/input/event3", O_RDONLY | O_NONBLOCK | O_CLOEXEC); - - for (int i = 1; i < INPUT_COUNT; i++) { - if (inputs[i] < 0) - LOG_debug("Could not open /dev/input/event%d\n", i); - } -} - -void PLAT_quitInput(void) { - for (int i = 0; i < INPUT_COUNT; i++) { - if (inputs[i] >= 0) - close(inputs[i]); - } -} - -// from which has BTN_ constants that conflict with platform.h -struct input_event { - struct timeval time; - __u16 type; - __u16 code; - __s32 value; -}; -#define EV_KEY 0x01 -#define EV_ABS 0x03 - -void PLAT_pollInput(void) { - uint32_t tick = SDL_GetTicks(); - PAD_beginPolling(); - PAD_handleRepeat(tick); - - int input; - static struct input_event event; - for (int i = 0; i < INPUT_COUNT; i++) { - input = inputs[i]; - if (input < 0) - continue; - while (read(input, &event, sizeof(event)) == sizeof(event)) { - if (event.type != EV_KEY && event.type != EV_ABS) - continue; - - int btn = BTN_NONE; - int pressed = 0; - int type = event.type; - int code = event.code; - int value = event.value; - - if (type == EV_KEY) { - if (value > 1) - continue; - - pressed = value; - if (code == RAW_UP) { - btn = BTN_DPAD_UP; - } else if (code == RAW_DOWN) { - btn = BTN_DPAD_DOWN; - } else if (code == RAW_LEFT) { - btn = BTN_DPAD_LEFT; - } else if (code == RAW_RIGHT) { - btn = BTN_DPAD_RIGHT; - } else if (code == RAW_A) { - btn = BTN_B; // Nintendo: bottom is B (RAW_A = BTN_SOUTH) - } else if (code == RAW_B) { - btn = BTN_A; // Nintendo: right is A (RAW_B = BTN_EAST) - } else if (code == RAW_X) { - btn = BTN_Y; // Nintendo: left is Y (RAW_X = BTN_WEST) - } else if (code == RAW_Y) { - btn = BTN_X; // Nintendo: top is X (RAW_Y = BTN_NORTH) - } else if (code == RAW_START) { - btn = BTN_START; - } else if (code == RAW_SELECT) { - btn = BTN_SELECT; - } else if (code == RAW_MENU) { - btn = BTN_MENU; - } else if (code == RAW_MENU1) { - btn = BTN_MENU; - } else if (code == RAW_MENU2) { - btn = BTN_MENU; - } else if (code == RAW_L1) { - btn = BTN_L1; - } else if (code == RAW_R1) { - btn = BTN_R1; - - - - - } else if (code == RAW_PLUS) { - btn = BTN_PLUS; - } else if (code == RAW_MINUS) { - btn = BTN_MINUS; - } else if (code == RAW_POWER) { - btn = BTN_POWER; - } - } else if (type == EV_ABS) { - // Analog sticks - Retroid uses ~-1408 to +1408 range - // Clamp to prevent overflow in scaling calculation - if (code == RAW_LSX) { - int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); - pad.laxis.x = (clamped * 32767) / 1408; - PAD_setAnalog(BTN_ID_ANALOG_LEFT, BTN_ID_ANALOG_RIGHT, pad.laxis.x, - tick + PAD_REPEAT_DELAY); - } else if (code == RAW_LSY) { - int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); - pad.laxis.y = (clamped * 32767) / 1408; - PAD_setAnalog(BTN_ID_ANALOG_UP, BTN_ID_ANALOG_DOWN, pad.laxis.y, - tick + PAD_REPEAT_DELAY); - } else if (code == RAW_RSX) { - int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); - pad.raxis.x = (clamped * 32767) / 1408; - } else if (code == RAW_RSY) { - int clamped = value > 1408 ? 1408 : (value < -1408 ? -1408 : value); - pad.raxis.y = (clamped * 32767) / 1408; - } else if (code == RAW_L2) { - // Analog L2 trigger (0 to ~1552) - pressed = value > 100; // Threshold for digital press - btn = BTN_L2; - } else if (code == RAW_R2) { - // Analog R2 trigger (0 to ~1552) - pressed = value > 100; - btn = BTN_R2; - } - } - - PAD_updateButton(btn, pressed, tick); - } - } -} - -int PLAT_shouldWake(void) { - int input; - static struct input_event event; - for (int i = 0; i < INPUT_COUNT; i++) { - input = inputs[i]; - if (input < 0) - continue; - while (read(input, &event, sizeof(event)) == sizeof(event)) { - if (event.type == EV_KEY && event.code == RAW_POWER && event.value == 0) - return 1; - } - } - return 0; -} - -/////////////////////////////// -// Power Management -/////////////////////////////// - -static int online = 0; - -void PLAT_getBatteryStatus(int* is_charging, int* charge) { - // SM8250 uses standard power_supply class - *is_charging = getInt("/sys/class/power_supply/battery/status") == 2; // Charging status - - int i = getInt("/sys/class/power_supply/battery/capacity"); - if (i > 80) - *charge = 100; - else if (i > 60) - *charge = 80; - else if (i > 40) - *charge = 60; - else if (i > 20) - *charge = 40; - else if (i > 10) - *charge = 20; - else - *charge = 10; - - char status[16]; - getFile("/sys/class/net/wlan0/operstate", status, 16); - online = prefixMatch("up", status); -} - -#define BRIGHTNESS_PATH "/sys/class/backlight/ae94000.dsi.0/brightness" -#define BLANK_PATH "/sys/class/graphics/fb0/blank" - -// Minimum time for DSI panel brightness register to take effect before blanking. -// Empirically determined for AMOLED panels; may need adjustment for different panel revisions. -#define BRIGHTNESS_SETTLE_US 20000 - -void PLAT_enableBacklight(int enable) { - if (enable) { - // Restore brightness first, then unblank - SetBrightness(GetBrightness()); - putInt(BLANK_PATH, FB_BLANK_UNBLANK); - } else { - // Set brightness to 0 before blanking to ensure screen turns off - SetRawBrightness(0); - usleep(BRIGHTNESS_SETTLE_US); - putInt(BLANK_PATH, FB_BLANK_POWERDOWN); - } -} - -void PLAT_powerOff(void) { - sleep(2); - SetRawVolume(MUTE_VOLUME_RAW); - PLAT_enableBacklight(0); - SND_quit(); - VIB_quit(); - PWR_quit(); - GFX_quit(); - system("poweroff"); - while (1) - pause(); -} - -double PLAT_getDisplayHz(void) { - return SDL2_getDisplayHz(); -} - -uint32_t PLAT_measureVsyncInterval(void) { - return SDL2_measureVsyncInterval(&vid_ctx); -} - -#define GOVERNOR_PATH "/sys/devices/system/cpu/cpufreq/policy0/scaling_setspeed" - -/** - * Sets CPU frequency based on performance mode. - * - * Snapdragon 865 frequencies (Cortex-A77 prime core): - * Up to 2.84 GHz (prime), 2.4 GHz (performance), 1.8 GHz (efficiency) - * - * @param speed CPU_SPEED_* constant - */ -void PLAT_setCPUSpeed(int speed) { - int freq = 0; - switch (speed) { - case CPU_SPEED_IDLE: - freq = 300000; // 300 MHz (idle) - break; - case CPU_SPEED_POWERSAVE: - freq = 1056000; // ~1 GHz (powersave) - break; - case CPU_SPEED_NORMAL: - freq = 1804800; // ~1.8 GHz (normal) - break; - case CPU_SPEED_PERFORMANCE: - freq = 2419200; // ~2.4 GHz (performance) - break; - } - putInt(GOVERNOR_PATH, freq); -} - -/** - * Gets available CPU frequencies from sysfs. - */ -int PLAT_getAvailableCPUFrequencies(int* frequencies, int max_count) { - return PWR_getAvailableCPUFrequencies_sysfs(frequencies, max_count); -} - -/** - * Sets CPU frequency directly via sysfs. - */ -int PLAT_setCPUFrequency(int freq_khz) { - return PWR_setCPUFrequency_sysfs(freq_khz); -} - -#define RUMBLE_PATH "/sys/class/leds/vibrator/activate" - -void PLAT_setRumble(int strength) { - if (GetHDMI()) - return; - // pmi8998_haptics uses /sys/class/leds/vibrator interface - putInt(RUMBLE_PATH, strength ? 1 : 0); -} - -int PLAT_pickSampleRate(int requested, int max) { - return MIN(requested, max); -} - -char* PLAT_getModel(void) { - return (char*)PLAT_getDeviceName(); -} - -int PLAT_isOnline(void) { - return online; -} From e2bfe241c816bf86507205f1895f171d3bf6dc47 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Fri, 2 Jan 2026 11:14:11 -0800 Subject: [PATCH 10/10] Switch retroid to sm8250 toolchain. - Update toolchains.json to use sm8250 instead of rk3566 - Update Makefile.env with cortex-a76.cortex-a55 tuning (closest to A77.A55) - Remove trailing empty line from system.cfg --- skeleton/SYSTEM/retroid/system.cfg | 1 - toolchains.json | 2 +- workspace/retroid/platform/Makefile.env | 7 +++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/skeleton/SYSTEM/retroid/system.cfg b/skeleton/SYSTEM/retroid/system.cfg index f2e1550f..f2376e50 100644 --- a/skeleton/SYSTEM/retroid/system.cfg +++ b/skeleton/SYSTEM/retroid/system.cfg @@ -1,2 +1 @@ -player_screen_sharpness = Crisp - diff --git a/toolchains.json b/toolchains.json index d6b1c354..1b2fe45c 100644 --- a/toolchains.json +++ b/toolchains.json @@ -12,7 +12,7 @@ }, "retroid": { "platform": "linux/amd64", - "toolchain": "rk3566" + "toolchain": "sm8250" } } } diff --git a/workspace/retroid/platform/Makefile.env b/workspace/retroid/platform/Makefile.env index a281fb89..f242d6a8 100644 --- a/workspace/retroid/platform/Makefile.env +++ b/workspace/retroid/platform/Makefile.env @@ -1,7 +1,6 @@ -# retroid - LessOS toolchain (using rk3566 for now) -# TODO: When sm8250 toolchain is available, update to: -# ARCH = -mtune=cortex-a77.cortex-a55 -march=armv8.2-a+crypto -O3 -ARCH = -mtune=cortex-a55 -march=armv8.2-a -O3 +# retroid - LessOS SM8250 toolchain (Snapdragon 865) +# Note: GCC doesn't support cortex-a77.cortex-a55, using cortex-a76.cortex-a55 (closest available) +ARCH = -mtune=cortex-a76.cortex-a55 -march=armv8.2-a+crypto -O3 LIBS = -flto -ldrm -lstdc++ -lasound -lfreetype -ljpeg -lpng16 -lbz2 SDL = SDL2