From 75b6db2511e95a33efe9f7f6f6d49401553648e2 Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Thu, 11 Jun 2026 23:15:06 -0700 Subject: [PATCH 1/2] Input: keyboard: fix GPIO matrix same-row chord detection via tri-state + pull-ups Symptom: Shift/Symbol chords with same-row keys (N/M/-) unreliable or dropped. Root cause: Matrix scan drove non-active columns to LOW (push-pull) instead of hi-Z. Two keys on the same input row but different columns created a short: one column driven HIGH (scanning), other column driven LOW (idle), both connected via their pressed keys to the shared row wire. The row voltage became indeterminate, masking one or both keys. Fix in two coordinated parts: 1. Device tree (imx28-brain.dtsi): Enable pull-up resistors on the 8 sense/row lines (GPIO4_0..7 / ENET0 pins). Pulled-up rows idle at HIGH. 2. Driver (brain-kbd-gpio.c): - Scan: Drive only the active column LOW; set all other columns to input mode (hi-Z). This eliminates the short. - Decode: Remove the leading bitwise NOT (~) to match the new active-low polarity (pressed key pulls row LOW, reads as 0). Result: Same-row chords now work. All keys remain fully functional. Tested on: PW-SH3 (pwsh3) GPIO model. --- arch/arm/boot/dts/imx28-brain.dtsi | 28 +++++++++++++++++++++++++ drivers/input/keyboard/brain-kbd-gpio.c | 21 ++++++++++++++++--- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/arch/arm/boot/dts/imx28-brain.dtsi b/arch/arm/boot/dts/imx28-brain.dtsi index e4c0929af8ad3f..ea6e03605d4688 100644 --- a/arch/arm/boot/dts/imx28-brain.dtsi +++ b/arch/arm/boot/dts/imx28-brain.dtsi @@ -153,6 +153,32 @@ fsl,pull-up = ; }; + kbd_in_pins: keyboard@0 { + reg = <0>; + /* + * Keyboard matrix sense (row) lines. + * They are scanned by driving one column + * low at a time while the others are kept + * hi-Z, so the rows need a pull-up to + * define the idle ("not pressed") level + * and to avoid floating reads. A pressed + * key pulls its row low. + */ + fsl,pinmux-ids = < + MX28_PAD_ENET0_MDC__GPIO_4_0 + MX28_PAD_ENET0_MDIO__GPIO_4_1 + MX28_PAD_ENET0_RX_EN__GPIO_4_2 + MX28_PAD_ENET0_RXD0__GPIO_4_3 + MX28_PAD_ENET0_RXD1__GPIO_4_4 + MX28_PAD_ENET0_TX_CLK__GPIO_4_5 + MX28_PAD_ENET0_TX_EN__GPIO_4_6 + MX28_PAD_ENET0_TXD0__GPIO_4_7 + >; + fsl,drive-strength = ; + fsl,voltage = ; + fsl,pull-up = ; + }; + lcd_backlight_pins: pwm@0 { reg = <0>; fsl,pinmux-ids = < @@ -392,6 +418,8 @@ keyboard_gpio: keyboard_gpio { status = "disabled"; compatible = "sharp,brain-kbd-gpio"; + pinctrl-names = "default"; + pinctrl-0 = <&kbd_in_pins>; }; buzzer_cold: buzzer_cold { diff --git a/drivers/input/keyboard/brain-kbd-gpio.c b/drivers/input/keyboard/brain-kbd-gpio.c index 4b050a9966bd2b..f67ed94eb05809 100644 --- a/drivers/input/keyboard/brain-kbd-gpio.c +++ b/drivers/input/keyboard/brain-kbd-gpio.c @@ -48,15 +48,30 @@ static void bk_gpio_read_keys(struct input_dev *inputdev, ulong* result) for (try = 0; try < ARRAY_SIZE(in); try++) { for (i = 0; i < ARRAY_SIZE(in[0]); i++) { - gpiod_set_value(kbd->out[i], 1); + /* + * Drive only the scanned column low and leave every + * other column in high-impedance (input) mode. The + * sense (row) lines are pulled up in the device tree, + * so a pressed key pulls its row low. Keeping the other + * columns hi-Z (instead of driving them low) prevents a + * key held on another column from shorting a shared row + * line, which is what previously masked same-row chords + * such as Shift/Symbol + N/M/-. + */ + gpiod_direction_output(kbd->out[i], 0); udelay(100); in[try][i] = 0; err = gpiod_get_array_value(8, kbd->in, NULL, &in[try][i]); if (err) { dev_err(dev, "failed to get array value: %d\n", err); } - in[try][i] = ~(((in[try][i] ^ (in[try][i] >> 1)) & 0x1f) ^ (in[try][i] >> 1)) & 0x7f; - gpiod_set_value(kbd->out[i], 0); + /* + * Decode the raw read into a 7-bit per-column row mask + * where a pressed key is 0 (active low). Input bit 5 is + * unused; raw bits 6 and 7 map to rows 5 and 6. + */ + in[try][i] = (((in[try][i] ^ (in[try][i] >> 1)) & 0x1f) ^ (in[try][i] >> 1)) & 0x7f; + gpiod_direction_input(kbd->out[i]); } if (try < 3) { From 1c928c3cf1eb9179e2b69e9b49cd33d8cd3b8f4f Mon Sep 17 00:00:00 2001 From: Mingyang Li Date: Thu, 11 Jun 2026 23:50:47 -0700 Subject: [PATCH 2/2] Report a key release even if its bank is still active. Required for chords to work on the row where "Symbol" key sits. If we hold a chord like "Sym + N", where both keys are in the same bank, and let's say we release N but keep holding Sym. We should report N immediately, not waiting for the whole bank, because Sym is still making the bank active. --- drivers/input/keyboard/brain-kbd-gpio.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/drivers/input/keyboard/brain-kbd-gpio.c b/drivers/input/keyboard/brain-kbd-gpio.c index f67ed94eb05809..2b51574c504e58 100644 --- a/drivers/input/keyboard/brain-kbd-gpio.c +++ b/drivers/input/keyboard/brain-kbd-gpio.c @@ -149,6 +149,24 @@ static void bk_gpio_poll(struct input_dev *inputdev) } kbd->pressed[i][j] = true; } else { + /* + * Key released while its bank is still + * active (e.g. another key in the same + * bank is held, as in a modifier chord). + * Emit the release here; the bank-flush + * branch below only runs once the WHOLE + * bank goes idle, so without this a key + * lifted mid-chord would stick. + */ + if (kbd->pressed[i][j]) { + if (i == kbd->sym_key_bank && j == kbd->sym_key_num) { + kbd->symbol = false; + } else { + dev_dbg(dev, "R: %04x\n", kbd->km[i][j]); + input_report_key(inputdev, kbd->km[i][j], 0); + input_report_key(inputdev, kbd->km_symbol[i][j], 0); + } + } kbd->pressed[i][j] = false; } }