[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250926141913.25919-6-jefflessard3@gmail.com>
Date: Fri, 26 Sep 2025 10:19:06 -0400
From: Jean-François Lessard <jefflessard3@...il.com>
To: Andy Shevchenko <andy@...nel.org>,
Geert Uytterhoeven <geert@...ux-m68k.org>,
Rob Herring <robh@...nel.org>,
Krzysztof Kozlowski <krzk+dt@...nel.org>,
Conor Dooley <conor+dt@...nel.org>
Cc: linux-kernel@...r.kernel.org,
linux-leds@...r.kernel.org,
devicetree@...r.kernel.org
Subject: [PATCH v5 5/7] auxdisplay: TM16xx: Add keypad support for scanning matrix keys
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@...il.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 | 9 ++
drivers/auxdisplay/Makefile | 1 +
drivers/auxdisplay/tm16xx.h | 25 ++++
drivers/auxdisplay/tm16xx_core.c | 4 +
drivers/auxdisplay/tm16xx_keypad.c | 196 +++++++++++++++++++++++++++++
6 files changed, 236 insertions(+)
create mode 100644 drivers/auxdisplay/tm16xx_keypad.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 7d5912f2d954..84f2135903cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25448,6 +25448,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@...g-engineering.com>
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index 7bacf11112b5..f9a2c0641c3c 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -528,13 +528,22 @@ config SEG_LED_GPIO
config TM16XX
tristate
+ depends on INPUT
+ select INPUT_MATRIXKMAP
select LEDS_CLASS
select LEDS_TRIGGERS
select LINEDISP
select NEW_LEDS
+ select TM16XX_KEYPAD if (INPUT)
help
Core TM16XX-compatible 7-segment LED controllers module
+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 7ecf3cd4a0d3..a9b9c8ff05e8 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 973b6ac19515..c503c6136807 100644
--- a/drivers/auxdisplay/tm16xx.h
+++ b/drivers/auxdisplay/tm16xx.h
@@ -103,6 +103,7 @@
struct tm16xx_display;
struct tm16xx_digit;
struct tm16xx_led;
+struct tm16xx_keypad;
/**
* DOC: struct tm16xx_controller - Controller-specific operations and limits
@@ -133,6 +134,7 @@ struct tm16xx_controller {
* @dev: Pointer to device struct.
* @controller: Controller-specific function table and limits.
* @linedisp: character line display structure
+ * @keypad: Opaque pointer to tm16xx_keypad struct.
* @spi_buffer: DMA-safe buffer for SPI transactions, or NULL for I2C.
* @num_hwgrid: Number of controller grids in use.
* @num_hwseg: Number of controller segments in use.
@@ -150,6 +152,7 @@ struct tm16xx_controller {
struct tm16xx_display {
struct device *dev;
const struct tm16xx_controller *controller;
+ struct tm16xx_keypad *keypad;
struct linedisp linedisp;
u8 *spi_buffer;
u8 num_hwgrid;
@@ -169,4 +172,26 @@ 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 int row,
+ const int 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 int row, const int col,
+ const bool pressed)
+{
+}
+#endif
+
+#define tm16xx_for_each_key(display, _r, _c) \
+ for (int _r = 0; _r < (display)->controller->max_key_rows; _r++) \
+ for (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 e090c578f8a0..1d474d980254 100644
--- a/drivers/auxdisplay/tm16xx_core.c
+++ b/drivers/auxdisplay/tm16xx_core.c
@@ -408,6 +408,10 @@ int tm16xx_probe(struct tm16xx_display *display)
goto unregister_leds;
}
+ ret = tm16xx_keypad_probe(display);
+ if (ret)
+ 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 000000000000..daa6afaf749a
--- /dev/null
+++ b/drivers/auxdisplay/tm16xx_keypad.c
@@ -0,0 +1,196 @@
+// 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) 2025 Jean-François Lessard
+ */
+
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#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;
+ int 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 int 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 int 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 int row,
+ const int 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;
+ int row, col, scancode;
+ 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) {
+ 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);
+
+ 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 unsigned int rows = display->controller->max_key_rows;
+ const unsigned int 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)
+ return 0; /* keypad not supported */
+
+ if (!device_property_present(display->dev, "poll-interval") ||
+ !device_property_present(display->dev, "linux,keymap"))
+ return 0; /* keypad disabled */
+
+ ret = device_property_read_u32(display->dev, "poll-interval", &poll_interval);
+ if (ret)
+ 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); /* !cols already checked */
+ ret = matrix_keypad_build_keymap(NULL, "linux,keymap", rows, cols, NULL, input);
+ if (ret)
+ 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)
+ return dev_err_probe(display->dev, ret,
+ "Failed to register input device\n");
+
+ return 0;
+}
--
2.43.0
Powered by blists - more mailing lists