-
Notifications
You must be signed in to change notification settings - Fork 1
H700 port 03 Platform layer
Reference map for the platform API, device detection, input architecture, settings backend, and H700-specific tools. Evidence is indexed in 08 and release classification in 09.
Strategy, as planned and executed: clone workspace/tg5040/, keep the
generic_video.c/generic_wifi.c/generic_bt.c includes and the current PLAT_* API
surface, repoint hardware access to the H700 paths (00), borrowing only hardware
constants from the old rg35xxplus port (git show 8cd78866:...).
One platform serves all devices: h700-device.sh scrapes stock RGXX_MODEL and
exports exact DEVICE SKU tokens (see 02). detect_device() keys only on DEVICE
and sets panel_w / panel_h (app framebuffer size) plus stick/LED capability
flags. Panel rotation on RG28XX is handled entirely by the SDL driver via
SDL_ROTATION=1 (needs_portrait_sdl); nothing app-side rotates — 04.
Analog stick / LED matrix (DEVICE contract):
$DEVICE |
Sticks | RGB LEDs |
|---|---|---|
rg28xx, rg34xx, rgsp, rg35xxplus, rg35xxsp
|
none | no |
rg34xxsp, rg35xxh, rg35xxpro
|
dual | no |
rg40xxv |
left only | 1 bank |
rg40xxh, rgcubexx
|
dual | 2 banks |
Unknown models map to rg35xxplus (640×480, no sticks, no RGB). No H700 Anbernic
device has an Fn switch. The H700 PS1 pak binds L3/R3 so DualShock titles can use
stick clicks on models that have them.
The RG SP is stickless on device-tree evidence rather than spec sheets: its DTB has
no keyL3/keyR3 nodes, none of the analog multiplexer pins the RG34XXSP carries
(amux-en-gpios, A0_gpio, A1_gpio, adc-en-gpios), and the GPADC itself is
status = "disabled".
The Input pak uses the per-device capability flags to show only the sticks that exist, including their analog axis movement and L3/R3 click state. This is verified on the single-stick RG40XXV and dual-stick RG34XXSP: movement is visualized correctly for the left stick on RG40XXV and for both sticks on RG34XXSP.
extern int panel_w, panel_h; // app framebuffer from DEVICE
extern int needs_portrait_sdl; // DEVICE=rg28xx
extern int hdmi_active;
extern int dev_has_lstick, dev_has_rstick;
extern int dev_has_rgb, dev_num_leds;
#define HDMI_WIDTH 1280
#define HDMI_HEIGHT 720
#define FIXED_SCALE 2
#define FIXED_WIDTH (hdmi_active?HDMI_WIDTH:panel_w)
#define FIXED_HEIGHT (hdmi_active?HDMI_HEIGHT:panel_h)
#define FIXED_BPP 2
#define SCREEN_FPS 60.0 // ⚠ assumed, never measured per panel
#define SDCARD_PATH "/mnt/SDCARD" // symlink to the real TF2 mountpoint (02)
#define MAX_LIGHTS 2 // ceiling; PLAT_getNumLeds() reports 0/1/2 per model
#define MAIN_ROW_COUNT ((hdmi_active||panel_h>=720)?10:6)
#define PADDING ((hdmi_active||panel_h>=720)?5:10)The plan preferred the tg5040-style pure-SDL-joystick route. Shipped: raw evdev is the primary path for built-in controls, with SDL joystick as a secondary path for external controllers. Reasons discovered during bring-up:
- SDL is built without udev and
SDL_JOYSTICK_DISABLE_UDEV=1is exported. During bring-up the built-in pad did not enumerate because the SDL fork's Batocera patches deleted the joystick heuristic inSDL_EVDEV_GuessDeviceClass(), so the no-udev fallback path never classified any device as a joystick. Fixed by h700-toolchain'ssupport/sdl2-h700.patch(see 01); SDL joystick enumeration of the built-in pad now works. Note the fork assigns SDL button indices in ascending evdev-keycode order, so the pad's ESC/VOL−/VOL+ (1/114/115) occupy indices 0–2 and the gamepad cluster starts at index 3 (A=3 … MENU=11). Because of that, platform.c must never open the built-in pad as an SDL joystick:poll_sdl_input()interprets SDL joysticks with the BT-padJOY_*layout, so the built-in pad's events would double-apply with scrambled meanings (B→L1, VOL−→back, MENU→volume UI — seen in Settings 2026-07-09).is_builtin_pad()skips "ANBERNIC-keys" in both the init scan and the hotplug path; evdev is the sole path for built-in controls, SDL joystick is for external (BT) pads only. - evdev codes are needed for keymon and wake handling anyway.
Implementation: poll_evdev_input() scans/reads /dev/input/event0..11 directly with
periodic (2 s) rescans for hotplug, mapping via CODE_* defines; poll_sdl_input()
handles SDL joystick events (BT pads) via JOY_* indices.
Verified mappings (also recorded in 00):
- evdev (event1
ANBERNIC-keys): A 304, B 305, Y 306, X 307, L1 308, R1 309, SELECT 310, START 311, MENU 312, L3 313, L2 314, R2 315, R3 316; PLUS 115 / MINUS 114 (event2); POWER 116 (event0,axp2202-pek— note tg5040 uses 102). D-pad has no keycodes on current firmware — it arrives as ABS_HAT0X/Y. -
MENU compound-tap quirk (fixed): the firmware reports the physical MENU button
faithfully on 312 (down while held, up on release), but on a short tap it also
emits a synthetic 354 (
KEY_GOTO) pulse that starts the instant 312 releases and lasts ~190 ms (verified via evtest on RG34XXSP; on a long hold 354 never fires). Mapping both 312 and 354 toBTN_MENU(as the old rg35xxplus platform did viaCODE_MENU_ALT) stretched every tap past the 250 msMENU_DELAYthreshold — a quick MENU tap registered as a hold, so the home screen flipped from the shortcuts overlay into brightness mode. Fix:button_from_code()no longer mapsCODE_MENU_ALTtoBTN_MENU(keymon likewise ignores it); tap-vs-hold is derived from the clean 312 timing. The SDL path was never affected (JOY_MENU_ALT = JOY_NA). - Analog sticks: ABS_Z/RX/RY/RZ; signed values are scaled ×32767/4096. Presence varies
per model (see the stick matrix above);
dev_has_lstick/dev_has_rstickgate CODE_L3/R3, JOY_L3/R3, and AXIS_LX/LY/RX/RY to NA where the stick is absent, so the Input pak and shared input code adapt without platform-independent changes. (Previously CODE_L3/R3 were unconditional, so the Input pak drew L3/R3 pills on every device.) - SDL indices (secondary path): A=0 B=1 Y=2 X=3 L1=4 R1=5 SELECT=6 START=7 MENU=8, L3=9 L2=10 R2=11 R3=12, MINUS=15 PLUS=16; axes LX=0 LY=1 RX=2 RY=3.
Button semantics: BTN_RESUME=BTN_X, BTN_SLEEP/WAKE=BTN_POWER,
BTN_MOD_BRIGHTNESS=BTN_MENU, BTN_MOD_COLORTEMP=BTN_SELECT, dedicated
BTN_MOD_PLUS/MINUS on the volume keys — parity with tg5040 conventions.
Not supported: user-assignable FN1/FN2/HOME pak-launch actions (upstream #788).
H700 has no dedicated FN1/FN2/HOME buttons, so BTN_FN* are BTN_NONE with empty
names (Settings hides the Assignments menu; main-menu FN presses are no-ops).
PLAT_shouldWake keeps a persistent wake_fd on event0 (opened once
O_NONBLOCK|O_CLOEXEC, drained per poll, closed in PLAT_quitInput) — an early version
open/closed per poll and could drop the wake press between polls.
| Subsystem | Implementation |
|---|---|
| Video |
#include "generic_video.c" — custom SDL2 mali driver does the rest (04) |
| Battery |
axp2202-battery/capacity + axp2202-usb/online, with coarse bucketing in shared code. Unlike tg5040, H700 charging detection uses online alone |
| CPU speed |
governor.sh via system(): auto=schedutil and performance both permit the greatest advertised frequency; powersave=conservative capped mid-range. H700's advertised 1.5 GHz ceiling is in-spec, not an overclock, so Auto no longer caps it one step below maximum. Manual changes work. Former MD Auto slowdown near 480 MHz is fixed (user-verified 2026-07-20). Single A53 cluster → PLAT_pinToCores no-op |
| CPU temp | thermal_zone0 |
| GPU temp | thermal_zone1 (zone map in 00 — zone2 is the video engine, a first draft got this wrong) |
| GPU speed / governor | HUD read resolves devfreq/clk paths once (first readable candidate wins) with a 648 MHz last-resort fallback. governor.sh also pins GPU min_freq to max under auto/performance and to min under powersave — simple_ondemand under-clocks vsync'd spiky loads (06) |
| Rumble |
echo 1/0 > axp2202-battery/moto — on/off only, strength>0 → 1. Works (tested). Input-FF (event1 advertises FF bits) unexplored |
| LEDs | RGB on RG40XX H / RG40XX V / RG CubeXX only, via an MCU on /dev/ttyS5 (115200 8N1) gated by axp2202-battery/mcu_pwr — see platform/led.c. MAX_LIGHTS 2 is a ceiling; PLAT_getNumLeds() returns 0 (no RGB), 1 (RG40XXV, one populated bank) or 2. Effects are rendered by the MCU firmware, not in userspace. work_led remains the sleep/backlight indicator on every model |
| Backlight | raw brightness 0 via disp ioctl + fb blank + work_led on/off around it |
| Lid |
hallkey path wired into PLAT_initLid/PLAT_lidChanged (has_lid = file exists); lid close → sleep and lid open wakes screen-off. RG34XXSP: POWER can wake light sleep with lid closed; deep sleep re-sleeps if the lid remains closed (06). |
| Model |
PLAT_getModel copies the raw RGXX_MODEL into a static buffer; when unavailable it returns Anbernic RG XX
|
| Date/time |
timedatectl / hwclock / date via snprintf-bounded commands; timezone choices are parsed from /usr/share/zoneinfo/zone.tab, setting uses timedatectl set-timezone, and NTP uses set-ntp (systemd-timesyncd) |
| Turbo |
PLAT_canTurbo()=false, no-ops (tg5040's turbo rides trimui_inputd; no H700 equivalent wired) |
| Deep sleep |
PLAT_supportsDeepSleep()=1; suspend script wraps echo mem (06) |
| Sample rate | tg5040 logic; consults PLAT_bluetoothConnected() only — calling GetAudioSink() here segfaulted minarch pre-InitSettings() (shm not yet mapped) |
| WiFi/BT | generic_wifi.c / generic_bt.c + init scripts (07) |
-
Brightness:
/dev/dispioctlDISP_LCD_SET_BRIGHTNESS (0x102), args{0, raw, 0, 0}; level curve0→4, 1→6, 2→10, 3→16, 4→32, 5→48, 6→64, 7→96, 8→128, 9→192, 10→255. StockbrightCtrl.binis killed at launch so it can't fight us. -
Volume: tinyalsa on card 0.
digital volumeis a 0–63 attenuator — the code writesmixer_ctl_set_percent(digital, 0, 100 - val)(reversed mapping, confirmed correct by ear; the control's TLV metadata is garbage).lineout volumesecondary. Tested on RG40XXV and RG34XXSP: UI and audible levels are correct from mute through 100%, with no current volume-control issues. -
Mute:
SPKswitch off + store/restore volume (no/sys/class/speaker/muteon H700). The physical volume UI still supports mute; Fn-switch settings are gated off because RG XX devices have no Fn switch. -
Color temperature:
/sys/class/disp/disp/attr/color_temperature— works (tested ✅). The siblingenhance_contrast/enhance_saturation/enhance_brightattrs are also written, but have no visible effect on RG XX panels — those settings are gated off for h700 (04). Allscale*()switches carrydefault:cases (a draft could hit uninitialized values on out-of-range input). - DisplayCal: same gamma-LUT ioctls as tg5040 (0x10b/0x10c/0x10d) — worked 1:1 as predicted; tested passing on RG40XXV including persistence across sleep and game launch.
-
HDMI:
GetHDMI()probes/sys/class/extcon/hdmi/{state,cable.0/state,...}andSetHDMI()switches disp0, framebuffer/layer geometry, and the ALSA route. Menu, game, audio, and in-game hotplug in both directions pass on RG40XXV (04). - Jack detection: not wired (path exists but historically dead — 00).
- Debug printfs were stripped/commented from the runtime path; remaining prints are error paths.
tg5040 structure + H700 codes: reads event1 (buttons) + event2 (volume 115/114) + event0 (power 116). MENU+vol = brightness, SELECT+vol = colortemp, plain vol = volume — tg5040 parity. No mute DIP switch on H700 (gpio243 watcher dropped). Tracks MENU on code 312 only — the synthetic 354 post-tap pulse is ignored (see MENU quirk above).
tg5040's list verbatim — the full 28-core set builds and ships (same aarch64/a53 target), with the picodrive LTO/patch-hygiene fixes noted in 01.
Most H700 code is platform-local, but these shared areas knowingly carry H700 integration. Review them when rebasing or adding another platform:
-
workspace/all/common/generic_video.cfails fast on SDL/window/GL initialization errors instead of continuing into a black screen. -
workspace/all/common/api.c/api.hexposePWR_requestSleep()for lid-driven sleep and close audio withSND_quit()before suspend. -
workspace/all/audiomon/audiomon.cppchecks BlueALSA availability; its Makefile gives H700 the compile-time omission for the unsupporteddelay 0option. -
workspace/all/minarch/makefileincludes H700 in the tg5040-class RA/CHD/SRM/ sample-rate feature filters. -
workspace/all/settings/settings.cpp,btmenu.*, and the settings Makefile carry the Anbernic vendor/model capabilities and stock-Bluetooth-audio integration. -
workspace/all/syncsettings/syncsettings.crestores the display settings and gamma LUT after suspend. -
workspace/all/bootlogo/bootlogo.ccontains the diagnostic empty-state handling and the optionalBOOTLOGO_PREVIEW_ROTATE_CWhook used by RG28XX. - Root/workspace makefiles register the H700 platform, dedicated
h700-toolchain, rfkill, LedControl, and the H700 Bootlogo build.
Any change in this surface requires the H700 build plus the tg5040 regression build
recorded as BUILD-01 and BUILD-02 in 08. A new shared button or PLAT_* hook must be
defined on every platform; a full build is required because single-pak builds omit
some translation units.
-
btmanager/(BlueZ-upgrade pakz — H700 uses firmware-provided BlueZ) -
Reversed. That reasoning was wrong twice over: the tg5040 limbo is a PMIC latch that no init system can fix, and the base OS runs BusyBox init with no systemd at all. Both tools are now ported — see 06.poweroff_next/,reboot_next(systemd poweroff/reboot work fine) -
LED animation wiring (Partly reversed.led_anim), ledcontrol.elf (gated to tg50x0 inworkspace/makefile)led_animreally is TrimUI-only, but the premise that RG XX has no RGB LEDs was wrong — RG40XX H/V and RG CubeXX drive theirs from an MCU over UART5 (00).ledcontrol.elfnow builds and ships for h700 as well, and LedControl is platform-aware: it asks the platform for the light count, names and effect list, and shows a "no RGB lights" screen on the models without them. Bootlogo was initially gated off too, but has since been ported:Bootlogo.pakbuilds for h700 with resolution-keyed preset folders (640x480,720x480,480x640,720x720) selected via$DEVICE, writesbootlogo.bmptommcblk0p2(BOOTLOGO_PARTITIONinplatform.h), backs up the stock logo asoriginal.bmpon first apply (see 02 for the TF1-write-policy exception), and on the RG28XX rotates previews to boot orientation (BOOTLOGO_PREVIEW_ROTATE_CW, 04) -
trimui_inputdinteractions (turbo, gpio 5V export)
New vs tg5040: rfkill/ — a minimal from-scratch /dev/rfkill ioctl tool (~120
lines), since the stock binary can't be relied on.