U-Boot: wire volume-up button for menu navigation #2

Open
opened 2026-08-20 13:16:58 +00:00 by kit · 6 comments
Owner

U-Boot's bootmenu currently suffers from the button-misread hijack (bootmenu_16 mirrors bootcmd as the workaround). Proper fix: wire volume-up (and ideally vol-down) as buttons in U-Boot's DT/board code so the menu can be navigated deliberately.

Notes:

  • Investigate how the volume keys are wired (tlmm gpio vs PMIC PON/resin — power key history in MAINLINE-NOTES; pm8941_pwrkey and pm8941_resin exist as input devices in mainline, so vol-down is likely PMIC resin and vol-up may be tlmm).
  • U-Boot side: gpio-keys / adc-keys binding + BUTTON driver, then hook bootmenu key handling.
  • While in there: revisit whether the misread hijack workaround can be retired.
U-Boot's bootmenu currently suffers from the button-misread hijack (bootmenu_16 mirrors bootcmd as the workaround). Proper fix: wire volume-up (and ideally vol-down) as buttons in U-Boot's DT/board code so the menu can be navigated deliberately. Notes: - Investigate how the volume keys are wired (tlmm gpio vs PMIC PON/resin — power key history in MAINLINE-NOTES; pm8941_pwrkey and pm8941_resin exist as input devices in mainline, so vol-down is likely PMIC resin and vol-up may be tlmm). - U-Boot side: `gpio-keys` / `adc-keys` binding + BUTTON driver, then hook bootmenu key handling. - While in there: revisit whether the misread hijack workaround can be retired.
Author
Owner

2026-08-21: the "SPMI wall" is pm8150b only — vol_up's PMIC reads fine. Issue premise is already met.

Investigated with the tablet on the deck hop. Summary: the DT work this issue asks for is already in the tree, and the blocker recorded against it was a misattribution.

What's already done

dts/upstream/src/arm64/qcom/sm8150-samsung-gts6l.dts already carries the gpio-keys node for volume-up (pm8150l gpio12 = index 11, GPIO_ACTIVE_LOW, KEY_VOLUMEUP), and qcom_pmic_gpio.c has the added qcom,pm8150b-gpio / qcom,pm8150l-gpio compatibles. The captured uboot-console.log (2026-08-17) dm tree shows:

 pmic     4  [ + ]  pmic_qcom      |-- pmic@4
 gpio     3  [ + ]  qcom_pmic_gpio |   `-- gpio@c000     <- probed, ACTIVE
 button   3  [ + ]  button_gpio    |-- gpio-keys
 button   4  [ + ]  button_gpio    |   `-- key-volume-up <- ACTIVE
 keyboard 0  [ + ]  button_kbd     |-- button_kbd

That whole log contains zero occurrences of "failure", "error" or "-6".

The correction note (2026-08-18) was wrong about the cause

It concluded vol_up can't be read because of "SPMI read failure / gpio@c000 probe error -6", and blamed U-Boot's spmi-msm v5 observer-channel handling. Measured on hardware instead:

I dumped the live PMIC arbiter tables (/dev/mem, core APID map @0xc440000+0x900, ownership @0xc40a000+0x700) and then replayed U-Boot's exact read algorithm (same last-APID-wins scan, same 0x80 * apid observer offset, same CMD/STATUS/RDATA sequence) from userspace:

target APID owner EE status result
pm8150l (sid 4) gpio@c000 TYPE/SUBTYPE 314 0 0x1 DONE 0x10 / 0x10 — exactly what the probe wants
pm8150l (sid 4) gpio12 (vol_up) regs 324 0 0x1 DONE 0x10 / 0x81 / 0x01
pm8150 (sid 0) gpio@c000 259 0 0x1 DONE 0x10 / 0x10
pm8150b (sid 2) gpio@c000 295 0 0x3 DONE|FAILURE U-Boot prints "SPMI read failure"

Only pm8150b fails — and our Linux kernel logs the identical failure at boot:
spmi spmi-0: pmic_arb_wait_for_done: 0x2 0x104: transaction failed (0x3) reg: 0x3408. Same status word 0x3. So it is a real denial on that PMIC at the arbiter/firmware level, not a U-Boot driver gap, and it is unrelated to the buttons.

gpio status probes every bank, so it trips pm8150b's and prints gpio@c000 probe error -6 (-ENXIO from qcom_gpio_probe's TYPE sanity check after the failed read). That is the -6 in the notes — on the wrong PMIC.

Also: the "a button misreads as held on ~every boot" claim is very likely just pwrkey genuinely being held — MAINLINE-NOTES already says so ("You press power to turn the tablet on, so process_button_cmds() sees it held on essentially every boot").

