lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250825033237.60143-5-jefflessard3@gmail.com>
Date: Sun, 24 Aug 2025 23:32:30 -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 v4 4/6] 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         |  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@...g-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


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