Skip to content

Add support for Anbernic H700-based devices - #807

Open
pvaibhav wants to merge 118 commits into
LoveRetro:mainfrom
pvaibhav:h700
Open

Add support for Anbernic H700-based devices#807
pvaibhav wants to merge 118 commits into
LoveRetro:mainfrom
pvaibhav:h700

Conversation

@pvaibhav

@pvaibhav pvaibhav commented Aug 15, 2026

Copy link
Copy Markdown

This huge changeset adds support for Anbernic's RG XX lineup for devices based on the Allwinner H700 SoC.

Supported are all SKUs of this family:

  • RG28XX
  • RG34XX
  • RG34XX SP
  • RG35XX+ and RG35 XX 2024
  • RG35XX Pro
  • RG35XX SP
  • RG35XX H
  • RG40XX H
  • RG40XX V
  • RG Cube XX
  • RG SP

The same platform h700 supports all these devices. Third party paks and tools can use the DEVICE env var to figure out the exact model they're running on.

It's been tested thoroughly be several users. The main development was done on RG40xxV and RG34xxSP by me.

There are some small known bugs notably with LED control on RG Cube XX and RG40xxH. These will be fixed post merge. For the most part, all features work, including features that NextUI doesn't support yet, like HDMI output. The power drain and sleep performance was optimised to be near-identical to stock OS.

You can check my testing gate here, and the roadmap + milestone status here. I also tested some cores that I play myself, the core and game testing status is here.

Note: third party paks, the Pak Store and the Updater all need upstream changes.

I've tried to keep my hands off platform-agnostic code unless absolutely necessary.

Furthermore, default display calibrations for some models (RG40xxV, RG34xxSP, and RG28xx calibrated by me, RG SP calibrated by @frysee) are included. The rest I don't have so they're left uncalibrated.

Boot logo pak was also updated to bundle 4 variants of each boot logo.

Good luck with the review! ;-P

pvaibhav added 30 commits August 9, 2026 00:50
The upstream code changed enough that our existing patch can't
find the hunk. I updated it to it builds now.
Document the RG XX H700 port strategy, including verified device facts, build/toolchain choices, boot hijack layout, platform-layer hardware paths, media stack, power behavior, wireless strategy, and rollout validation criteria.

This captures the implementation plan before the port work begins so the subsequent build and platform changes can be reviewed against a concrete hardware-backed baseline.
Add the initial Allwinner H700 platform for Anbernic RG XX devices, using the existing tg5040 toolchain image while forcing in-container builds to run as PLATFORM=h700.

This wires the new platform into the build and package skeleton, adds the dmenu.bin boot shim and TF2 installer/update flow, builds and bundles the Mali fbdev SDL2 runtime, ports the platform layer to H700 sysfs and input paths, adds H700 libmsettings/keymon/rfkill support, and exposes H700 displaycal, WiFi, and Bluetooth settings.

The runtime skeleton owns /mnt/sdcard with /mnt/SDCARD compatibility, stops conflicting stock services, starts H700 WiFi/BT helpers, supports mem suspend with radio bounce on resume, and keeps RG34XXSP/RG28XX detection guarded for later hardware validation.
Disable PicoDrive's inherited LTO flags for the H700 target and filter the remaining linker plugin flags after the upstream makefile is included. The tg5040 toolchain otherwise drops the libretro glue objects during the final shared-object link, leaving VFS and logging symbols unresolved. This keeps the H700 core bundle buildable without changing the shared core list.
Include h700 in the existing tg5040-class minarch build filters so the platform gets the RA, CHD, SRM, samplerate, and compression support libraries that the H700 package already stages. Without this, the package target could copy the H700 cores but failed before creating a usable MinUI payload because minarch.elf was never produced.
Prefer the stock OS unzip binary for the RG40XXV boot shim so the TF2 update payload can be extracted without relying on the embedded tarball helper. Keep the embedded helper as a fallback with explicit logging, and add exFAT/auto mount fallback for TF2 cards that are not already mounted by stock.
Package the H700 runtime libraries required by the Mali SDL stack and H700 platform helpers, including libUMP, libtinyalsa, and libasound. Add a debug keep-network flag and launch logging so failed RG40XXV bring-up attempts remain reachable over SSH for diagnosis.
Incorporate the H700 branch review by tightening the RG40XXV boot and update path, delegating fresh MinUI.zip installs through the platform updater, repairing TF2 mount failures when possible, and improving TF1/TF2 compatibility mounts and dmenu self-heal behavior.