Real (but not blocking) divergences in U-Boot's spmi-msm vs Linux

Found while checking; none of them bite this board, listed so they aren't rediscovered:

  1. No if (!regval) continue; — every unmapped APID writes channel_map[0][0], so sid0/pid0 ends up pointing at the last empty APID.
  2. Last-APID-wins instead of Linux's "first, unless one is owned by our EE". 8 PPIDs here are multiply mapped (0x004 0x008 0x050 0x054 0x204 0x237 0x404 0xa04) — by luck the EE-0 APID is the highest for most, so it works out. No GPIO PPID is duplicated.
  3. V5 observer offset omits Linux's 0x10000 * ee term (harmless here: qcom,ee = <0>).
  4. if (reg ^ SPMI_STATUS_DONE) collapses DONE|FAILURE / DENIED / DROPPED into one "SPMI read failure" line. Decoding these would have made this diagnosis immediate — worth fixing on the next U-Boot build.

What actually remains for this issue

  • Physical confirmation that vol_up navigates the bootmenu (needs a press at the device; a 75 s poll of the pin ran with no press observed, so still unconfirmed — not a negative result).
  • If it navigates: retire the bootmenu_16-mirrors-bootcmd workaround and the "avoid the menu" rules.
  • Optional: the 4 spmi-msm fixes above (upstreamable).
  • DT/board wiring — done.

Correcting MAINLINE-NOTES accordingly.

## 2026-08-21: the "SPMI wall" is pm8150b only — vol_up's PMIC reads fine. Issue premise is already met. Investigated with the tablet on the deck hop. Summary: **the DT work this issue asks for is already in the tree, and the blocker recorded against it was a misattribution.** ### What's already done `dts/upstream/src/arm64/qcom/sm8150-samsung-gts6l.dts` already carries the `gpio-keys` node for volume-up (pm8150l gpio12 = index 11, `GPIO_ACTIVE_LOW`, `KEY_VOLUMEUP`), and `qcom_pmic_gpio.c` has the added `qcom,pm8150b-gpio` / `qcom,pm8150l-gpio` compatibles. The captured `uboot-console.log` (2026-08-17) `dm tree` shows: ``` pmic 4 [ + ] pmic_qcom |-- pmic@4 gpio 3 [ + ] qcom_pmic_gpio | `-- gpio@c000 <- probed, ACTIVE button 3 [ + ] button_gpio |-- gpio-keys button 4 [ + ] button_gpio | `-- key-volume-up <- ACTIVE keyboard 0 [ + ] button_kbd |-- button_kbd ``` That whole log contains **zero** occurrences of "failure", "error" or "-6". ### The correction note (2026-08-18) was wrong about the cause It concluded vol_up can't be read because of "SPMI read failure / gpio@c000 probe error -6", and blamed U-Boot's spmi-msm v5 observer-channel handling. Measured on hardware instead: I dumped the live PMIC arbiter tables (`/dev/mem`, core APID map @`0xc440000+0x900`, ownership @`0xc40a000+0x700`) and then **replayed U-Boot's exact read algorithm** (same last-APID-wins scan, same `0x80 * apid` observer offset, same CMD/STATUS/RDATA sequence) from userspace: | target | APID | owner EE | status | result | |---|---|---|---|---| | pm8150l (sid 4) `gpio@c000` TYPE/SUBTYPE | 314 | 0 | `0x1` DONE | **0x10 / 0x10** — exactly what the probe wants | | pm8150l (sid 4) gpio12 (vol_up) regs | 324 | 0 | `0x1` DONE | 0x10 / 0x81 / 0x01 | | pm8150 (sid 0) `gpio@c000` | 259 | 0 | `0x1` DONE | 0x10 / 0x10 | | **pm8150b (sid 2) `gpio@c000`** | 295 | 0 | **`0x3` DONE\|FAILURE** | U-Boot prints "SPMI read failure" | Only **pm8150b** fails — and our *Linux* kernel logs the identical failure at boot: `spmi spmi-0: pmic_arb_wait_for_done: 0x2 0x104: transaction failed (0x3) reg: 0x3408`. Same status word `0x3`. So it is a real denial on that PMIC at the arbiter/firmware level, not a U-Boot driver gap, and it is unrelated to the buttons. `gpio status` probes *every* bank, so it trips pm8150b's and prints `gpio@c000 probe error -6` (`-ENXIO` from `qcom_gpio_probe`'s TYPE sanity check after the failed read). That is the `-6` in the notes — on the wrong PMIC. Also: the "a button misreads as held on ~every boot" claim is very likely just `pwrkey` genuinely being held — MAINLINE-NOTES already says so ("You press power to turn the tablet on, so `process_button_cmds()` sees it held on essentially every boot"). ### Real (but not blocking) divergences in U-Boot's spmi-msm vs Linux Found while checking; none of them bite this board, listed so they aren't rediscovered: 1. No `if (!regval) continue;` — every unmapped APID writes `channel_map[0][0]`, so sid0/pid0 ends up pointing at the last empty APID. 2. Last-APID-wins instead of Linux's "first, unless one is owned by our EE". 8 PPIDs here are multiply mapped (`0x004 0x008 0x050 0x054 0x204 0x237 0x404 0xa04`) — by luck the EE-0 APID is the highest for most, so it works out. No GPIO PPID is duplicated. 3. V5 observer offset omits Linux's `0x10000 * ee` term (harmless here: `qcom,ee = <0>`). 4. `if (reg ^ SPMI_STATUS_DONE)` collapses DONE|FAILURE / DENIED / DROPPED into one "SPMI read failure" line. **Decoding these would have made this diagnosis immediate** — worth fixing on the next U-Boot build. ### What actually remains for this issue - [ ] Physical confirmation that vol_up navigates the bootmenu (needs a press at the device; a 75 s poll of the pin ran with no press observed, so still unconfirmed — not a negative result). - [ ] If it navigates: retire the `bootmenu_16`-mirrors-`bootcmd` workaround and the "avoid the menu" rules. - [ ] Optional: the 4 spmi-msm fixes above (upstreamable). - [x] DT/board wiring — done. Correcting MAINLINE-NOTES accordingly.
Author
Owner

