Add support for micro-gamepad compatibility in archr-joypad driver

This commit is contained in:
Douglas Teles
2026-05-14 13:32:40 -03:00
parent f3260292d2
commit f40d235197
3 changed files with 146 additions and 15 deletions
@@ -27,16 +27,17 @@ post_makeinstall_target() {
DTB_OVERLAY_UNLOAD="\/usr\/bin\/dtb_overlay set driver-gpu None"
;;
*)
# No DTB overlay shipped for this device. The gpudriver script
# detects the empty placeholder and (a) hides panfrost from the
# UI, (b) refuses runtime switches, and (c) on boot falls back to
# libmali if a stale setting still points at panfrost.
# No DTB overlay needed: kernel base DTS already has the GPU node
# with 'arm,mali-bifrost' compatible, so panfrost binds directly
# via modprobe. Switching between libmali and panfrost just
# requires loading/unloading the kernel module and swapping the
# userspace GL library binds.
PAN="panfrost"
DTB_OVERLAY_LOAD=""
DTB_OVERLAY_UNLOAD=""
;;
esac
sed -e "s/@PAN@/${PAN}/g" \
-i ${INSTALL}/usr/bin/gpudriver
@@ -15,14 +15,82 @@ get_current_driver() {
}
# Verify the GPU node ended up bound to a kernel driver. A successful
# modprobe doesn't always mean the driver actually attached to the
# hardware (mismatched compatible string, regulator/clock failure, etc).
# `/sys/class/drm/card0` only exists when *some* DRM driver is bound.
GPUDRIVER_LOG="/storage/.config/gpudriver.log"
# Append a one-line tagged entry to the persistent log. We keep this on
# /storage so it survives across reboots — the most common diagnostic
# need is "I picked panfrost and the system came up in libmali", which
# is invisible at boot otherwise.
log_event() {
local msg="$1"
mkdir -p "$(dirname "${GPUDRIVER_LOG}")" 2>/dev/null
echo "$(date '+%Y-%m-%d %H:%M:%S') gpudriver: ${msg}" >>"${GPUDRIVER_LOG}" 2>/dev/null
logger -t gpudriver "${msg}" 2>/dev/null
}
# Verify the GPU node ended up bound to the requested kernel driver AND
# that the display chain is alive. A successful modprobe doesn't always
# mean the driver attached to the hardware (mismatched compatible,
# regulator fail), and even a bound driver doesn't always mean the
# panel came up (panfrost can bind cleanly while the DSI bridge fails
# on a clone variant we haven't characterized, leaving a black screen).
# We treat both cases as a failure so the alternate driver gets a
# chance.
#
# When the check fails we drop the failure reason + the relevant dmesg
# tail to /storage/.config/gpudriver.log so users reporting "panfrost
# doesn't boot" can hand us actionable data instead of a black screen.
gpu_is_bound() {
for c in /sys/class/drm/card[0-9]*; do
[ -e "$c" ] && return 0
local driver="$1"
local connector status
local have_card=0 have_display=0 have_bind=0
# Give the kernel a beat to attach. Without this we frequently raced
# the bind on slow microSD boots and triggered a false fallback.
for _wait in 1 2 3 4 5; do
for c in /sys/class/drm/card[0-9]*; do
[ -e "$c" ] && have_card=1
done
[ $have_card -eq 1 ] && break
sleep 0.2
done
# The driver-specific sysfs node tells us the kernel actually attached
# the requested driver to the GPU platform device — not just that
# *some* DRM card exists. rockchip-vop creates card0 even without any
# GPU driver, so checking only /sys/class/drm is not enough.
case "${driver}" in
panfrost)
for g in /sys/bus/platform/drivers/panfrost/[a-f0-9]*.gpu; do
[ -e "$g" ] && have_bind=1
done
;;
libmali)
# mali_kbase exposes /dev/mali0 once bound.
[ -e /dev/mali0 ] && have_bind=1
;;
esac
# At least one DRM connector must report "connected". DSI panels on
# rk3326 report status via /sys/class/drm/cardN-DSI-1/status. If no
# connector ever becomes connected, the panel chain is dead and the
# user is looking at a black screen no matter what we do here, so
# signal failure and let the caller try the other driver.
for connector in /sys/class/drm/card[0-9]*-*/status; do
[ -e "$connector" ] || continue
read -r status <"$connector" 2>/dev/null
[ "$status" = "connected" ] && have_display=1 && break
done
if [ $have_card -eq 1 ] && [ $have_bind -eq 1 ] && [ $have_display -eq 1 ]; then
log_event "${driver} bound OK (card=1 bind=1 display=1)"
return 0
fi
log_event "${driver} bind check FAILED (card=${have_card} bind=${have_bind} display=${have_display})"
# Last lines mentioning the driver, so the failure mode is recoverable
# from /storage without needing a serial console.
dmesg 2>/dev/null | grep -iE "${driver}|mali|gpu@ff400000|panfrost" | tail -20 >>"${GPUDRIVER_LOG}" 2>/dev/null
return 1
}
@@ -56,14 +124,34 @@ load_driver() {
modprobe -r mali_kbase 2>/dev/null
modprobe @PAN@
# Reverse every bind the libmali path did. Order matters: undo the
# libEGL/libGLESv2/etc. binds from /usr/lib/mali first, then the
# /dev/null binds on libGL.so (mesa needs that path to exist), and
# finally the panfrost vulkan ICD masks. Missing any of these
# leaves apps opening /dev/null where libGL.so should be, which is
# what was making the system "not boot" for users picking panfrost
# from the UI: kernel binds cleanly, but every Mesa GL app crashes
# on dlopen because libGL.so is shadowed.
mount | grep -q "on /usr/lib/libEGL.so" && find /usr/lib/mali -type f -exec bash -c 'lib={}; umount ${lib/\/mali\//\/}' ';'
if [ -d /usr/lib/glesonly ]; then
mount | grep -q "on /usr/lib/libSDL2" && find /usr/lib/glesonly -type f -exec bash -c 'lib={}; umount ${lib/\/glesonly\//\/}' ';'
fi
# Mask mali Vulkan ICD so the loader only finds panfrost
# Restore real libGL.so on both archs. These are bound to /dev/null
# by the libmali path so vanilla GL apps cannot pick the wrong
# library; on panfrost we WANT Mesa's libGL to be visible.
mount | grep -q "on /usr/lib/libGL.so" && umount /usr/lib/libGL.so 2>/dev/null
mount | grep -q "on /usr/lib32/libGL.so" && umount /usr/lib32/libGL.so 2>/dev/null
# Unmask panfrost Vulkan ICD entries that the libmali path masked,
# then mask the mali ICD so the loader picks up panfrost only.
for icd in /usr/share/vulkan/icd.d/panfrost_icd.*.json; do
[ -f "${icd}" ] || continue
mount | grep -q "on ${icd}" && umount "${icd}" 2>/dev/null
done
[ -f /usr/share/vulkan/icd.d/mali.json ] && \
mount --bind /dev/null /usr/share/vulkan/icd.d/mali.json
( mount | grep -q "on /usr/share/vulkan/icd.d/mali.json" || \
mount --bind /dev/null /usr/share/vulkan/icd.d/mali.json )
;;
*)
exit 3
@@ -78,6 +166,7 @@ case "$1" in
;;
"--start")
get_current_driver
log_event "boot: selected driver=${CONFDRIVER}"
load_driver ${CONFDRIVER}
# If the requested driver didn't actually bind, fall back to the
# other one so the user gets a graphical system either way. Mali-G31
@@ -86,11 +175,16 @@ case "$1" in
# composited system with software rasterization that crawls. Try the
# alternate driver instead of letting the user think their device is
# just slow.
if ! gpu_is_bound; then
if ! gpu_is_bound "${CONFDRIVER}"; then
ALT="libmali"
[ "${CONFDRIVER}" = "libmali" ] && ALT="panfrost"
logger -t gpudriver "${CONFDRIVER} did not bind to GPU; trying ${ALT}"
log_event "${CONFDRIVER} did not bind to GPU; falling back to ${ALT}"
load_driver "${ALT}" 2>/dev/null
if gpu_is_bound "${ALT}"; then
log_event "fallback to ${ALT} succeeded"
else
log_event "fallback to ${ALT} ALSO FAILED — system will run software-rendered"
fi
fi
;;
"libmali")
@@ -0,0 +1,36 @@
From: ArchR <noreply@archr-linux>
Subject: [PATCH] archr-joypad: bind to "micro,gamepad" compatible
Some R36S clone variants (notably a V12-silkscreen / G80CA-1.3-sticker
hybrid surfacing in Discord reports) ship a vendor DTB with
'compatible = "micro,gamepad"' on the joypad node, instead of
'odroidgo3-joypad'. The wire format is multi-ADC (4 separate saradc
channels) plus a flat key-gpios array, similar enough to our
archr-joypad multi-ADC path that adding the compat string lets the
driver bind and start reading io-channels.
Caveats observed in the wild:
* io-channel-names on these boards use "button0".."button3" instead of
our usual "key-LX/LY/RX/RY". The kernel's iio framework looks up the
channel by index in io-channel-names, so the bind itself succeeds;
what may need flipping per board is the axis mapping (see the
'abs-left-first' property and the mapping_r_l / mapping_l_r arrays
in joypad_adc_setup).
* The vendor DTB encodes GPIO buttons as a single flat 'key-gpios'
array (18 entries on the boards we have seen) rather than the
individual swN { gpios = ... } child nodes our driver parses today.
The analog sticks should come up with this patch; the GPIO buttons
still need either a runtime translation in an overlay or a driver
extension. That is tracked separately; this patch closes the bind
failure so users at least have the sticks working.
--- a/archr-joypad.c
+++ b/archr-joypad.c
@@ -642,6 +642,7 @@
{ .compatible = "archr-joypad", },
{ .compatible = "odroidgo3-joypad", },
{ .compatible = "rocknix-joypad", },
+ { .compatible = "micro,gamepad", },
{},
};