Rework the H700 SDL/NextCommander build inputs so the custom Mali SDL is pinned and verified, Files.pak is built with H700 paths/buttons, and host libraries such as libasound/EGL/GLES are no longer bundled over the stock OS copies.

Harden the platform layer and runtime services with corrected joystick constants, persistent wake input handling, display/audio setting restore, WiFi runtime state separation, Bluetooth audio gating, better crash diagnostics, and removal of leftover Brick defaults that should not ship for H700.
Reorder the H700 auxiliary build so the pinned Mali SDL is configured and installed before Files.pak is compiled, and update the carried NextCommander patch to include the SDK SDL_image/SDL_ttf headers and libraries alongside the local SDL2 build.

Add a tiny framebuffer splash helper to the dmenu shim payload so first boot, missing TF2, stock-target warnings, and install/update work can show visible status before SDL/show2 exists.

Gate H700 Bluetooth audio affordances for the beta while keeping controller pairing available, avoid applying Bluetooth audio sample-rate limits to controller-only connections, and set the RG28XX software rotation flag during platform init.
Stop linking H700 binaries through the tg5040 glesv2 pkg-config library list, which pulled in the old libUMP dependency that is not part of the RG40XXV Mali-G31 stock stack. H700 now links GLES directly while still using the custom SDL2 pkg-config data.

Also stop copying UMP into the H700 payload, add early NextUI startup breadcrumbs around settings/video/input init, and make repeated UI crashes start the stock network/SSH services so boot-logo stalls remain diagnosable on-device.
Build the H700 Mali SDL stack without libudev and without dynamically loaded ALSA/libsamplerate helpers so video startup does not trip over the stock RG40XXV runtime's libudev/loadso behavior.

Add persistent installer logging and a debug-wifi.conf path that can bring up NetworkManager and SSH during install or after repeated UI crashes. Also make audiomon relink when platform link flags change, and guard the Bluetooth scan rate item before pushing it into the settings menu.
Replay the H700 raw evdev input restoration from the previous branch so built-in controls are read reliably on stock H700 images after rebasing onto c24b36d.

Keep the debug wifi launch guard from the same fix, but intentionally leave out the shared renderer and PLATFORM_STRAIGHT_ALPHA_UI workaround so this branch stays on the known-good pre-alpha graphics stack.
Picodrive's H700 patch already carried the FLTO filter needed by this platform, but clean rebuilds exposed two patch hygiene issues after the rebase. The hunk indentation was stale, and the parent core builder's PLATFORM=h700 value prevented Picodrive's included Makefile from selecting its libretro object list.

Keep the H700 platform block for cross flags, force Picodrive's internal PLATFORM back to libretro for object selection, and preserve the existing FLTO filter so clean release builds link the core reproducibly.
Normalize the shared asset sheet on H700 after SDL_image loads it and force source surfaces through a save/restore alpha blend path when blitting tinted bitmaps. The branch is gated by the existing PLATFORM string so tg5040 and tg5050 keep the pre-alpha rendering behavior while H700 can work around its SDL2/SDL_image bitmap handling.
This reverts commit 7f229c2dd11a048c5e4add511c86becc66cba250.
The tg5040-toolchain SDL2_image dlopens libpng12.so.0 at runtime. The
H700 stock Ubuntu rootfs only provides libpng16 (aarch64) and a 32-bit
libpng12 under /mnt/vendor/lib, so IMG_Load failed with 'wrong ELF
class: ELFCLASS32' and every asset bitmap silently blitted from a NULL
surface. Ship the toolchain's 64-bit libpng12 alongside the other
bundled SDL runtime libraries.
The emu pak launch scripts (cloned from tg5040) redirected minarch
output with the bashism '&>'. H700 stock OS /bin/sh is dash, which
parses it as 'background the command, then truncate the file' - so
launch.sh exited immediately, the launch loop restarted nextui.elf,
and games bounced straight back to the main screen. Use the POSIX
'> file 2>&1' form instead, like the old rg35xxplus port did.