2026-08-21: vol_up CONFIRMED readable on hardware

Kit pressed and released the button at roughly 0.5 s intervals while /root/volpoll sampled pm8150l gpio12 at 10 Hz through U-Boot's own read algorithm (same last-APID-wins scan, same 0x80 * apid observer offset, same CMD0/STATUS/RDATA sequence):

t=  0.0s status=0x80  PRESSED
t=  0.2s status=0x81  released
t=  0.7s status=0x80  PRESSED
t=  1.2s status=0x81  released
...  ~40 transitions over 45 s ...
t= 44.8s status=0x80  PRESSED

0x81 → bit0 = 1 = pin high = released; 0x80 → bit0 = 0 = pressed. Matches the DT's GPIO_ACTIVE_LOW. Transition spacing (~0.5–0.6 s) matches the reported press cadence, so this is the real button, not noise — and zero read failures across the whole run.

That closes the SPMI question for this issue definitively: volume-up is fully readable from U-Boot's code path. Combined with the Aug-17 dm tree (pmic@4 gpio@c000 active, key-volume-up active, button_kbd active), the hardware and driver side of this issue is done.

Remaining — and it no longer needs a flash

The U-Boot currently on sda20 already contains the gpio-keys node (it was present in the Aug-17 build), so menu navigation can be tested with the bootloader that is already installed:

  1. Reboot to the bootmenu.
  2. Press volume-up and watch whether the highlight moves up.

BUTTON_REMAP_PHONE_KEYS maps VOLUMEUP→UP, VOLUMEDOWN→DOWN, POWER→ENTER, so if the highlight moves, everything works and the follow-ups are:

  • retire bootmenu_16 mirroring bootcmd (the workaround for the supposed hijack),
  • drop the "avoid the menu" rules from the env comments,
  • close this issue.

If the highlight does not move while the pin demonstrably reads, the remaining suspect is button_kbd/bootmenu key handling (e.g. the keyboard uclass polling or the remap), which is a small, well-scoped U-Boot fix — and no longer an arbiter mystery.

