mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-07-12 18:19:42 -07:00
mipi-generator: extract joypad wiring + auto-discover custom vendor props
The overlay generator now propagates board-specific hardware that the
previous version dropped on the floor:
- Amux selectors and per-button GPIO pin numbers (varies per subdevice:
clone uses 16/15/11, original 11/8/13, soysauce 13/12/17)
- button-adc tunings (deadzone/fuzz/flat) when vendor differs
- rumble-gpio (R36S V20 2025-05-18, GR36 — fixes "rumble stuck on")
- Panel status LEDs red/green/blue1/blue2 (8 soysauce V04+/V05 boards)
Adds an auto-discovery pass that propagates ANY remaining vendor
property not in our whitelist/blacklist, logging each propagation in
applied.txt so future board variants surface their quirks instead of
silently inheriting the original layout.
Blacklist preserves joypad-name/product/revision (ES gamecontrollerdb
match), per-button linux,code (vendor uses F-keys, ArchR uses standard
BTN_*) and PMIC regulator ranges (our base intentionally has expanded
ranges for the 1512MHz CPU OC).
This commit is contained in:
@@ -308,6 +308,249 @@ def switch_joypad_to_mymini(overlay, jp_ovl):
|
||||
jp_ovl.set_property('poll-interval', 10)
|
||||
|
||||
|
||||
def copy_gpio_prop(dt, symbols, overlay, src_node, prop_name, ovl_target_path):
|
||||
"""Propagate a *-gpios property from `src_node` (vendor DTB) into the
|
||||
overlay at `ovl_target_path`, attaching the correct __fixup__ so the
|
||||
bootloader resolves the phandle against the live base DTB.
|
||||
|
||||
Returns True on success, False if the property is absent or the
|
||||
referenced controller cannot be resolved. Logs nothing — caller can
|
||||
decide whether to warn.
|
||||
"""
|
||||
if not src_node.exist_property(prop_name):
|
||||
return False
|
||||
try:
|
||||
data = src_node.get_property(prop_name).data
|
||||
except Exception:
|
||||
return False
|
||||
if not data or len(data) < 3:
|
||||
return False
|
||||
try:
|
||||
gpio_path = resolve_phandle(dt, data[0])
|
||||
gpio_sym = next(p.name for p in symbols.props if p.value == gpio_path)
|
||||
except (StopIteration, Exception):
|
||||
return False
|
||||
overlay.set_property(prop_name, [0xffffffff, data[1], data[2]],
|
||||
path=ovl_target_path)
|
||||
add_fixup(overlay, gpio_sym, ovl_target_path + ':' + prop_name + ':0')
|
||||
return True
|
||||
|
||||
|
||||
def extract_joypad_wiring(dt, overlay, jp_ovl, args, symbols):
|
||||
"""Propagate vendor-specific joypad wiring to the overlay:
|
||||
* amux-a-gpios, amux-b-gpios, amux-en-gpios (root joypad node)
|
||||
* gpios on each swN child (per-button)
|
||||
* button-adc-{deadzone,fuzz,flat} when vendor differs from defaults
|
||||
|
||||
These vary per board family: clone uses pins 16/15/11, original
|
||||
uses 11/8/13, soysauce uses 13/12/17. The button GPIOs vary even
|
||||
more (different banks). Without this propagation the merged DTB
|
||||
keeps our base's "original" wiring and clone/soysauce hardware
|
||||
boots with all buttons mapped to wrong pins.
|
||||
"""
|
||||
try:
|
||||
jp = dt.get_node('/odroidgo3-joypad')
|
||||
except Exception:
|
||||
return
|
||||
|
||||
ovl_root = jp_ovl.path + '/__overlay__'
|
||||
|
||||
# amux selectors
|
||||
for prop in ('amux-a-gpios', 'amux-b-gpios', 'amux-en-gpios'):
|
||||
if copy_gpio_prop(dt, symbols, overlay, jp, prop, ovl_root):
|
||||
data = jp.get_property(prop).data
|
||||
args['logger'].info(f"{prop} pin={data[1]} flags={data[2]} on {ovl_root}")
|
||||
|
||||
# per-button gpios (sw1..sw22 etc.)
|
||||
button_count = 0
|
||||
for sw_node in jp.nodes:
|
||||
name = sw_node.name
|
||||
if not name.startswith('sw'):
|
||||
continue
|
||||
target = ovl_root + '/' + name
|
||||
if copy_gpio_prop(dt, symbols, overlay, sw_node, 'gpios', target):
|
||||
button_count += 1
|
||||
if button_count:
|
||||
args['logger'].info(f"propagated {button_count} per-button gpios on {ovl_root}")
|
||||
|
||||
# button-adc tunings — only emit override if vendor differs from
|
||||
# the default our base ships. Saves overlay size for the common case.
|
||||
BUTTON_ADC_DEFAULTS = {
|
||||
'button-adc-deadzone': 96,
|
||||
'button-adc-fuzz': 48,
|
||||
'button-adc-flat': 48,
|
||||
}
|
||||
for prop, default in BUTTON_ADC_DEFAULTS.items():
|
||||
if not jp.exist_property(prop):
|
||||
continue
|
||||
val = jp.get_property(prop).value
|
||||
if val != default:
|
||||
overlay.set_property(prop, val, path=ovl_root)
|
||||
args['logger'].info(f"{prop}={val} (vendor differs from default {default})")
|
||||
|
||||
# rumble-gpio: some clone boards (R36S V20 2025-05-18 batch, GR36)
|
||||
# expose a dedicated GPIO that gates the rumble motor — without
|
||||
# propagating it the motor sits in its default state, which on
|
||||
# those boards is "on" (= constant vibration). The audit across
|
||||
# 44 vendor DTBs flagged this in Tier 3; we handle it explicitly
|
||||
# here so the overlay carries the right pin and the bootloader
|
||||
# phandle fixup resolves against our GPIO controller.
|
||||
if copy_gpio_prop(dt, symbols, overlay, jp, 'rumble-gpio', ovl_root):
|
||||
data = jp.get_property('rumble-gpio').data
|
||||
args['logger'].info(f"rumble-gpio pin={data[1]} flags={data[2]} on {ovl_root}")
|
||||
|
||||
|
||||
# ---------- Tier 3: auto-discovery of custom vendor properties --------------
|
||||
#
|
||||
# Some vendor BSPs ship board-specific properties we never planned for
|
||||
# (rumble-direction-fix, special-button-quirk, custom-trigger-mode, ...).
|
||||
# The "extract every known prop" approach leaves those on the cutting
|
||||
# room floor. Tier 3 walks the vendor tree, identifies anything we don't
|
||||
# already handle, and propagates it to the overlay — with logging so
|
||||
# the user knows exactly which non-standard props came along.
|
||||
|
||||
# Props we ALREADY handle via dedicated extraction logic above; don't
|
||||
# touch them in the auto-discovery pass.
|
||||
_KNOWN_PANEL_PROPS = frozenset({
|
||||
'compatible', 'reg', 'phandle', 'linux,phandle', 'status',
|
||||
'panel_description', 'panel-init-sequence', 'panel-exit-sequence',
|
||||
'display-timings', 'rotation',
|
||||
'dsi,format', 'dsi,lanes', 'dsi,flags', 'dsi,burst-mode', 'dsi,sync-mode',
|
||||
'prepare-delay-ms', 'reset-delay-ms', 'init-delay-ms',
|
||||
'enable-delay-ms', 'disable-delay-ms', 'unprepare-delay-ms',
|
||||
'width-mm', 'height-mm', 'bus-format', 'bpc',
|
||||
'reset-gpios', 'enable-gpios',
|
||||
'led-red-gpios', 'led-green-gpios',
|
||||
'led-blue1-gpios', 'led-blue2-gpios',
|
||||
'iovcc-supply', 'vdd-supply', 'vsp-supply', 'vsn-supply',
|
||||
'vcc-supply', 'power-supply', 'backlight', 'backlight-supply',
|
||||
})
|
||||
|
||||
_KNOWN_JOYPAD_PROPS = frozenset({
|
||||
'compatible', 'reg', 'phandle', 'linux,phandle', 'status',
|
||||
'pinctrl-names', 'pinctrl-0', 'pinctrl-1',
|
||||
'io-channel-names', 'io-channels',
|
||||
'amux-count', 'amux-channel-mapping',
|
||||
'amux-a-gpios', 'amux-b-gpios', 'amux-en-gpios',
|
||||
'button-adc-deadzone', 'button-adc-fuzz', 'button-adc-flat',
|
||||
'button-adc-scale', 'button-adc-tuned-scale',
|
||||
'abs_x-p-tuning', 'abs_x-n-tuning', 'abs_y-p-tuning', 'abs_y-n-tuning',
|
||||
'abs_rx-p-tuning', 'abs_rx-n-tuning', 'abs_ry-p-tuning', 'abs_ry-n-tuning',
|
||||
'abs-x-p-tuned-val', 'abs-x-n-tuned-val',
|
||||
'abs-y-p-tuned-val', 'abs-y-n-tuned-val',
|
||||
'abs-rx-p-tuned-val', 'abs-rx-n-tuned-val',
|
||||
'abs-ry-p-tuned-val', 'abs-ry-n-tuned-val',
|
||||
'abs-left-first', 'poll-interval',
|
||||
'invert-absx', 'invert-absy', 'invert-absrx', 'invert-absry',
|
||||
'swap-absxy', 'swap-absrxy', 'vendor-direction',
|
||||
'rumble-boost-weak', 'rumble-boost-strong',
|
||||
'rumble-gpio',
|
||||
'pwms', 'pwm-names',
|
||||
'key-gpios',
|
||||
})
|
||||
|
||||
_KNOWN_SW_PROPS = frozenset({
|
||||
'gpios', 'phandle', 'linux,phandle',
|
||||
'amux-channel',
|
||||
})
|
||||
|
||||
# Props that must NEVER be propagated from vendor, even if discovered.
|
||||
# Either userspace expects our specific value (joypad-name, linux,code)
|
||||
# or our base intentionally diverges (regulator ranges for OC).
|
||||
_BLACKLIST = frozenset({
|
||||
'joypad-name', 'joypad-product', 'joypad-revision',
|
||||
'linux,code', 'label',
|
||||
'regulator-min-microvolt', 'regulator-max-microvolt',
|
||||
'regulator-ramp-delay', 'regulator-initial-mode',
|
||||
'regulator-always-on', 'regulator-boot-on',
|
||||
})
|
||||
|
||||
|
||||
def _prop_is_phandle_array(prop):
|
||||
"""Heuristic: PropWords with values that look like phandles (small
|
||||
positive integers, multiple entries). Without knowing the exact
|
||||
layout (#xxx-cells) we can't remap; skip these and let the user
|
||||
add manual support if needed."""
|
||||
if not isinstance(prop, fdt.PropWords):
|
||||
return False
|
||||
# Common phandle arrays have 2-4 cells per entry; single-cell
|
||||
# numerics (delays, sizes) are safe to copy.
|
||||
if len(prop.data) <= 1:
|
||||
return False
|
||||
# If every value is small (< 0x1000), it's likely a numeric tuning
|
||||
# array; if any is "large" (>= 0x10), it's likely a phandle. Soft
|
||||
# rule but works for the props we've seen in the wild.
|
||||
return any(v >= 0x10 for v in prop.data)
|
||||
|
||||
|
||||
def _set_prop_on_overlay(overlay, prop, ovl_target_path):
|
||||
"""Copy a Property to the overlay tree at the given path. Handles
|
||||
the three concrete subclasses of fdt.Property we care about."""
|
||||
if isinstance(prop, fdt.PropStrings):
|
||||
overlay.set_property(prop.name, prop.data, path=ovl_target_path)
|
||||
elif isinstance(prop, fdt.PropWords):
|
||||
overlay.set_property(prop.name, prop.data, path=ovl_target_path)
|
||||
elif isinstance(prop, fdt.PropBytes):
|
||||
overlay.set_property(prop.name, bytes(prop.data), path=ovl_target_path)
|
||||
elif hasattr(prop, 'value') and prop.value is None:
|
||||
# Boolean-style empty property
|
||||
overlay.set_property(prop.name, True, path=ovl_target_path)
|
||||
else:
|
||||
# Unknown type — skip rather than crash
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def auto_discover_custom(dt, overlay, panel_ovl, jp_ovl, args):
|
||||
"""Walk panel + joypad + sw* subnodes in the vendor DTB. Any prop
|
||||
not in our known/handled set and not blacklisted gets propagated
|
||||
to the overlay. Phandle arrays are skipped (would need remap that
|
||||
we don't have layout info for). Logs each discovery."""
|
||||
discoveries = []
|
||||
|
||||
def _visit(node, known_set, ovl_target_path, scope_label):
|
||||
for p in node.props:
|
||||
if p.name in known_set or p.name in _BLACKLIST:
|
||||
continue
|
||||
if _prop_is_phandle_array(p):
|
||||
args['logger'].info(
|
||||
f"auto-discovery: skip {scope_label}/{p.name} "
|
||||
f"(phandle array, layout unknown)")
|
||||
continue
|
||||
if _set_prop_on_overlay(overlay, p, ovl_target_path):
|
||||
val = p.value if hasattr(p, 'value') else '<binary>'
|
||||
discoveries.append((scope_label, p.name, val))
|
||||
|
||||
# Panel
|
||||
try:
|
||||
panel = dt.get_node('/dsi@ff450000/panel@0')
|
||||
panel_ovl_path = panel_ovl.path + '/__overlay__/dsi@ff450000/panel@0'
|
||||
_visit(panel, _KNOWN_PANEL_PROPS, panel_ovl_path, 'panel')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Joypad root
|
||||
try:
|
||||
jp = dt.get_node('/odroidgo3-joypad')
|
||||
jp_ovl_path = jp_ovl.path + '/__overlay__'
|
||||
_visit(jp, _KNOWN_JOYPAD_PROPS, jp_ovl_path, 'joypad')
|
||||
# And each sw* subnode
|
||||
for sw in jp.nodes:
|
||||
if not sw.name.startswith('sw'):
|
||||
continue
|
||||
_visit(sw, _KNOWN_SW_PROPS, jp_ovl_path + '/' + sw.name,
|
||||
f'joypad/{sw.name}')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if discoveries:
|
||||
args['logger'].info(
|
||||
f"auto-discovery: propagated {len(discoveries)} custom vendor props")
|
||||
for scope, name, val in discoveries:
|
||||
args['logger'].info(f" custom: {scope}/{name} = {val!r}")
|
||||
return discoveries
|
||||
|
||||
|
||||
def make_dtbo(dtb_data, args):
|
||||
dt = fdt.parse_dtb(dtb_data)
|
||||
symbols = dt.get_node('__symbols__')
|
||||
@@ -386,6 +629,19 @@ def make_dtbo(dtb_data, args):
|
||||
except:
|
||||
pass
|
||||
|
||||
# Soysauce Y3506 V04+/V05 boards have status LEDs on the panel
|
||||
# itself (red + blue1/blue2/green) used for battery/charging
|
||||
# indication. Tier 3 audit flagged these in 8 vendor DTBs. Without
|
||||
# propagation the LED GPIOs default to the bootloader state — on
|
||||
# most boards they end up "on", giving the user a permanent red
|
||||
# light that doesn't react to charging state. Each is a standard
|
||||
# <phandle pin flags> triple, same layout as reset-gpios.
|
||||
for led_prop in ('led-red-gpios', 'led-green-gpios',
|
||||
'led-blue1-gpios', 'led-blue2-gpios'):
|
||||
if copy_gpio_prop(dt, symbols, overlay, panel, led_prop, panel_ovl_path):
|
||||
data = panel.get_property(led_prop).data
|
||||
args['logger'].info(f"panel {led_prop} pin={data[1]} flags={data[2]}")
|
||||
|
||||
|
||||
# If the vendor DTB explicitly disables /adc-keys, mirror that decision
|
||||
# in the overlay. We deliberately do NOT treat "/adc-keys absent" as
|
||||
@@ -437,6 +693,13 @@ def make_dtbo(dtb_data, args):
|
||||
jp_ovl.set_property('invert-absry', 1)
|
||||
args['logger'].info(f"invert right stick on {jp_ovl.path}")
|
||||
|
||||
# Propagate the vendor's joypad hardware wiring (amux pins, per-button
|
||||
# GPIOs, ADC tunings). Without this, clone and soysauce boards inherit
|
||||
# the "original" base layout and end up with controls all over the
|
||||
# place — see audit_all.py for the pin matrix across the 44 vendor
|
||||
# DTBs we ship.
|
||||
extract_joypad_wiring(dt, overlay, jp_ovl, args, symbols)
|
||||
|
||||
|
||||
try:
|
||||
snd = dt.get_node('/rk817-sound')
|
||||
@@ -510,6 +773,12 @@ def make_dtbo(dtb_data, args):
|
||||
except Exception as e:
|
||||
args['logger'].info(e)
|
||||
|
||||
# Tier 3: walk any remaining vendor properties we haven't already
|
||||
# handled and propagate them. Catches board-specific quirks (custom
|
||||
# rumble flags, exotic input properties, etc.) without us needing
|
||||
# to hardcode every possible vendor invention.
|
||||
auto_discover_custom(dt, overlay, panel_ovl, jp_ovl, args)
|
||||
|
||||
# Move fixups to the very end (if any)
|
||||
if overlay.exist_node('__fixups__'):
|
||||
fixups = overlay.get_node('__fixups__')
|
||||
|
||||
Reference in New Issue
Block a user