With output captured, minarch still segfaulted in SND_init:
PLAT_pickSampleRate called GetAudioSink(), but minarch deliberately
runs SND_init before InitSettings(), so the msettings shared memory
is not mapped yet (NULL->audiosink at offset 0x70). Match tg5040 and
only consult PLAT_bluetoothConnected() there.

Also fix the same '&>' bashism in a system() amixer call in
msettings.c, which dash would background as well.
SDL2 was configured with --disable-alsa-shared, direct-linking the
tg5040 toolchain's libasound, which is built without symbol
versioning. The resulting unversioned references resolve against the
H700 stock Ubuntu's versioned libasound to the legacy ALSA_0.9
hw_params symbols (value semantics, chosen value as return) instead
of the new pointer API. snd_pcm_hw_params_set_rate_near then read
SDL's pointer argument as the requested rate and clamped it to the
hardware max: the codec ran at 192 kHz while SDL believed 32.768 kHz,
so only ~17% of every buffer was real audio and games sounded like
short slivers with silence gaps.

Configure with --enable-alsa-shared instead: SDL dlopens libasound at
runtime and dlsym always resolves the default (new-API) symbols. The
codec now honors the negotiated rate (e.g. req 32768 -> rec 32000)
and minarch's resampler bridges the difference. Bump the SDL2 stamp
to force a reconfigure.
The implementation is complete (working beta on RG40XXV), so the plan
docs are rewritten as-implemented: deviations recorded with reasoning,
branch-review files verified against the code and folded into the
topical docs, testing matrix filled with actual results, and a
prioritized roadmap added for the remaining work.
- Alpha/tinted-bitmap question resolved: glitches were the missing 64-bit
  libpng bundle, not main's alpha-blending work; plan is to rebase the
  h700 branch onto main including that work.
- 480p UI verdict from use: OK for alpha; Battery pak hint pills overlap.
- New roadmap item: audit Brick-era hardware assumptions in shared UI
  (Input tester layout, Fn switch, dead enhance display controls —
  only backlight/colortemp/white-point/RGB tuning work on RG XX).
Document that the RG34XXSP generally behaves like the RG40XXV on the H700 port while lid sleep/wake remains broken. Replace local test fleet IP/login details with public-safe evidence notes so the bring-up docs can be committed without leaking LAN-specific data.
The H700 firmware emits a compound sequence for a short MENU tap:
312 (BTN_TL2) down/up tracking the physical button, then a synthetic
354 (KEY_GOTO) pulse starting the instant 312 releases and lasting
~190ms (on a long hold, 354 never fires). Mapping both codes to
BTN_MENU stretched every tap past the 250ms MENU_DELAY threshold, so
a quick tap at the home screen flipped the shortcuts overlay into
brightness-adjust mode, and the Input tester showed MENU held after
release.

Drop the CODE_MENU_ALT (354) -> BTN_MENU mapping in the platform
layer and keymon; tap-vs-hold is derived from the clean 312 timing.
The SDL joystick path is unaffected (JOY_MENU_ALT = JOY_NA).

Verified via evtest and on-device testing on RG34XXSP; the quirk is
firmware-common across the RG XX H700 line.
Record that volume control is working correctly on both RG40XXV and RG34XXSP, including the UI and audible levels from mute through 100%. Retire the roadmap note that treated the digital-volume inversion as an unresolved documentation task.
…ume freeze

Four stacked bugs, all verified on RG34XXSP hardware:

- bin/suspend died on 'set -o pipefail' (unsupported by stock /bin/sh),
  exiting 2 before writing /sys/power/state; PWR_deepSleep treated that
  as suspend failure and powered off. Use 'set -u' only.