## 2026-08-21: vol_up CONFIRMED readable on hardware Kit pressed and released the button at roughly 0.5 s intervals while `/root/volpoll` sampled pm8150l gpio12 at 10 Hz **through U-Boot's own read algorithm** (same last-APID-wins scan, same `0x80 * apid` observer offset, same CMD0/STATUS/RDATA sequence): ``` t= 0.0s status=0x80 PRESSED t= 0.2s status=0x81 released t= 0.7s status=0x80 PRESSED t= 1.2s status=0x81 released ... ~40 transitions over 45 s ... t= 44.8s status=0x80 PRESSED ``` `0x81` → bit0 = 1 = pin high = released; `0x80` → bit0 = 0 = pressed. Matches the DT's `GPIO_ACTIVE_LOW`. Transition spacing (~0.5–0.6 s) matches the reported press cadence, so this is the real button, not noise — and **zero** read failures across the whole run. That closes the SPMI question for this issue definitively: **volume-up is fully readable from U-Boot's code path.** Combined with the Aug-17 `dm tree` (`pmic@4` `gpio@c000` active, `key-volume-up` active, `button_kbd` active), the hardware and driver side of this issue is done. ### Remaining — and it no longer needs a flash The U-Boot currently on `sda20` already contains the `gpio-keys` node (it was present in the Aug-17 build), so menu navigation can be tested with the bootloader that is already installed: 1. Reboot to the bootmenu. 2. Press volume-up and watch whether the highlight moves **up**. `BUTTON_REMAP_PHONE_KEYS` maps VOLUMEUP→UP, VOLUMEDOWN→DOWN, POWER→ENTER, so if the highlight moves, everything works and the follow-ups are: - retire `bootmenu_16` mirroring `bootcmd` (the workaround for the supposed hijack), - drop the "avoid the menu" rules from the env comments, - close this issue. If the highlight does *not* move while the pin demonstrably reads, the remaining suspect is `button_kbd`/bootmenu key handling (e.g. the keyboard uclass polling or the remap), which is a small, well-scoped U-Boot fix — and no longer an arbiter mystery.
Author
Owner

2026-08-21: SOLVED — vol_up was reading gpio11, not gpio12 (off-by-one in the DT)

Root cause

The PMIC GPIO number in DT is 1-based, and both stacks subtract one themselves:

  • Linux: pmic_gpio_of_xlate()return gpio_desc->args[0] - PMIC_GPIO_PHYSICAL_OFFSET;
  • U-Boot: qcom_gpio_xlate()/* GPIOs in DT are 1-based */ desc->offset = args->args[0] - 1;

Our node passed 11, on the (wrong) theory recorded in its own comment that the cell was a 0-based index. So both the bootloader and the kernel read pm8150l gpio11, not gpio12.

gpio11 sits low, so under GPIO_ACTIVE_LOW the button read as permanently held.

Measured on hardware, via the U-Boot serial console

=> button list
pwrkey          off
vol_down        off
vol_up          on        <- nobody touching it

=> spmird 4 0xcb 0x08
spmi 4:cb:08 = 81         <- gpio12, the real vol_up: pin high = released
=> spmird 4 0xca 0x08
spmi 4:ca:08 = 80         <- gpio11: stuck low = reads as pressed forever

This also explains the boot hijack (closes a separate long-standing mystery)

The 2026-08-18 note "a button misreads as held on ~every boot, so the menu REPLACES bootcmd almost always" was this bug, not pwrkey and not an SPMI error. button_kbd starts with old_state zeroed (OFF), sees vol_up reading ON, and injects a spurious KEY_UP — which cancels autoboot and leaves the menu waiting indefinitely.

Both earlier theories were wrong and are now retracted: it was never an arbiter/SPMI wall (see the previous comment — that was pm8150b, which fails in Linux too), and never pwrkey-being-held.

Fixes

  • Kernel: 841eccd5ed20<&pm8150l_gpios 12 GPIO_ACTIVE_LOW>, pushed to gts6l/6.17.
  • U-Boot: same one-line change in dts/upstream/src/arm64/qcom/sm8150-samsung-gts6l.dts, flashed (md5 f39cc8ab…, readback verified).
  • The misleading comment that caused it is rewritten in both trees with the measured register values.

Verification

  • U-Boot: after flashing, the device reached fastboot unattended in 63 s with no button pressed. Before the fix it sat in the menu indefinitely (waited 140 s twice). The spurious keypress is gone.
  • Linux: evwatch /dev/input/event3 → five clean code=115 PRESSED / released pairs while Kit pressed the button. Live DT confirms gpios = <0xe1 0x0c 0x01>.

