From: "Jean-François Lessard" <jefflessard3@gmail.com>
To: Andy Shevchenko <andy@kernel.org>,
Geert Uytterhoeven <geert@linux-m68k.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org,
devicetree@vger.kernel.org
Subject: [PATCH v4 4/6] auxdisplay: TM16xx: Add keypad support for scanning matrix keys
Date: Sun, 24 Aug 2025 23:32:30 -0400 [thread overview]
Message-ID: <20250825033237.60143-5-jefflessard3@gmail.com> (raw)
In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com>
Add support for keypad scanning on TM16xx-compatible auxiliary display
controllers. It handles keypad initialization, scanning, and input
reporting for matrix keys managed by the TM16xx devices.
Key features include:
- Input device registration configured by device properties (poll-interval,
linux,keymap, autorepeat)
- Key state tracking using managed bitmaps
- Matrix scanning and event reporting integrated with Linux input subsystem
This code is separated from main core driver to improve maintainability and
reviewability.
Signed-off-by: Jean-François Lessard <jefflessard3@gmail.com>
---
Notes:
checkpatch reports false positives that are intentionally ignored:
COMPLEX_MACRO/MACRO_ARG_REUSE for tm16xx_for_each_key(): This is a
standard iterator pattern following kernel conventions (similar to
for_each_* macros throughout the kernel). The nested for loops are
the correct implementation for matrix iteration.
MAINTAINERS | 1 +
drivers/auxdisplay/Kconfig | 11 +-
drivers/auxdisplay/Makefile | 1 +
drivers/auxdisplay/tm16xx.h | 27 ++++
drivers/auxdisplay/tm16xx_core.c | 4 +
drivers/auxdisplay/tm16xx_keypad.c | 208 +++++++++++++++++++++++++++++
6 files changed, 251 insertions(+), 1 deletion(-)
create mode 100644 drivers/auxdisplay/tm16xx_keypad.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 0ed971881..8edcdd52c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25409,6 +25409,7 @@ F: Documentation/ABI/testing/sysfs-class-leds-tm16xx
F: Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml
F: drivers/auxdisplay/tm16xx.h
F: drivers/auxdisplay/tm16xx_core.c
+F: drivers/auxdisplay/tm16xx_keypad.c
TMIO/SDHI MMC DRIVER
M: Wolfram Sang <wsa+renesas@sang-engineering.com>
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index 7b58c6cc8..b5dcd024d 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -527,11 +527,14 @@ config SEG_LED_GPIO
will be called seg-led-gpio.
config TM16XX
- tristate "TM16XX-compatible 7-segment LED controllers"
+ tristate "TM16XX-compatible 7-segment LED controllers with keyscan"
depends on SPI || I2C
+ depends on INPUT
select NEW_LEDS
select LEDS_CLASS
select LEDS_TRIGGERS
+ select INPUT_MATRIXKMAP
+ select TM16XX_KEYPAD if (INPUT)
help
This driver supports the following TM16XX compatible
I2C and SPI 7-segment led display chips:
@@ -544,6 +547,12 @@ config TM16XX
This driver can also be built as a module. If so, the module
will be called tm16xx.
+config TM16XX_KEYPAD
+ bool
+ depends on TM16XX
+ help
+ Enable keyscan support for TM16XX driver.
+
#
# Character LCD with non-conforming interface section
#
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index 7ecf3cd4a..a9b9c8ff0 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_PARPORT_PANEL) += panel.o
obj-$(CONFIG_SEG_LED_GPIO) += seg-led-gpio.o
obj-$(CONFIG_TM16XX) += tm16xx.o
tm16xx-y += tm16xx_core.o
+tm16xx-$(CONFIG_TM16XX_KEYPAD) += tm16xx_keypad.o
diff --git a/drivers/auxdisplay/tm16xx.h b/drivers/auxdisplay/tm16xx.h
index a7ce483d3..65e2511cd 100644
--- a/drivers/auxdisplay/tm16xx.h
+++ b/drivers/auxdisplay/tm16xx.h
@@ -104,6 +104,7 @@
struct tm16xx_display;
struct tm16xx_digit;
struct tm16xx_led;
+struct tm16xx_keypad;
/**
* DOC: struct tm16xx_controller - Controller-specific operations and limits
@@ -136,6 +137,7 @@ struct tm16xx_controller {
* @controller: Controller-specific function table and limits.
* @client: Union of I2C and SPI client pointers.
* @spi_buffer: DMA-safe buffer for SPI transactions, or NULL for I2C.
+ * @keypad: Opaque pointer to tm16xx_keypad struct.
* @num_grids: Number of controller grids in use.
* @num_segments: Number of controller segments in use.
* @main_led: LED class device for the entire display.
@@ -157,6 +159,7 @@ struct tm16xx_display {
struct spi_device *spi;
} client;
u8 *spi_buffer;
+ struct tm16xx_keypad *keypad;
u8 num_grids;
u8 num_segments;
struct led_classdev main_led;
@@ -174,4 +177,28 @@ struct tm16xx_display {
int tm16xx_probe(struct tm16xx_display *display);
void tm16xx_remove(struct tm16xx_display *display);
+/* keypad support */
+#if IS_ENABLED(CONFIG_TM16XX_KEYPAD)
+int tm16xx_keypad_probe(struct tm16xx_display *display);
+void tm16xx_set_key(const struct tm16xx_display *display, const u8 row,
+ const u8 col, const bool pressed);
+#else
+static inline int tm16xx_keypad_probe(struct tm16xx_display *display)
+{
+ return 0;
+}
+
+static inline void tm16xx_set_key(const struct tm16xx_display *display,
+ const u8 row, const u8 col,
+ const bool pressed)
+{
+}
+#endif
+
+#define tm16xx_for_each_key(display, _r, _c) \
+ for (unsigned int _r = 0; \
+ _r < (display)->controller->max_key_rows; _r++) \
+ for (unsigned int _c = 0; \
+ _c < (display)->controller->max_key_cols; _c++)
+
#endif /* _TM16XX_H */
diff --git a/drivers/auxdisplay/tm16xx_core.c b/drivers/auxdisplay/tm16xx_core.c
index 415be7747..e21c41a09 100644
--- a/drivers/auxdisplay/tm16xx_core.c
+++ b/drivers/auxdisplay/tm16xx_core.c
@@ -566,6 +566,10 @@ int tm16xx_probe(struct tm16xx_display *display)
goto unregister_leds;
}
+ ret = tm16xx_keypad_probe(display);
+ if (ret < 0)
+ dev_warn(dev, "Failed to initialize keypad: %d\n", ret);
+
return 0;
unregister_leds:
diff --git a/drivers/auxdisplay/tm16xx_keypad.c b/drivers/auxdisplay/tm16xx_keypad.c
new file mode 100644
index 000000000..391ae737e
--- /dev/null
+++ b/drivers/auxdisplay/tm16xx_keypad.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TM16xx and compatible LED display/keypad controller driver
+ * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips.
+ *
+ * Copyright (C) 2024 Jean-François Lessard
+ */
+
+#include <linux/input.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/property.h>
+
+#include "tm16xx.h"
+
+/**
+ * struct tm16xx_keypad - Keypad matrix state and input device
+ * @input: Input device for reporting key events.
+ * @state: Current bitmap of key states.
+ * @last_state: Previous bitmap of key states for change detection.
+ * @changes: Bitmap of key state changes since last poll.
+ * @row_shift: Row shift for keymap encoding.
+ */
+struct tm16xx_keypad {
+ struct input_dev *input;
+ unsigned long *state;
+ unsigned long *last_state;
+ unsigned long *changes;
+ u8 row_shift;
+};
+
+/**
+ * tm16xx_key_nbits() - Number of bits for the keypad state bitmap
+ * @display: pointer to tm16xx_display
+ *
+ * Return: total bits in keypad state bitmap (max_key_rows * max_key_cols)
+ */
+static inline unsigned int tm16xx_key_nbits(const struct tm16xx_display *display)
+{
+ return display->controller->max_key_rows *
+ display->controller->max_key_cols;
+}
+
+/**
+ * tm16xx_get_key_row() - Get row index from keypad bit index
+ * @display: pointer to tm16xx_display
+ * @bit: bit index in state bitmap
+ *
+ * Return: row index
+ */
+static inline u8 tm16xx_get_key_row(const struct tm16xx_display *display,
+ const unsigned int bit)
+{
+ return bit / display->controller->max_key_cols;
+}
+
+/**
+ * tm16xx_get_key_col() - Get column index from keypad bit index
+ * @display: pointer to tm16xx_display
+ * @bit: bit index in state bitmap
+ *
+ * Return: column index
+ */
+static inline u8 tm16xx_get_key_col(const struct tm16xx_display *display,
+ const unsigned int bit)
+{
+ return bit % display->controller->max_key_cols;
+}
+
+/**
+ * tm16xx_set_key() - Set the keypad state for a key
+ * @display: pointer to tm16xx_display
+ * @row: row index
+ * @col: column index
+ * @pressed: true if pressed, false otherwise
+ */
+void tm16xx_set_key(const struct tm16xx_display *display, const u8 row,
+ const u8 col, const bool pressed)
+{
+ __assign_bit(row * display->controller->max_key_cols + col,
+ display->keypad->state, pressed);
+}
+EXPORT_SYMBOL_NS(tm16xx_set_key, "TM16XX");
+
+/**
+ * tm16xx_keypad_poll() - Polls the keypad, reports events
+ * @input: pointer to input_dev
+ *
+ * Reads the matrix keypad state, compares with previous state, and
+ * reports key events to the input subsystem.
+ */
+static void tm16xx_keypad_poll(struct input_dev *input)
+{
+ struct tm16xx_display *display = input_get_drvdata(input);
+ struct tm16xx_keypad *keypad = display->keypad;
+ const unsigned short *keycodes = keypad->input->keycode;
+ unsigned int nbits = tm16xx_key_nbits(display);
+ unsigned int bit, scancode;
+ u8 row, col;
+ bool pressed;
+ int ret;
+
+ bitmap_zero(keypad->state, nbits);
+ bitmap_zero(keypad->changes, nbits);
+
+ scoped_guard(mutex, &display->lock) {
+ ret = display->controller->keys(display);
+ }
+
+ if (ret < 0) {
+ dev_err(display->dev, "Reading failed: %d\n", ret);
+ return;
+ }
+
+ bitmap_xor(keypad->changes, keypad->state, keypad->last_state, nbits);
+
+ for_each_set_bit(bit, keypad->changes, nbits) {
+ row = tm16xx_get_key_row(display, bit);
+ col = tm16xx_get_key_col(display, bit);
+ pressed = _test_bit(bit, keypad->state);
+ scancode = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
+
+ dev_dbg(display->dev,
+ "key changed: %u, row=%u col=%u down=%d\n", bit, row,
+ col, pressed);
+
+ input_event(keypad->input, EV_MSC, MSC_SCAN, scancode);
+ input_report_key(keypad->input, keycodes[scancode], pressed);
+ }
+ input_sync(keypad->input);
+
+ bitmap_copy(keypad->last_state, keypad->state, nbits);
+}
+
+/**
+ * tm16xx_keypad_probe() - Initialize keypad/input device
+ * @display: pointer to tm16xx_display
+ *
+ * Return: 0 on success, negative error code on failure
+ */
+int tm16xx_keypad_probe(struct tm16xx_display *display)
+{
+ const u8 rows = display->controller->max_key_rows;
+ const u8 cols = display->controller->max_key_cols;
+ struct tm16xx_keypad *keypad;
+ struct input_dev *input;
+ unsigned int poll_interval, nbits;
+ int ret;
+
+ if (!display->controller->keys || !rows || !cols) {
+ dev_dbg(display->dev, "keypad not supported\n");
+ return 0;
+ }
+
+ if (!device_property_present(display->dev, "poll-interval") ||
+ !device_property_present(display->dev, "linux,keymap")) {
+ dev_dbg(display->dev, "keypad disabled\n");
+ return 0;
+ }
+
+ dev_dbg(display->dev, "Configuring keypad\n");
+
+ ret = device_property_read_u32(display->dev, "poll-interval",
+ &poll_interval);
+ if (ret < 0)
+ return dev_err_probe(display->dev, ret,
+ "Failed to read poll-interval\n");
+
+ keypad = devm_kzalloc(display->dev, sizeof(*keypad), GFP_KERNEL);
+ if (!keypad)
+ return -ENOMEM;
+ display->keypad = keypad;
+
+ nbits = tm16xx_key_nbits(display);
+ keypad->state = devm_bitmap_zalloc(display->dev, nbits, GFP_KERNEL);
+ keypad->last_state = devm_bitmap_zalloc(display->dev, nbits, GFP_KERNEL);
+ keypad->changes = devm_bitmap_zalloc(display->dev, nbits, GFP_KERNEL);
+ if (!keypad->state || !keypad->last_state || !keypad->changes)
+ return -ENOMEM;
+
+ input = devm_input_allocate_device(display->dev);
+ if (!input)
+ return -ENOMEM;
+ input->name = "tm16xx-keypad";
+ keypad->input = input;
+ input_set_drvdata(input, display);
+
+ keypad->row_shift = get_count_order(cols);
+ ret = matrix_keypad_build_keymap(NULL, "linux,keymap", rows, cols, NULL,
+ input);
+ if (ret < 0)
+ return dev_err_probe(display->dev, ret,
+ "Failed to build keymap\n");
+
+ if (device_property_read_bool(display->dev, "autorepeat"))
+ __set_bit(EV_REP, input->evbit);
+
+ input_setup_polling(input, tm16xx_keypad_poll);
+ input_set_poll_interval(input, poll_interval);
+ ret = input_register_device(input);
+ if (ret < 0)
+ return dev_err_probe(display->dev, ret,
+ "Failed to register input device\n");
+
+ dev_dbg(display->dev, "keypad rows=%u, cols=%u, poll=%u\n", rows, cols,
+ poll_interval);
+
+ return 0;
+}
--
2.43.0
next prev parent reply other threads:[~2025-08-25 3:32 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-25 3:32 [PATCH v4 0/6] auxdisplay: Add TM16xx 7-segment LED matrix display controllers driver Jean-François Lessard
2025-08-25 3:32 ` [PATCH v4 1/6] dt-bindings: vendor-prefixes: Add fdhisi, titanmec, princeton, winrise, wxicore Jean-François Lessard
2025-08-25 13:53 ` Andy Shevchenko
2025-08-26 2:57 ` Jean-François Lessard
2025-08-25 3:32 ` [PATCH v4 2/6] dt-bindings: auxdisplay: add Titan Micro Electronics TM16xx Jean-François Lessard
2025-08-25 18:26 ` Rob Herring
2025-08-26 1:33 ` Jean-François Lessard
2025-08-26 14:37 ` Jean-François Lessard
2025-08-29 15:26 ` Rob Herring
2025-08-29 16:26 ` Jean-François Lessard
2025-08-25 19:08 ` Per Larsson
2025-08-26 1:53 ` Jean-François Lessard
2025-08-25 3:32 ` [PATCH v4 3/6] auxdisplay: Add TM16xx 7-segment LED matrix display controllers driver Jean-François Lessard
2025-08-25 15:14 ` Andy Shevchenko
2025-08-25 17:48 ` Jean-François Lessard
2025-08-26 15:22 ` Andy Shevchenko
2025-08-26 20:44 ` Jean-François Lessard
2025-08-27 18:37 ` Jean-François Lessard
2025-09-01 6:04 ` Andy Shevchenko
2025-08-25 3:32 ` Jean-François Lessard [this message]
2025-08-25 3:32 ` [PATCH v4 5/6] auxdisplay: TM16xx: Add support for I2C-based controllers Jean-François Lessard
2025-08-25 15:18 ` Andy Shevchenko
2025-08-26 4:01 ` Jean-François Lessard
2025-08-26 15:30 ` Andy Shevchenko
2025-08-26 17:38 ` Jean-François Lessard
2025-08-26 18:26 ` Andy Shevchenko
2025-08-26 20:21 ` Jean-François Lessard
2025-08-25 3:32 ` [PATCH v4 6/6] auxdisplay: TM16xx: Add support for SPI-based controllers Jean-François Lessard
2025-08-25 15:19 ` Andy Shevchenko
2025-08-26 4:04 ` Jean-François Lessard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250825033237.60143-5-jefflessard3@gmail.com \
--to=jefflessard3@gmail.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=geert@linux-m68k.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=robh@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.