- The persistent wake_fd buffered the power-key release that triggered
  sleep, so every 2nd+ sleep of a process woke instantly. Drain the fd
  each PLAT_pollInput.
- Lid close injected a BTN_SLEEP release, which can never satisfy the
  manual-sleep condition (requires a power-key press). Add
  PWR_requestSleep() and call it on lid close; stop firing on lid open.
- Wake froze UI/input 5-10s: an ALSA PCM held open across suspend takes
  SDL ~9s to close. Close audio in PWR_enterSleep (reopened on wake;
  reinit now 150-200ms), and split the suspend script's resume work into
  after_sync (alsactl/syncsettings) and backgrounded after_async (wifi/bt).

Update h700-port docs: postmortem in 06, test matrix, roadmap P0 #1 done.
- Persist white point correction / RGB gains across reboots: displaycal
  defaults are now seeded only by the shm host and only when the settings
  file is missing or predates v11, instead of clobbering user values on
  every InitSettings()
- Hide Contrast/Saturation/Exposure on H700 (disp driver lacks the
  enhance_* sysfs nodes), including their mute-toggle variants
- Add per-model displaycal preset plumbing for the full H700 family
  (RG28XX..RGCubeXX), all neutral/disabled until each panel is measured;
  detection via RGXX_MODEL with DEVICE fallback
- Map RG35xx* to DEVICE=rg35xx in launch.sh instead of lumping the
  RG35xx family into rg40xx
- Raise default brightness to 4 on H700 devices (Brick keeps 2)

Verified on RG40XXV: gains survive reboot, fresh defaults are
brightness 4 with neutral displaycal; tg5040 builds unaffected.
- 04: displaycal reboot-persistence fix, per-model preset plumbing,
  RGXX_MODEL detection notes, hidden enhance controls, brightness 4
- 08: add verified rows for reboot persistence, fresh defaults, gating
- 09: mark display settings item done at 0e60efd, note remaining
  syncsettings nit, add per-panel calibration to the later list
… first frame

The Files app (NextCommander) appeared to freeze on startup: it ran fine but
never received input. The bundled SDL fork's Batocera patches removed the evdev
joystick heuristic from SDL_EVDEV_GuessDeviceClass() (Batocera classifies via
udev), so our no-udev build never enumerated the built-in pad and
SDL_NumJoysticks() returned 0. New patches/sdl2-h700.patch restores
classification via BTN_GAMEPAD/BTN_JOYSTICK (the pad has no ABS_X/ABS_Y, so the
original upstream check would not have matched either), applied with a stamp
like the NextCommander patch.

NextCommander fixes (patches/NextCommander-h700.patch):
- Button indices remapped: the fork assigns SDL button indices in ascending
  keycode order, so ESC/VOL-/VOL+ (1/114/115) occupy 0-2 and the gamepad
  cluster starts at 3 (A=3, B=4, Y=5, X=6, L1=7, R1=8, SELECT=9, START=10,
  MENU=11). Volume keys no longer trigger navigation.
- SDL_CreateWindow was passed height for both dimensions (square window that
  could not fill 640x480), and sized the window from disp_width*ppu, which
  exceeds what mali-fbdev can allocate at higher PPU; with autoscale the
  window now uses the display's current mode.
- Render the first 8 frames instead of one: a single startup flip could land
  on a stale page during the launcher handoff, leaving a blank screen until
  the first button press.

PPU stays 2 (user-preferred after comparing 2/3/4/6 on RG40XXV) with
disp_autoscale_dpi off since SDL_GetDisplayDPI is unsupported here.
The sdl2-h700.patch made the built-in gpio-keys pad enumerable by SDL (needed
by the Files app), but NextUI's platform layer interprets every SDL joystick
with the Bluetooth-pad JOY_* layout. The built-in pad's presses were therefore
processed twice: correctly via raw evdev, and again through the BT mapping
with the fork's ascending-keycode indices (B->L1, Y->R1, VOL- -> back, MENU's
354 keycode -> volume UI), scrambling controls in Settings and every other
NextUI app.