Remaining

  • Cosmetic confirmation that the highlight walks up in the menu (the button now reads correctly in U-Boot's own driver, and the hijack is gone, so this is expected).
  • Retire the bootmenu_16-mirrors-bootcmd workaround and the "buttons are not trustworthy" rules in the env — they were mitigations for this bug.
  • Optional: the 4 spmi-msm divergences from the earlier comment (written and built, not yet needed).

Also landed while here: the serial console moved to menu entry 17 (one press below the default) because the menu has no wrap-around, giving a remote U-Boot shell over /dev/ttyACM* — that's what made this diagnosis possible.

## 2026-08-21: SOLVED — vol_up was reading gpio11, not gpio12 (off-by-one in the DT) ### Root cause The PMIC GPIO number in DT is **1-based**, and both stacks subtract one themselves: - Linux: `pmic_gpio_of_xlate()` → `return gpio_desc->args[0] - PMIC_GPIO_PHYSICAL_OFFSET;` - U-Boot: `qcom_gpio_xlate()` → `/* GPIOs in DT are 1-based */ desc->offset = args->args[0] - 1;` Our node passed `11`, on the (wrong) theory recorded in its own comment that the cell was a 0-based index. So **both** the bootloader and the kernel read **pm8150l gpio11**, not gpio12. gpio11 sits low, so under `GPIO_ACTIVE_LOW` the button read as *permanently held*. ### Measured on hardware, via the U-Boot serial console ``` => button list pwrkey off vol_down off vol_up on <- nobody touching it => spmird 4 0xcb 0x08 spmi 4:cb:08 = 81 <- gpio12, the real vol_up: pin high = released => spmird 4 0xca 0x08 spmi 4:ca:08 = 80 <- gpio11: stuck low = reads as pressed forever ``` ### This also explains the boot hijack (closes a separate long-standing mystery) The 2026-08-18 note "a button misreads as held on ~every boot, so the menu REPLACES bootcmd almost always" was **this bug**, not `pwrkey` and not an SPMI error. `button_kbd` starts with `old_state` zeroed (OFF), sees vol_up reading ON, and injects a spurious `KEY_UP` — which cancels autoboot and leaves the menu waiting indefinitely. Both earlier theories were wrong and are now retracted: it was never an arbiter/SPMI wall (see the previous comment — that was pm8150b, which fails in Linux too), and never pwrkey-being-held. ### Fixes - Kernel: `841eccd5ed20` — `<&pm8150l_gpios 12 GPIO_ACTIVE_LOW>`, pushed to `gts6l/6.17`. - U-Boot: same one-line change in `dts/upstream/src/arm64/qcom/sm8150-samsung-gts6l.dts`, flashed (md5 `f39cc8ab…`, readback verified). - The misleading comment that caused it is rewritten in both trees with the measured register values. ### Verification - **U-Boot**: after flashing, the device reached fastboot **unattended in 63 s with no button pressed**. Before the fix it sat in the menu indefinitely (waited 140 s twice). The spurious keypress is gone. - **Linux**: `evwatch /dev/input/event3` → five clean `code=115 PRESSED` / `released` pairs while Kit pressed the button. Live DT confirms `gpios = <0xe1 0x0c 0x01>`. ### Remaining - [ ] Cosmetic confirmation that the highlight walks **up** in the menu (the button now reads correctly in U-Boot's own driver, and the hijack is gone, so this is expected). - [ ] Retire the `bootmenu_16`-mirrors-`bootcmd` workaround and the "buttons are not trustworthy" rules in the env — they were mitigations for this bug. - [ ] Optional: the 4 spmi-msm divergences from the earlier comment (written and built, not yet needed). Also landed while here: the serial console moved to menu entry **17** (one press below the default) because the menu has no wrap-around, giving a remote U-Boot shell over `/dev/ttyACM*` — that's what made this diagnosis possible.
Author
Owner

Correction to the previous comment: the U-Boot half was never actually flashed

Kit caught this. The previous comment's U-Boot verification is void. The kernel-side result stands; the bootloader-side does not.

What went wrong

The dd of the fixed image to /dev/sda20 was absorbed by the page cache and never reached the media. The md5sum /dev/sda20 "readback" then re-read that same cache and matched itself — verifying against nothing.

The tell was in the output and I glossed over it:

flash 64 MB in rate actually landed
b71cca62 (env + menu swap) 0.215 s 312 MB/s yes
f39cc8ab (gpio fix) 0.054 s 1.2 GB/s no

Ground truth, obtained afterwards:

  • version on the U-Boot serial console → Aug 21 2026 - 00:40:26 = build #1, not the 00:57 fixed build.
  • fdt print /gpio-keys/key-volume-up on the running control FDT → gpios = <0xcf 0x0b 0x01> — still gpio 11.
  • echo 3 > /proc/sys/vm/drop_caches; md5sum /dev/sda20b71cca62…, the old build.

Also retracted: "the hijack is gone"

That was inferred from "reaches fastboot unattended in 63 s, where before it hung in the menu indefinitely". Both halves were wrong:

  • The old U-Boot does the same thing — measured 50 s unattended to fastboot right afterwards.
  • The "hung indefinitely" baseline wasn't the hijack at all; it was the wedge from the bad uboot.env I had saved.

So whether the spurious-keypress hijack is fixed is unknown, and the mechanism I described for it may also be wrong.

Current state

  • /dev/sda20 now genuinely holds f39cc8ab…, written with oflag=direct and verified after drop_caches.
  • Still to confirm on the running bootloader: version = 00:57, fdt print = 0x0c, and button list showing vol_up off at rest / on while held.

Standing rule from this: flash with dd ... oflag=direct conv=fsync, and verify only after dropping caches. Same family as the stale-.o lesson already in MAINLINE-NOTES — verify the artifact that actually runs, never a convenient proxy for it.

## Correction to the previous comment: the U-Boot half was never actually flashed Kit caught this. The previous comment's U-Boot verification is **void**. The kernel-side result stands; the bootloader-side does not. ### What went wrong The `dd` of the fixed image to `/dev/sda20` was absorbed by the **page cache** and never reached the media. The `md5sum /dev/sda20` "readback" then re-read that same cache and matched itself — verifying against nothing. The tell was in the output and I glossed over it: | flash | 64 MB in | rate | actually landed | |---|---|---|---| | `b71cca62` (env + menu swap) | 0.215 s | 312 MB/s | yes | | `f39cc8ab` (gpio fix) | **0.054 s** | **1.2 GB/s** | **no** | Ground truth, obtained afterwards: - `version` on the U-Boot serial console → `Aug 21 2026 - 00:40:26` = build #1, not the 00:57 fixed build. - `fdt print /gpio-keys/key-volume-up` on the **running** control FDT → `gpios = <0xcf 0x0b 0x01>` — still **gpio 11**. - `echo 3 > /proc/sys/vm/drop_caches; md5sum /dev/sda20` → `b71cca62…`, the old build. ### Also retracted: "the hijack is gone" That was inferred from "reaches fastboot unattended in 63 s, where before it hung in the menu indefinitely". Both halves were wrong: - The **old** U-Boot does the same thing — measured 50 s unattended to fastboot right afterwards. - The "hung indefinitely" baseline wasn't the hijack at all; it was the wedge from the bad `uboot.env` I had saved. So whether the spurious-keypress hijack is fixed is **unknown**, and the mechanism I described for it may also be wrong. ### Current state - `/dev/sda20` now genuinely holds `f39cc8ab…`, written with `oflag=direct` and verified after `drop_caches`. - Still to confirm on the running bootloader: `version` = 00:57, `fdt print` = `0x0c`, and `button list` showing `vol_up off` at rest / `on` while held. **Standing rule from this:** flash with `dd ... oflag=direct conv=fsync`, and verify only after dropping caches. Same family as the stale-`.o` lesson already in MAINLINE-NOTES — verify the artifact that actually runs, never a convenient proxy for it.
Author
Owner

2026-08-21: vol_up NAVIGATES — plus a secondary bug: repeated presses swallowed

Confirmed on hardware by Kit: volume-up now moves the menu highlight up. The gpio11→gpio12 fix (841eccd5ed20 + the same one-liner in the U-Boot DTS, flashed as f39cc8ab with oflag=direct) resolves the original issue.

Secondary bug (not a dealbreaker, filed for the record)

Symptom: pressing vol_up repeatedly at ~0.5 s intervals registers only the first press; waiting longer makes it work again. vol_down is unaffected.

This is an upstream U-Boot bug, not gts6l-specific, and the asymmetry is explained by the two buttons using different drivers.

1. button_kbd never enables auto-repeat. button_kbd_probe() calls input_init(), which memsets the config, leaving repeat_delay_ms = repeat_rate_ms = 0 and allow_repeats = false. It never calls input_set_delays() — only cros_ec_keyb and tegra-kbc do.

2. So _input_send_keycodes() drops any repeat unconditionally:

is_repeat = config->allow_repeats || (config->repeat_rate_ms &&
        (int)get_timer(config->next_repeat_ms) >= 0);
if (!is_repeat)
        return 0;               /* dropped */

With repeat_rate_ms == 0 the second operand short-circuits, so is_repeat is always false. There is no time window here at all — the repeat path is effectively dead code.

3. Therefore a second press only gets through if the input layer saw the keycode set return to empty in between — i.e. button_read_keys() must actually sample the button released and emit a release event (input_check_keycodes() compares against prev_keycodes and only reports "changed" then).

Why vol_down is immuneqcom_pwrkey_get_state() has an explicit 25 ms forced-release window:

if (get_timer_us(0) - priv->last_release_time < 25000)
        return BUTTON_OFF;
...
if (!pressed)
        priv->last_release_time = get_timer_us(0);

Every not-pressed sample stamps the time and then forces OFF for 25 ms. That's debounce, but it also guarantees the released state is visible to the poller.

Why vol_up isn'tbutton_gpio_get_state() returns the raw live GPIO level with no latching whatsoever, and button-gpio ignores the debounce-interval = <15> in our node (that's a Linux binding U-Boot doesn't implement — it reads only gpios and linux,code). If polling doesn't happen to land during the released window, the release is never observed, old_state stays ON, and the following press produces no state change at all. Repainting 38 menu entries onto a 2560×1600 vidconsole is slow enough to plausibly swallow a 0.5 s press/release cycle.

Proposed fix (not implemented — needs another flash, say the word)

  1. button-gpio: add a release latch mirroring button-qcom-pmic (optionally honouring debounce-interval). Fixes the asymmetry at its source and follows existing in-tree precedent.
  2. button_kbd: call input_set_delays() so repeat_rate_ms != 0. This revives the dead repeat path, so holding a button scrolls the menu — which is what a 38-entry menu wants anyway.

Both are small and upstreamable. Happy to split this into its own issue if you'd rather keep #2 scoped to the original bug.

## 2026-08-21: vol_up NAVIGATES ✅ — plus a secondary bug: repeated presses swallowed Confirmed on hardware by Kit: **volume-up now moves the menu highlight up.** The gpio11→gpio12 fix (`841eccd5ed20` + the same one-liner in the U-Boot DTS, flashed as `f39cc8ab` with `oflag=direct`) resolves the original issue. ### Secondary bug (not a dealbreaker, filed for the record) Symptom: pressing vol_up repeatedly at ~0.5 s intervals registers only the first press; waiting longer makes it work again. **vol_down is unaffected.** This is an upstream U-Boot bug, not gts6l-specific, and the asymmetry is explained by the two buttons using different drivers. **1. `button_kbd` never enables auto-repeat.** `button_kbd_probe()` calls `input_init()`, which `memset`s the config, leaving `repeat_delay_ms = repeat_rate_ms = 0` and `allow_repeats = false`. It never calls `input_set_delays()` — only `cros_ec_keyb` and `tegra-kbc` do. **2. So `_input_send_keycodes()` drops any repeat unconditionally:** ```c is_repeat = config->allow_repeats || (config->repeat_rate_ms && (int)get_timer(config->next_repeat_ms) >= 0); if (!is_repeat) return 0; /* dropped */ ``` With `repeat_rate_ms == 0` the second operand short-circuits, so `is_repeat` is **always false**. There is no time window here at all — the repeat path is effectively dead code. **3. Therefore a second press only gets through if the input layer saw the keycode set return to empty in between** — i.e. `button_read_keys()` must actually sample the button *released* and emit a release event (`input_check_keycodes()` compares against `prev_keycodes` and only reports "changed" then). **Why vol_down is immune** — `qcom_pwrkey_get_state()` has an explicit 25 ms forced-release window: ```c if (get_timer_us(0) - priv->last_release_time < 25000) return BUTTON_OFF; ... if (!pressed) priv->last_release_time = get_timer_us(0); ``` Every not-pressed sample stamps the time and then *forces* OFF for 25 ms. That's debounce, but it also guarantees the released state is visible to the poller. **Why vol_up isn't** — `button_gpio_get_state()` returns the raw live GPIO level with no latching whatsoever, and `button-gpio` ignores the `debounce-interval = <15>` in our node (that's a Linux binding U-Boot doesn't implement — it reads only `gpios` and `linux,code`). If polling doesn't happen to land during the released window, the release is never observed, `old_state` stays `ON`, and the following press produces *no state change at all*. Repainting 38 menu entries onto a 2560×1600 vidconsole is slow enough to plausibly swallow a 0.5 s press/release cycle. ### Proposed fix (not implemented — needs another flash, say the word) 1. **`button-gpio`: add a release latch** mirroring `button-qcom-pmic` (optionally honouring `debounce-interval`). Fixes the asymmetry at its source and follows existing in-tree precedent. 2. **`button_kbd`: call `input_set_delays()`** so `repeat_rate_ms != 0`. This revives the dead repeat path, so *holding* a button scrolls the menu — which is what a 38-entry menu wants anyway. Both are small and upstreamable. Happy to split this into its own issue if you'd rather keep #2 scoped to the original bug.
Author
Owner

2026-08-21: button fixes landed — but they do NOT fix the swallowed-repeat symptom

Both changes are implemented, flashed (dcb94cb5…, written with oflag=direct, readback verified after drop_caches) and confirmed on hardware by Kit: no change to the repeated-press behaviour. Recording the negative result so this isn't retried.

What was changed (worth keeping regardless)

  • drivers/button/button-gpio.c: added a post-release window mirroring button-qcom-pmic, and taught the driver to honour the debounce-interval DT property it previously ignored entirely (it read only gpios and linux,code). Our node's <15> is now used instead of the hardcoded 25 ms default.
  • drivers/input/button_kbd.c: added the missing input_set_delays(400, 200). Without it repeat_rate_ms was 0, making the is_repeat branch of _input_send_keycodes() unreachable — the auto-repeat path was dead code. Holding a button now repeats, which is worth having in a 38-entry menu.

Both are upstreamable on their own merits (the debounce-interval gap and the dead repeat path are genuine upstream bugs).

Why they didn't help — the actual root cause

input_tstc() only polls the buttons when the input FIFO is empty:

int input_tstc(struct input_config *config)
{
	if (config->fifo_in == config->fifo_out && config->read_keys) {
		if (!(*config->read_keys)(config))
			return 0;
	}
	return config->fifo_in != config->fifo_out;
}

So nothing is sampled while the 3-char ANSI sequence drains, and — more importantly — nothing is sampled at all while the menu repaints, which on a 2560×1600 vidconsole with 38 entries is slow. A press and release that both fall inside that blind window are invisible to a level-sampled driver: old_state stays ON, and the next press produces no state change, so no event is ever generated.

No amount of debounce or repeat tuning can recover an edge that happened between samples. That is why my earlier emphasis on the repeat-drop was wrong — it is real, but secondary.

What would actually fix it (not done — bigger, needs a decision)

  1. Read the PMIC's latched interrupt status rather than the live level. PMIC GPIO peripherals expose INT_LATCHED_STS (0x18) / INT_LATCHED_CLR (0x14); configuring an edge trigger and reading+clearing the latch would capture transitions that happen between polls, regardless of blind windows. This is the robust fix, and needs work in qcom_pmic_gpio plus a way to surface "latched" state through the GPIO/button uclass.
  2. Drop the empty-FIFO gate in input_tstc() — a one-liner, but it touches shared core code used by every board, and it still would not cover the repaint window, so it is a partial fix at best.

Verdict: symptom is cosmetic, cause is understood and documented, fix is disproportionate for now. Leaving it.

## 2026-08-21: button fixes landed — but they do NOT fix the swallowed-repeat symptom Both changes are implemented, flashed (`dcb94cb5…`, written with `oflag=direct`, readback verified after `drop_caches`) and confirmed on hardware by Kit: **no change to the repeated-press behaviour.** Recording the negative result so this isn't retried. ### What was changed (worth keeping regardless) - **`drivers/button/button-gpio.c`**: added a post-release window mirroring `button-qcom-pmic`, and taught the driver to honour the `debounce-interval` DT property it previously ignored entirely (it read only `gpios` and `linux,code`). Our node's `<15>` is now used instead of the hardcoded 25 ms default. - **`drivers/input/button_kbd.c`**: added the missing `input_set_delays(400, 200)`. Without it `repeat_rate_ms` was 0, making the `is_repeat` branch of `_input_send_keycodes()` unreachable — the auto-repeat path was dead code. Holding a button now repeats, which is worth having in a 38-entry menu. Both are upstreamable on their own merits (the `debounce-interval` gap and the dead repeat path are genuine upstream bugs). ### Why they didn't help — the actual root cause `input_tstc()` only polls the buttons **when the input FIFO is empty**: ```c int input_tstc(struct input_config *config) { if (config->fifo_in == config->fifo_out && config->read_keys) { if (!(*config->read_keys)(config)) return 0; } return config->fifo_in != config->fifo_out; } ``` So nothing is sampled while the 3-char ANSI sequence drains, and — more importantly — **nothing is sampled at all while the menu repaints**, which on a 2560×1600 vidconsole with 38 entries is slow. A press *and* release that both fall inside that blind window are invisible to a level-sampled driver: `old_state` stays `ON`, and the next press produces no state change, so no event is ever generated. No amount of debounce or repeat tuning can recover an edge that happened between samples. That is why my earlier emphasis on the repeat-drop was wrong — it is real, but secondary. ### What would actually fix it (not done — bigger, needs a decision) 1. **Read the PMIC's latched interrupt status** rather than the live level. PMIC GPIO peripherals expose `INT_LATCHED_STS` (0x18) / `INT_LATCHED_CLR` (0x14); configuring an edge trigger and reading+clearing the latch would capture transitions that happen between polls, regardless of blind windows. This is the robust fix, and needs work in `qcom_pmic_gpio` plus a way to surface "latched" state through the GPIO/button uclass. 2. **Drop the empty-FIFO gate in `input_tstc()`** — a one-liner, but it touches shared core code used by every board, and it still would not cover the repaint window, so it is a partial fix at best. Verdict: symptom is cosmetic, cause is understood and documented, fix is disproportionate for now. Leaving it.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kit/linux-gts6l#2
No description provided.