Skip "ANBERNIC-keys" in PLAT_initInput and the SDL_JOYDEVICEADDED hotplug
path: raw evdev stays the sole input path for built-in controls, SDL joystick
remains for external Bluetooth pads only.
Remove writes to unsupported enhance_* sysfs nodes for contrast, saturation, and exposure. Keep the API for future LUT-based implementation.

Review: LoveRetro#807 (comment)
Stock OS, stock mod and BaseOS all have rfkill command (BaseOS via
a BusyBox applet). So we don't need to bundle our own rfkill binary.
This upgrade replaces system Bluetooth binaries and libraries with
versions built for tg5040. So skip all other platforms, rather than
just h700.
The toolchain images already set UNION_PLATFORM and the workspace
directory. So we can start bash directly without exporting the same
values again or starting an extra shell.
Zero28 support is not planned. So remove its detection branch and
the extra guard that kept it from overriding H700 detection.
HDMI output needs to be selected before SDL initializes video. So
handle it in GFX_init using the existing settings hooks, and keep
model detection and panel rotation in the platform code.

The other current platforms return zero from GetHDMI and do nothing
in SetHDMI, so their behavior stays the same.
The RG SP exposes time_to_full_now and returns a positive value while
charging. So use the same check as tg5040 instead of treating USB
power alone as active charging.
The RG SP reports its USB controller as configured when connected
to a computer. So use the same detection as tg5040 and expose the
existing Keep awake over USB setting for H700.
H700, tg5040 and tg5050 were carrying identical copies of the CPU
monitor. It now lives with the shared perf code in api.c, including
the two clock helpers. The 100 ms polling and rolling average stay
as they were.
H700 does not implement turbo. The three stubs were identical to
the shared defaults in api.c; let those handle it.
The date command only contains six integers and is under 100 chars,
even at their limits. The 512-byte buffer is plenty; use the same
formatting as tg5040 and tg5050.
Gearcoleco changed a CXXFLAGS assignment and our patches no longer apply
on a fresh checkout. Update the context for all three platforms; the
platform build flags are unchanged.
Brick and Brick Pro were saving to the Smart Pro file, then loading
their old settings again on exit. Use the model's existing filename
for both saving and loading.
The UART frame has one brightness byte for both sticks. Scale each
bank's RGB values relative to it so either stick can be dimmed or
turned off independently in solid and breathing modes.

Breathing modes accept separate bank colors too. Send both banks for
modes 1–4, matching KNULLI, instead of repeating the left color over
the whole strip.
The power light was off during use and on while asleep. Keep it lit
from boot through normal use, then turn it off for sleep and shutdown.

Both screen-off and deep sleep follow this now. HDMI sleep also updates
the light without changing the display output.
@pvaibhav

pvaibhav commented Sep 7, 2026

Copy link
Copy Markdown
Author

One more thing: I think pakz install on boot is not yet working.

Could not reproduce it. I used .pakz to install a couple of paks I made (calibration etc.) and it works fine. Did you experience it on TrimUI?

@pvaibhav

pvaibhav commented Sep 7, 2026

Copy link
Copy Markdown
Author

@frysee I've completed the post-review work. I think we're good to go. Need a last look from you but I think I've addressed all 60+ (!) comments, and fixed a few outstanding polish items before calling it release-ready.

  • Power LED now stays on when device is in use and turns off during sleep - as people wanted
  • LED control regression on TrimUI is fixed, settings are now properly saved
  • Models with two stick LEDs should now work (checked against muOS and Knulli code, but can't test myself)
  • LCD refresh rates updated based on data gathered from the DTBs that I showed you, so micro stuttering should be gone now
  • Removed custom rfkill binary, should improve WiFi stability (I have checked on RG SP and it seems faster to reconnect after power on or sleep-wake)
  • Added USB-keep-awake

I will cut a final -rc9 for people to test, because these are not only internal refactors. But you can re-review and merge if all looks OK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants