From 210f77b77004d4e6ef6834cca00957d4bee3f24e Mon Sep 17 00:00:00 2001 From: EvilOlaf Date: Sun, 14 Dec 2025 12:34:55 +0000 Subject: [PATCH] rockchip64-6.19: drop partially upstreamed `general-driver-tm16xx-led-driver` --- .../general-driver-tm16xx-led-driver.patch | 3201 ----------------- 1 file changed, 3201 deletions(-) delete mode 100644 patch/kernel/archive/rockchip64-6.19/general-driver-tm16xx-led-driver.patch diff --git a/patch/kernel/archive/rockchip64-6.19/general-driver-tm16xx-led-driver.patch b/patch/kernel/archive/rockchip64-6.19/general-driver-tm16xx-led-driver.patch deleted file mode 100644 index c793185941..0000000000 --- a/patch/kernel/archive/rockchip64-6.19/general-driver-tm16xx-led-driver.patch +++ /dev/null @@ -1,3201 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sun, 31 Aug 2025 00:23:51 -0400 -Subject: docs: ABI: auxdisplay: document linedisp library sysfs attributes - -Add ABI documentation for sysfs attributes provided by the line-display -auxdisplay library module. These attributes enable text message display and -configuration on character-based auxdisplay devices. - -Documents previously undocumented attributes: -- message, scroll_step_ms (introduced in v5.16) -- map_seg7, map_seg14 (introduced in v6.9) - -The line-display library is used by multiple auxdisplay drivers and -can expose these attributes either on linedisp.N child devices or -directly on parent auxdisplay devices. - -Signed-off-by: Jean-Francois Lessard ---- - Documentation/ABI/testing/sysfs-auxdisplay-linedisp | 79 ++++++++++ - 1 file changed, 79 insertions(+) - -diff --git a/Documentation/ABI/testing/sysfs-auxdisplay-linedisp b/Documentation/ABI/testing/sysfs-auxdisplay-linedisp -new file mode 100644 -index 000000000000..111111111111 ---- /dev/null -+++ b/Documentation/ABI/testing/sysfs-auxdisplay-linedisp -@@ -0,0 +1,79 @@ -+What: /sys/.../message -+Date: October 2021 -+KernelVersion: 5.16 -+Description: -+ Controls the text message displayed on character line displays. -+ -+ Reading returns the current message with a trailing newline. -+ Writing updates the displayed message. Messages longer than the -+ display width will automatically scroll. Trailing newlines in -+ input are automatically trimmed. -+ -+ Writing an empty string clears the display. -+ -+ Example: -+ echo "Hello World" > message -+ cat message # Returns "Hello World\n" -+ -+What: /sys/.../scroll_step_ms -+Date: October 2021 -+KernelVersion: 5.16 -+Description: -+ Controls the scrolling speed for messages longer than the display -+ width, specified in milliseconds per scroll step. -+ -+ Setting to 0 disables scrolling. Default is 500ms. -+ -+ Example: -+ echo "250" > scroll_step_ms # 4Hz scrolling -+ cat scroll_step_ms # Returns "250\n" -+ -+What: /sys/.../map_seg7 -+Date: January 2024 -+KernelVersion: 6.9 -+Description: -+ Read/write binary blob representing the ASCII-to-7-segment -+ display conversion table used by the linedisp driver, as defined -+ by struct seg7_conversion_map in . -+ -+ Only visible on displays with 7-segment capability. -+ -+ This attribute is not human-readable. Writes must match the -+ struct size exactly, else -EINVAL is returned; reads return the -+ entire mapping as a binary blob. -+ -+ This interface and its implementation match existing conventions -+ used in segment-mapped display drivers since 2005. -+ -+ ABI note: This style of binary sysfs attribute *is an exception* -+ to current "one value per file, text only" sysfs rules, for -+ historical compatibility and driver uniformity. New drivers are -+ discouraged from introducing additional binary sysfs ABIs. -+ -+ Reference interface guidance: -+ - include/uapi/linux/map_to_7segment.h -+ -+What: /sys/.../map_seg14 -+Date: January 2024 -+KernelVersion: 6.9 -+Description: -+ Read/write binary blob representing the ASCII-to-14-segment -+ display conversion table used by the linedisp driver, as defined -+ by struct seg14_conversion_map in . -+ -+ Only visible on displays with 14-segment capability. -+ -+ This attribute is not human-readable. Writes must match the -+ struct size exactly, else -EINVAL is returned; reads return the -+ entire mapping as a binary blob. -+ -+ This interface and its implementation match existing conventions -+ used by segment-mapped display drivers since 2005. -+ -+ ABI note: This style of binary sysfs attribute *is an exception* -+ to current "one value per file, text only" sysfs rules, for -+ historical compatibility and driver uniformity. New drivers are -+ discouraged from introducing additional binary sysfs ABIs. -+ -+ Reference interface guidance: -+ - include/uapi/linux/map_to_14segment.h --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 30 Aug 2025 20:31:38 -0400 -Subject: auxdisplay: linedisp: encapsulate container_of usage within - to_linedisp - -Replace direct container_of() calls with a to_linedisp() helper function -throughout the line-display auxdisplay library module. This abstraction -prepares for upcoming dual-mode support where linedisp context retrieval -will need to handle both dedicated child devices and attached parent -auxdisplay devices. - -No functional changes in this patch. - -Signed-off-by: Jean-Francois Lessard ---- - drivers/auxdisplay/line-display.c | 21 ++++++---- - 1 file changed, 13 insertions(+), 8 deletions(-) - -diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/line-display.c -+++ b/drivers/auxdisplay/line-display.c -@@ -31,6 +31,11 @@ - - #define DEFAULT_SCROLL_RATE (HZ / 2) - -+static struct linedisp *to_linedisp(struct device *dev) -+{ -+ return container_of(dev, struct linedisp, dev); -+} -+ - /** - * linedisp_scroll() - scroll the display by a character - * @t: really a pointer to the private data structure -@@ -133,7 +138,7 @@ static int linedisp_display(struct linedisp *linedisp, const char *msg, - static ssize_t message_show(struct device *dev, struct device_attribute *attr, - char *buf) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - - return sysfs_emit(buf, "%s\n", linedisp->message); - } -@@ -152,7 +157,7 @@ static ssize_t message_show(struct device *dev, struct device_attribute *attr, - static ssize_t message_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - int err; - - err = linedisp_display(linedisp, buf, count); -@@ -164,7 +169,7 @@ static DEVICE_ATTR_RW(message); - static ssize_t scroll_step_ms_show(struct device *dev, - struct device_attribute *attr, char *buf) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - - return sysfs_emit(buf, "%u\n", jiffies_to_msecs(linedisp->scroll_rate)); - } -@@ -173,7 +178,7 @@ static ssize_t scroll_step_ms_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - unsigned int ms; - int err; - -@@ -195,7 +200,7 @@ static DEVICE_ATTR_RW(scroll_step_ms); - - static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, char *buf) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - struct linedisp_map *map = linedisp->map; - - memcpy(buf, &map->map, map->size); -@@ -205,7 +210,7 @@ static ssize_t map_seg_show(struct device *dev, struct device_attribute *attr, c - static ssize_t map_seg_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - struct linedisp_map *map = linedisp->map; - - if (count != map->size) -@@ -232,7 +237,7 @@ static struct attribute *linedisp_attrs[] = { - static umode_t linedisp_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) - { - struct device *dev = kobj_to_dev(kobj); -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - struct linedisp_map *map = linedisp->map; - umode_t mode = attr->mode; - -@@ -263,7 +268,7 @@ static DEFINE_IDA(linedisp_id); - - static void linedisp_release(struct device *dev) - { -- struct linedisp *linedisp = container_of(dev, struct linedisp, dev); -+ struct linedisp *linedisp = to_linedisp(dev); - - kfree(linedisp->map); - kfree(linedisp->message); --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sun, 31 Aug 2025 13:53:01 -0400 -Subject: auxdisplay: linedisp: display static message when length <= display - size - -Currently, when a message shorter than the display size is written, the -content wraps around (e.g., "123" on a 4-digit display shows "1231") -without scrolling, which is confusing and unintuitive. - -Change behavior to display short messages statically with space padding -(e.g. "123 ") while only scrolling messages longer than the display width. -This provides more natural behavior that aligns with user expectations -and current linedisp_display() kernel-doc. - -The scroll logic is also consolidated into a helper function for clarity. - -No API changes are introduced. - -Signed-off-by: Jean-Francois Lessard ---- - drivers/auxdisplay/line-display.c | 30 +++++++--- - 1 file changed, 21 insertions(+), 9 deletions(-) - -diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/line-display.c -+++ b/drivers/auxdisplay/line-display.c -@@ -36,6 +36,11 @@ static struct linedisp *to_linedisp(struct device *dev) - return container_of(dev, struct linedisp, dev); - } - -+static inline bool should_scroll(struct linedisp *linedisp) -+{ -+ return linedisp->message_len > linedisp->num_chars && linedisp->scroll_rate; -+} -+ - /** - * linedisp_scroll() - scroll the display by a character - * @t: really a pointer to the private data structure -@@ -67,8 +72,7 @@ static void linedisp_scroll(struct timer_list *t) - linedisp->scroll_pos %= linedisp->message_len; - - /* rearm the timer */ -- if (linedisp->message_len > num_chars && linedisp->scroll_rate) -- mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate); -+ mod_timer(&linedisp->timer, jiffies + linedisp->scroll_rate); - } - - /** -@@ -118,8 +122,16 @@ static int linedisp_display(struct linedisp *linedisp, const char *msg, - linedisp->message_len = count; - linedisp->scroll_pos = 0; - -- /* update the display */ -- linedisp_scroll(&linedisp->timer); -+ if (should_scroll(linedisp)) { -+ /* display scrolling message */ -+ linedisp_scroll(&linedisp->timer); -+ } else { -+ /* display static message */ -+ memset(linedisp->buf, ' ', linedisp->num_chars); -+ memcpy(linedisp->buf, linedisp->message, -+ umin(linedisp->num_chars, linedisp->message_len)); -+ linedisp->ops->update(linedisp); -+ } - - return 0; - } -@@ -186,12 +198,12 @@ static ssize_t scroll_step_ms_store(struct device *dev, - if (err) - return err; - -+ timer_delete_sync(&linedisp->timer); -+ - linedisp->scroll_rate = msecs_to_jiffies(ms); -- if (linedisp->message && linedisp->message_len > linedisp->num_chars) { -- timer_delete_sync(&linedisp->timer); -- if (linedisp->scroll_rate) -- linedisp_scroll(&linedisp->timer); -- } -+ -+ if (should_scroll(linedisp)) -+ linedisp_scroll(&linedisp->timer); - - return count; - } --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 30 Aug 2025 20:35:34 -0400 -Subject: auxdisplay: linedisp: add num_chars sysfs attribute - -Add a read-only 'num_chars' sysfs attribute to report display digit count. - -The num_chars attribute provides essential capability information to -userspace applications that need to know display dimensions before writing -messages, complementing the existing message and scroll controls. - -No functional changes to existing behavior. - -Signed-off-by: Jean-Francois Lessard ---- - Documentation/ABI/testing/sysfs-auxdisplay-linedisp | 11 ++++++++++ - drivers/auxdisplay/line-display.c | 11 ++++++++++ - 2 files changed, 22 insertions(+) - -diff --git a/Documentation/ABI/testing/sysfs-auxdisplay-linedisp b/Documentation/ABI/testing/sysfs-auxdisplay-linedisp -index 111111111111..222222222222 100644 ---- a/Documentation/ABI/testing/sysfs-auxdisplay-linedisp -+++ b/Documentation/ABI/testing/sysfs-auxdisplay-linedisp -@@ -15,6 +15,17 @@ Description: - echo "Hello World" > message - cat message # Returns "Hello World\n" - -+What: /sys/.../num_chars -+Date: November 2025 -+KernelVersion: 6.18 -+Contact: Jean-François Lessard -+Description: -+ Read-only attribute showing the character width capacity of -+ the line display device. Messages longer than this will scroll. -+ -+ Example: -+ cat num_chars # Returns "16\n" for 16-char display -+ - What: /sys/.../scroll_step_ms - Date: October 2021 - KernelVersion: 5.16 -diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/line-display.c -+++ b/drivers/auxdisplay/line-display.c -@@ -178,6 +178,16 @@ static ssize_t message_store(struct device *dev, struct device_attribute *attr, - - static DEVICE_ATTR_RW(message); - -+static ssize_t num_chars_show(struct device *dev, struct device_attribute *attr, -+ char *buf) -+{ -+ struct linedisp *linedisp = to_linedisp(dev); -+ -+ return sysfs_emit(buf, "%u\n", linedisp->num_chars); -+} -+ -+static DEVICE_ATTR_RO(num_chars); -+ - static ssize_t scroll_step_ms_show(struct device *dev, - struct device_attribute *attr, char *buf) - { -@@ -240,6 +250,7 @@ static DEVICE_ATTR(map_seg14, 0644, map_seg_show, map_seg_store); - - static struct attribute *linedisp_attrs[] = { - &dev_attr_message.attr, -+ &dev_attr_num_chars.attr, - &dev_attr_scroll_step_ms.attr, - &dev_attr_map_seg7.attr, - &dev_attr_map_seg14.attr, --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 30 Aug 2025 20:41:39 -0400 -Subject: auxdisplay: linedisp: support attribute attachment to auxdisplay - devices - -Enable linedisp library integration into existing kernel devices (like LED -class) to provide a uniform 7-segment userspace API without creating -separate child devices, meeting the consistent interface while maintaining -coherent device hierarchies. - -This allows uniform 7-segment API across all drivers while solving device -proliferation and fragmented userspace interfaces. - -The sysfs attributes appear in one of the two locations depending on usage: - 1. On linedisp.N child devices (legacy linedisp_register()) - 2. On the parent auxdisplay device (new linedisp_attach()) -Functionality is identical in both modes. - -Existing consumers of linedisp_register() are unaffected. The new API -enables drivers like TM16XX to integrate 7-segment display functionality -seamlessly within their LED class device hierarchy. - -Signed-off-by: Jean-Francois Lessard ---- - drivers/auxdisplay/line-display.c | 180 +++++++++- - drivers/auxdisplay/line-display.h | 4 + - 2 files changed, 178 insertions(+), 6 deletions(-) - -diff --git a/drivers/auxdisplay/line-display.c b/drivers/auxdisplay/line-display.c -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/line-display.c -+++ b/drivers/auxdisplay/line-display.c -@@ -6,20 +6,23 @@ - * Author: Paul Burton - * - * Copyright (C) 2021 Glider bv -+ * Copyright (C) 2025 Jean-François Lessard - */ - - #ifndef CONFIG_PANEL_BOOT_MESSAGE - #include - #endif - --#include -+#include - #include - #include - #include - #include - #include -+#include - #include - #include -+#include - #include - #include - #include -@@ -31,9 +34,80 @@ - - #define DEFAULT_SCROLL_RATE (HZ / 2) - -+/** -+ * struct linedisp_attachment - Holds the device to linedisp mapping -+ * @list: List entry for the linedisp_attachments list -+ * @device: Pointer to the device where linedisp attributes are added -+ * @linedisp: Pointer to the linedisp mapped to the device -+ * @direct: true for directly attached device using linedisp_attach(), -+ * false for child registered device using linedisp_register() -+ */ -+struct linedisp_attachment { -+ struct list_head list; -+ struct device *device; -+ struct linedisp *linedisp; -+ bool direct; -+}; -+ -+static LIST_HEAD(linedisp_attachments); -+static DEFINE_SPINLOCK(linedisp_attachments_lock); -+ -+static int create_attachment(struct device *dev, struct linedisp *linedisp, bool direct) -+{ -+ struct linedisp_attachment *attachment; -+ -+ attachment = kzalloc(sizeof(*attachment), GFP_KERNEL); -+ if (!attachment) -+ return -ENOMEM; -+ -+ attachment->device = dev; -+ attachment->linedisp = linedisp; -+ attachment->direct = direct; -+ -+ guard(spinlock)(&linedisp_attachments_lock); -+ list_add(&attachment->list, &linedisp_attachments); -+ -+ return 0; -+} -+ -+static struct linedisp *delete_attachment(struct device *dev, bool direct) -+{ -+ struct linedisp_attachment *attachment; -+ struct linedisp *linedisp; -+ -+ guard(spinlock)(&linedisp_attachments_lock); -+ -+ list_for_each_entry(attachment, &linedisp_attachments, list) { -+ if (attachment->device == dev && -+ attachment->direct == direct) -+ break; -+ } -+ -+ if (list_entry_is_head(attachment, &linedisp_attachments, list)) -+ return NULL; -+ -+ linedisp = attachment->linedisp; -+ list_del(&attachment->list); -+ kfree(attachment); -+ -+ return linedisp; -+} -+ - static struct linedisp *to_linedisp(struct device *dev) - { -- return container_of(dev, struct linedisp, dev); -+ struct linedisp_attachment *attachment; -+ -+ guard(spinlock)(&linedisp_attachments_lock); -+ -+ list_for_each_entry(attachment, &linedisp_attachments, list) { -+ if (attachment->device == dev) -+ break; -+ } -+ -+ if (list_entry_is_head(attachment, &linedisp_attachments, list)) -+ return NULL; -+ -+ return attachment->linedisp; - } - - static inline bool should_scroll(struct linedisp *linedisp) -@@ -348,6 +422,90 @@ static int linedisp_init_map(struct linedisp *linedisp) - #define LINEDISP_INIT_TEXT "Linux " UTS_RELEASE " " - #endif - -+/** -+ * linedisp_attach - attach a character line display -+ * @linedisp: pointer to character line display structure -+ * @dev: pointer of the device to attach to -+ * @num_chars: the number of characters that can be displayed -+ * @ops: character line display operations -+ * -+ * Directly attach the line-display sysfs attributes to the passed device. -+ * The caller is responsible for calling linedisp_detach() to release resources -+ * after use. -+ * -+ * Return: zero on success, else a negative error code. -+ */ -+int linedisp_attach(struct linedisp *linedisp, struct device *dev, -+ unsigned int num_chars, const struct linedisp_ops *ops) -+{ -+ int err; -+ -+ memset(linedisp, 0, sizeof(*linedisp)); -+ linedisp->ops = ops; -+ linedisp->num_chars = num_chars; -+ linedisp->scroll_rate = DEFAULT_SCROLL_RATE; -+ -+ linedisp->buf = kzalloc(linedisp->num_chars, GFP_KERNEL); -+ if (!linedisp->buf) -+ return -ENOMEM; -+ -+ /* initialise a character mapping, if required */ -+ err = linedisp_init_map(linedisp); -+ if (err) -+ goto out_free_buf; -+ -+ /* initialise a timer for scrolling the message */ -+ timer_setup(&linedisp->timer, linedisp_scroll, 0); -+ -+ err = create_attachment(dev, linedisp, true); -+ if (err) -+ goto out_del_timer; -+ -+ /* display a default message */ -+ err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1); -+ if (err) -+ goto out_del_attach; -+ -+ /* add attribute groups to target device */ -+ err = device_add_groups(dev, linedisp_groups); -+ if (err) -+ goto out_del_attach; -+ -+ return 0; -+ -+out_del_attach: -+ delete_attachment(dev, true); -+out_del_timer: -+ timer_delete_sync(&linedisp->timer); -+out_free_buf: -+ kfree(linedisp->buf); -+ return err; -+} -+EXPORT_SYMBOL_NS_GPL(linedisp_attach, "LINEDISP"); -+ -+/** -+ * linedisp_detach - detach a character line display -+ * @dev: pointer of the device to detach from, that was previously -+ * attached with linedisp_attach() -+ */ -+void linedisp_detach(struct device *dev) -+{ -+ struct linedisp *linedisp; -+ -+ linedisp = delete_attachment(dev, true); -+ if (!linedisp) -+ return; -+ -+ timer_delete_sync(&linedisp->timer); -+ -+ device_remove_groups(dev, linedisp_groups); -+ -+ kfree(linedisp->map); -+ kfree(linedisp->message); -+ kfree(linedisp->buf); -+} -+EXPORT_SYMBOL_NS_GPL(linedisp_detach, "LINEDISP"); -+ - /** - * linedisp_register - register a character line display - * @linedisp: pointer to character line display structure -@@ -355,6 +513,11 @@ static int linedisp_init_map(struct linedisp *linedisp) - * @num_chars: the number of characters that can be displayed - * @ops: character line display operations - * -+ * Register the line-display sysfs attributes to a new device named -+ * "linedisp.N" added to the passed parent device. -+ * The caller is responsible for calling linedisp_unregister() to release -+ * resources after use. -+ * - * Return: zero on success, else a negative error code. - */ - int linedisp_register(struct linedisp *linedisp, struct device *parent, -@@ -390,19 +553,23 @@ int linedisp_register(struct linedisp *linedisp, struct device *parent, - /* initialise a timer for scrolling the message */ - timer_setup(&linedisp->timer, linedisp_scroll, 0); - -- err = device_add(&linedisp->dev); -+ err = create_attachment(&linedisp->dev, linedisp, false); - if (err) - goto out_del_timer; - - /* display a default message */ - err = linedisp_display(linedisp, LINEDISP_INIT_TEXT, -1); - if (err) -- goto out_del_dev; -+ goto out_del_attach; -+ -+ err = device_add(&linedisp->dev); -+ if (err) -+ goto out_del_attach; - - return 0; - --out_del_dev: -- device_del(&linedisp->dev); -+out_del_attach: -+ delete_attachment(&linedisp->dev, false); - out_del_timer: - timer_delete_sync(&linedisp->timer); - out_put_device: -@@ -419,6 +586,7 @@ EXPORT_SYMBOL_NS_GPL(linedisp_register, "LINEDISP"); - void linedisp_unregister(struct linedisp *linedisp) - { - device_del(&linedisp->dev); -+ delete_attachment(&linedisp->dev, false); - timer_delete_sync(&linedisp->timer); - put_device(&linedisp->dev); - } -diff --git a/drivers/auxdisplay/line-display.h b/drivers/auxdisplay/line-display.h -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/line-display.h -+++ b/drivers/auxdisplay/line-display.h -@@ -6,6 +6,7 @@ - * Author: Paul Burton - * - * Copyright (C) 2021 Glider bv -+ * Copyright (C) 2025 Jean-François Lessard - */ - - #ifndef _LINEDISP_H -@@ -81,6 +82,9 @@ struct linedisp { - unsigned int id; - }; - -+int linedisp_attach(struct linedisp *linedisp, struct device *dev, -+ unsigned int num_chars, const struct linedisp_ops *ops); -+void linedisp_detach(struct device *dev); - int linedisp_register(struct linedisp *linedisp, struct device *parent, - unsigned int num_chars, const struct linedisp_ops *ops); - void linedisp_unregister(struct linedisp *linedisp); --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 16 Aug 2025 16:17:06 -0400 -Subject: dt-bindings: vendor-prefixes: Add fdhisi, titanmec, princeton, - winrise, wxicore - -Add vendor prefixes of chip manufacturers supported by the TM16xx 7-segment -LED matrix display controllers driver: -- fdhisi: Fuzhou Fuda Hisi Microelectronics Co., Ltd. -- titanmec: Shenzhen Titan Micro Electronics Co., Ltd. -- princeton: Princeton Technology Corp. -- winrise: Shenzhen Winrise Technology Co., Ltd. -- wxicore: Wuxi i-Core Electronics Co., Ltd. - -The titanmec prefix is based on the company's domain name titanmec.com. -The remaining prefixes are based on company names, as these manufacturers -either lack active .com domains or their .com domains are occupied by -unrelated businesses. - -The fdhisi and titanmec prefixes were originally identified by -Andreas Farber. - -CC: Andreas Farber -Acked-by: Conor Dooley -Signed-off-by: Jean-Francois Lessard ---- - Documentation/devicetree/bindings/vendor-prefixes.yaml | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml -index 111111111111..222222222222 100644 ---- a/Documentation/devicetree/bindings/vendor-prefixes.yaml -+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml -@@ -562,6 +562,8 @@ patternProperties: - description: Fastrax Oy - "^fcs,.*": - description: Fairchild Semiconductor -+ "^fdhisi,.*": -+ description: Fuzhou Fuda Hisi Microelectronics Co., Ltd. - "^feixin,.*": - description: Shenzhen Feixin Photoelectic Co., Ltd - "^feiyang,.*": -@@ -1279,6 +1281,8 @@ patternProperties: - description: Prime View International (PVI) - "^primux,.*": - description: Primux Trading, S.L. -+ "^princeton,.*": -+ description: Princeton Technology Corp. - "^probox2,.*": - description: PROBOX2 (by W2COMP Co., Ltd.) - "^pri,.*": -@@ -1632,6 +1636,8 @@ patternProperties: - description: Texas Instruments - "^tianma,.*": - description: Tianma Micro-electronics Co., Ltd. -+ "^titanmec,.*": -+ description: Shenzhen Titan Micro Electronics Co., Ltd. - "^tlm,.*": - description: Trusted Logic Mobility - "^tmt,.*": -@@ -1791,6 +1797,8 @@ patternProperties: - description: Wingtech Technology Co., Ltd. - "^winlink,.*": - description: WinLink Co., Ltd -+ "^winrise,.*": -+ description: Shenzhen Winrise Technology Co., Ltd. - "^winsen,.*": - description: Winsen Corp. - "^winstar,.*": -@@ -1807,6 +1815,8 @@ patternProperties: - description: Wobo - "^wolfvision,.*": - description: WolfVision GmbH -+ "^wxicore,.*": -+ description: Wuxi i-Core Electronics Co., Ltd. - "^x-powers,.*": - description: X-Powers - "^xen,.*": --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Mon, 1 Sep 2025 11:57:15 -0400 -Subject: dt-bindings: leds: add default-brightness property to common.yaml - -Add default-brightness property to leds/common.yaml to establish a single -canonical definition for LED brightness initialization. - -The property is currently defined locally in leds/leds-pwm.yaml and is -needed by auxdisplay/titanmec,tm16xx.yaml. Properties should be defined -in only one location to avoid type inconsistencies across bindings. - -Signed-off-by: Jean-Francois Lessard ---- - Documentation/devicetree/bindings/leds/common.yaml | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/Documentation/devicetree/bindings/leds/common.yaml b/Documentation/devicetree/bindings/leds/common.yaml -index 111111111111..222222222222 100644 ---- a/Documentation/devicetree/bindings/leds/common.yaml -+++ b/Documentation/devicetree/bindings/leds/common.yaml -@@ -173,6 +173,12 @@ properties: - led-max-microamp. - $ref: /schemas/types.yaml#/definitions/uint32 - -+ default-brightness: -+ description: -+ Brightness to be set if LED's default state is on. Used only during -+ initialization. If the option is not set then max brightness is used. -+ $ref: /schemas/types.yaml#/definitions/uint32 -+ - panic-indicator: - description: - This property specifies that the LED should be used, if at all possible, --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Mon, 1 Sep 2025 12:03:37 -0400 -Subject: dt-bindings: auxdisplay: add Titan Micro Electronics TM16xx - -Add documentation for TM16xx-compatible 7-segment LED display controllers -with keyscan. - -Signed-off-by: Jean-Francois Lessard ---- - Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml | 463 ++++++++++ - 1 file changed, 463 insertions(+) - -diff --git a/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml b/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml -new file mode 100644 -index 000000000000..111111111111 ---- /dev/null -+++ b/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml -@@ -0,0 +1,463 @@ -+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) -+%YAML 1.2 -+--- -+$id: http://devicetree.org/schemas/auxdisplay/titanmec,tm16xx.yaml# -+$schema: http://devicetree.org/meta-schemas/core.yaml# -+ -+title: Auxiliary displays based on TM16xx and compatible LED controllers -+ -+maintainers: -+ - Jean-François Lessard -+ -+description: | -+ LED matrix controllers used in auxiliary display devices that drive individual -+ LED icons and 7-segment digit groups through a grid/segment addressing scheme. -+ Controllers manage a matrix of LEDs organized as grids (columns/banks in -+ vendor datasheets) and segments (rows/bit positions in vendor datasheets). -+ Maximum brightness and grid/segment indices are controller-specific. -+ Controller-specific maximum are validated in the driver. -+ -+ The controller is agnostic of the display layout. Board-specific LED wiring is -+ described through child nodes that specify grid/segment coordinates for -+ individual icons and segment mapping for 7-segment digits. -+ -+ The bindings use separate 'leds' and 'digits' containers to accommodate -+ different addressing schemes: -+ - LEDs use 2-cell addressing (grid, segment) for matrix coordinates -+ - Digits use 1-cell addressing with explicit segment mapping -+ -+ The controller node exposes a logical LED-like control for the aggregate -+ display brightness. Child nodes describe individual icons and 7-seg digits. -+ The top-level control supports only label and brightness-related properties -+ and does not support other common LED properties such as color or function. -+ Child LED nodes use the standard LED binding. -+ -+ Optional keypad scanning is supported when both 'linux,keymap' and -+ 'poll-interval' properties are specified. -+ -+properties: -+ compatible: -+ oneOf: -+ - items: -+ - enum: -+ - fdhisi,fd628 -+ - princeton,pt6964 -+ - wxicore,aip1628 -+ - const: titanmec,tm1628 -+ - items: -+ - enum: -+ - wxicore,aip1618 -+ - const: titanmec,tm1618 -+ - items: -+ - enum: -+ - fdhisi,fd650 -+ - wxicore,aip650 -+ - const: titanmec,tm1650 -+ - enum: -+ - fdhisi,fd620 -+ - fdhisi,fd655 -+ - fdhisi,fd6551 -+ - titanmec,tm1618 -+ - titanmec,tm1620 -+ - titanmec,tm1628 -+ - titanmec,tm1638 -+ - titanmec,tm1650 -+ - winrise,hbs658 -+ -+ reg: -+ maxItems: 1 -+ -+ label: -+ description: -+ The label for the top-level LED. If omitted, the label is taken from the -+ node name (excluding the unit address). It has to uniquely identify a -+ device, i.e. no other LED class device can be assigned the same label. -+ -+ max-brightness: -+ minimum: 0 # 0=off -+ maximum: 8 # Maximum across all TM16xx controllers -+ description: -+ Normally the maximum brightness is determined by the hardware and this -+ property is not required. This property is used to put a software limit -+ on the brightness apart from what the driver says, as it could happen -+ that a LED can be made so bright that it gets damaged or causes damage -+ due to restrictions in a specific system, such as mounting conditions. -+ -+ default-brightness: -+ minimum: 0 # 0=off -+ maximum: 8 # Maximum across all TM16xx controllers -+ description: -+ Brightness to be set if LED's default state is on. Used only during -+ initialization. If the option is not set then max brightness is used. -+ -+ digits: -+ type: object -+ description: Container for 7-segment digit group definitions -+ additionalProperties: false -+ -+ properties: -+ "#address-cells": -+ const: 1 -+ "#size-cells": -+ const: 0 -+ -+ patternProperties: -+ "^digit@[0-9]+$": -+ type: object -+ unevaluatedProperties: false -+ -+ properties: -+ reg: -+ description: -+ Digit position identifier numbered sequentially left-to-right, -+ with reg=0 representing the leftmost digit position as displayed -+ to the user. -+ maxItems: 1 -+ -+ segments: -+ $ref: /schemas/types.yaml#/definitions/uint32-matrix -+ description: | -+ Array of grid/segment coordinate pairs for each 7-segment position. -+ Each entry is mapping to standard 7-segment positions -+ in order: a, b, c, d, e, f, g -+ -+ Standard 7-segment layout: -+ aaa -+ f b -+ f b -+ ggg -+ e c -+ e c -+ ddd -+ items: -+ items: -+ - description: Grid index -+ - description: Segment index -+ minItems: 7 -+ maxItems: 7 -+ -+ required: -+ - reg -+ - segments -+ -+ leds: -+ type: object -+ description: Container for individual LED icon definitions -+ additionalProperties: false -+ -+ properties: -+ "#address-cells": -+ const: 2 -+ "#size-cells": -+ const: 0 -+ -+ patternProperties: -+ "^led@[0-9]+,[0-9]+$": -+ type: object -+ $ref: /schemas/leds/common.yaml# -+ unevaluatedProperties: false -+ -+ properties: -+ reg: -+ description: -+ Grid and segment indices as of this individual LED icon -+ -+ required: -+ - reg -+ -+dependencies: -+ poll-interval: -+ - linux,keymap -+ linux,keymap: -+ - poll-interval -+ autorepeat: -+ - linux,keymap -+ - poll-interval -+ -+required: -+ - compatible -+ - reg -+ -+allOf: -+ - $ref: /schemas/leds/common.yaml# -+ properties: -+ color: false -+ function: false -+ function-enumerator: false -+ - $ref: /schemas/input/input.yaml# -+ - $ref: /schemas/input/matrix-keymap.yaml# -+ # SPI controllers require 3-wire (combined MISO/MOSI line) -+ - if: -+ properties: -+ compatible: -+ contains: -+ enum: -+ - fdhisi,fd620 -+ - fdhisi,fd628 -+ - princeton,pt6964 -+ - titanmec,tm1618 -+ - titanmec,tm1620 -+ - titanmec,tm1628 -+ - titanmec,tm1638 -+ - wxicore,aip1618 -+ - wxicore,aip1628 -+ then: -+ $ref: /schemas/spi/spi-peripheral-props.yaml# -+ properties: -+ spi-3wire: true -+ required: -+ - spi-3wire -+ -+unevaluatedProperties: false -+ -+examples: -+ - | -+ #include -+ -+ // I2C example: Magicsee N5 TV box with fd655 controller -+ i2c { -+ #address-cells = <1>; -+ #size-cells = <0>; -+ -+ display@24 { -+ reg = <0x24>; -+ compatible = "fdhisi,fd655"; -+ -+ digits { -+ #address-cells = <1>; -+ #size-cells = <0>; -+ digit@0 { -+ reg = <0>; -+ segments = <1 3>, <1 4>, <1 5>, <1 0>, <1 1>, <1 2>, <1 6>; -+ }; -+ digit@1 { -+ reg = <1>; -+ segments = <2 3>, <2 4>, <2 5>, <2 0>, <2 1>, <2 2>, <2 6>; -+ }; -+ digit@2 { -+ reg = <2>; -+ segments = <3 3>, <3 4>, <3 5>, <3 0>, <3 1>, <3 2>, <3 6>; -+ }; -+ digit@3 { -+ reg = <3>; -+ segments = <4 3>, <4 4>, <4 5>, <4 0>, <4 1>, <4 2>, <4 6>; -+ }; -+ }; -+ -+ leds { -+ #address-cells = <2>; -+ #size-cells = <0>; -+ led@0,0 { -+ reg = <0 0>; -+ function = LED_FUNCTION_ALARM; -+ }; -+ led@0,1 { -+ reg = <0 1>; -+ function = LED_FUNCTION_USB; -+ }; -+ led@0,2 { -+ reg = <0 2>; -+ function = "play"; -+ }; -+ led@0,3 { -+ reg = <0 3>; -+ function = "pause"; -+ }; -+ led@0,4 { -+ reg = <0 4>; -+ function = "colon"; -+ }; -+ led@0,5 { -+ reg = <0 5>; -+ function = LED_FUNCTION_LAN; -+ }; -+ led@0,6 { -+ reg = <0 6>; -+ function = LED_FUNCTION_WLAN; -+ }; -+ }; -+ }; -+ }; -+ -+ - | -+ #include -+ -+ // SPI example: TM1638 module with keypad support -+ spi { -+ #address-cells = <1>; -+ #size-cells = <0>; -+ -+ display@0 { -+ reg = <0>; -+ compatible = "titanmec,tm1638"; -+ spi-3wire; -+ spi-lsb-first; -+ spi-max-frequency = <500000>; -+ -+ label = "tm1638"; -+ default-brightness = <2>; -+ max-brightness = <4>; -+ poll-interval = <100>; -+ linux,keymap = ; -+ -+ digits { -+ #address-cells = <1>; -+ #size-cells = <0>; -+ -+ digit@0 { -+ reg = <0>; -+ segments = <7 0>, <7 1>, <7 2>, <7 3>, <7 4>, <7 5>, <7 6>; -+ }; -+ -+ digit@1 { -+ reg = <1>; -+ segments = <6 0>, <6 1>, <6 2>, <6 3>, <6 4>, <6 5>, <6 6>; -+ }; -+ -+ digit@2 { -+ reg = <2>; -+ segments = <5 0>, <5 1>, <5 2>, <5 3>, <5 4>, <5 5>, <5 6>; -+ }; -+ -+ digit@3 { -+ reg = <3>; -+ segments = <4 0>, <4 1>, <4 2>, <4 3>, <4 4>, <4 5>, <4 6>; -+ }; -+ -+ digit@4 { -+ reg = <4>; -+ segments = <3 0>, <3 1>, <3 2>, <3 3>, <3 4>, <3 5>, <3 6>; -+ }; -+ -+ digit@5 { -+ reg = <5>; -+ segments = <2 0>, <2 1>, <2 2>, <2 3>, <2 4>, <2 5>, <2 6>; -+ }; -+ -+ digit@6 { -+ reg = <6>; -+ segments = <1 0>, <1 1>, <1 2>, <1 3>, <1 4>, <1 5>, <1 6>; -+ }; -+ -+ digit@7 { -+ reg = <7>; -+ segments = <0 0>, <0 1>, <0 2>, <0 3>, <0 4>, <0 5>, <0 6>; -+ }; -+ }; -+ -+ leds { -+ #address-cells = <2>; -+ #size-cells = <0>; -+ -+ led@0,7 { -+ reg = <0 7>; -+ }; -+ -+ led@1,7 { -+ reg = <1 7>; -+ }; -+ -+ led@2,7 { -+ reg = <2 7>; -+ }; -+ -+ led@3,7 { -+ reg = <3 7>; -+ }; -+ -+ led@4,7 { -+ reg = <4 7>; -+ }; -+ -+ led@5,7 { -+ reg = <5 7>; -+ }; -+ -+ led@6,7 { -+ reg = <6 7>; -+ }; -+ -+ led@7,7 { -+ reg = <7 7>; -+ }; -+ }; -+ }; -+ }; -+ -+ - | -+ #include -+ -+ // SPI example: X96 Max with transposed layout (fd628 with tm1628 fallback) -+ spi { -+ #address-cells = <1>; -+ #size-cells = <0>; -+ -+ display@0 { -+ reg = <0>; -+ compatible = "fdhisi,fd628", "titanmec,tm1628"; -+ spi-3wire; -+ spi-lsb-first; -+ spi-max-frequency = <500000>; -+ -+ digits { -+ #address-cells = <1>; -+ #size-cells = <0>; -+ digit@0 { -+ reg = <0>; -+ segments = <0 7>, <1 7>, <2 7>, <3 7>, <4 7>, <5 7>, <6 7>; -+ }; -+ digit@1 { -+ reg = <1>; -+ segments = <0 6>, <1 6>, <2 6>, <3 6>, <4 6>, <5 6>, <6 6>; -+ }; -+ digit@2 { -+ reg = <2>; -+ segments = <0 5>, <1 5>, <2 5>, <3 5>, <4 5>, <5 5>, <6 5>; -+ }; -+ digit@3 { -+ reg = <3>; -+ segments = <0 4>, <1 4>, <2 4>, <3 4>, <4 4>, <5 4>, <6 4>; -+ }; -+ }; -+ -+ leds { -+ #address-cells = <2>; -+ #size-cells = <0>; -+ led@0,3 { -+ reg = <0 3>; -+ function = "apps"; -+ }; -+ led@1,3 { -+ reg = <1 3>; -+ function = "setup"; -+ }; -+ led@2,3 { -+ reg = <2 3>; -+ function = LED_FUNCTION_USB; -+ }; -+ led@3,3 { -+ reg = <3 3>; -+ function = LED_FUNCTION_SD; -+ }; -+ led@4,3 { -+ reg = <4 3>; -+ function = "colon"; -+ }; -+ led@5,3 { -+ reg = <5 3>; -+ function = "hdmi"; -+ }; -+ led@6,3 { -+ reg = <6 3>; -+ function = "video"; -+ }; -+ }; -+ }; -+ }; --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 16 Aug 2025 20:52:21 -0400 -Subject: auxdisplay: Add TM16xx 7-segment LED matrix display controllers - driver - -Add driver for TM16xx family LED controllers and compatible chips from -multiple vendors including Titan Micro, Fuda Hisi, i-Core, Princeton, and -Winrise. These controllers drive 7-segment digits and individual LED icons -through either I2C or SPI buses. - -Successfully tested on various ARM TV boxes including H96 Max, Magicsee N5, -Tanix TX3 Mini, Tanix TX6, X92, and X96 Max across different SoC platforms -(Rockchip, Amlogic, Allwinner). - -Acked-by: Paolo Sabatino # As primary user, integrated tm16xx into Armbian rockchip64 -Acked-by: Christian Hewitt # As primary user, integrated tm16xx into LibreElec -Tested-by: Boris Gjenero # Tested on X92 -Tested-by: Paolo Sabatino # Tested on H96 Max (XY_RK3328) -Tested-by: Christian Hewitt # Tested on X96 Max, Tanix TX3 Mini -Tested-by: Martin Blumenstingl # Tested on Tanix TX3 Mini -Signed-off-by: Jean-Francois Lessard ---- - drivers/auxdisplay/Kconfig | 9 + - drivers/auxdisplay/Makefile | 2 + - drivers/auxdisplay/tm16xx.h | 172 ++++ - drivers/auxdisplay/tm16xx_core.c | 459 ++++++++++ - 4 files changed, 642 insertions(+) - -diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/Kconfig -+++ b/drivers/auxdisplay/Kconfig -@@ -526,6 +526,15 @@ config SEG_LED_GPIO - This driver can also be built as a module. If so, the module - will be called seg-led-gpio. - -+config TM16XX -+ tristate -+ select LEDS_CLASS -+ select LEDS_TRIGGERS -+ select LINEDISP -+ select NEW_LEDS -+ help -+ Core TM16XX-compatible 7-segment LED controllers module -+ - # - # Character LCD with non-conforming interface section - # -diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/Makefile -+++ b/drivers/auxdisplay/Makefile -@@ -16,3 +16,5 @@ obj-$(CONFIG_LINEDISP) += line-display.o - obj-$(CONFIG_MAX6959) += max6959.o - 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 -diff --git a/drivers/auxdisplay/tm16xx.h b/drivers/auxdisplay/tm16xx.h -new file mode 100644 -index 000000000000..111111111111 ---- /dev/null -+++ b/drivers/auxdisplay/tm16xx.h -@@ -0,0 +1,172 @@ -+/* 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 -+ */ -+ -+#ifndef _TM16XX_H -+#define _TM16XX_H -+ -+#include -+#include -+#include -+ -+#include "line-display.h" -+ -+/* Common bit field definitions */ -+ -+/* Command type bits (bits 7-6) */ -+#define TM16XX_CMD_MASK GENMASK(7, 6) -+#define TM16XX_CMD_MODE (0 << 6) -+#define TM16XX_CMD_DATA (1 << 6) -+#define TM16XX_CMD_CTRL (2 << 6) -+#define TM16XX_CMD_ADDR (3 << 6) -+#define TM16XX_CMD_WRITE (TM16XX_CMD_DATA | TM16XX_DATA_MODE_WRITE) -+#define TM16XX_CMD_READ (TM16XX_CMD_DATA | TM16XX_DATA_MODE_READ) -+ -+/* Mode command grid settings (bits 1-0) */ -+#define TM16XX_MODE_GRID_MASK GENMASK(1, 0) -+#define TM16XX_MODE_4GRIDS (0 << 0) -+#define TM16XX_MODE_5GRIDS (1 << 0) -+#define TM16XX_MODE_6GRIDS (2 << 0) -+#define TM16XX_MODE_7GRIDS (3 << 0) -+ -+/* Data command settings */ -+#define TM16XX_DATA_ADDR_MASK BIT(2) -+#define TM16XX_DATA_ADDR_AUTO (0 << 2) -+#define TM16XX_DATA_ADDR_FIXED (1 << 2) -+#define TM16XX_DATA_MODE_MASK GENMASK(1, 0) -+#define TM16XX_DATA_MODE_WRITE (0 << 0) -+#define TM16XX_DATA_MODE_READ (2 << 0) -+ -+/* Control command settings */ -+#define TM16XX_CTRL_BR_MASK GENMASK(2, 0) -+#define TM16XX_CTRL_ON (1 << 3) -+ -+/* TM1618 specific constants */ -+#define TM1618_BYTE1_MASK GENMASK(4, 0) -+#define TM1618_BYTE2_MASK GENMASK(7, 5) -+#define TM1618_BYTE2_SHIFT 3 -+#define TM1618_KEY_READ_LEN 3 -+#define TM1618_KEY_MASK (BIT(4) | BIT(1)) -+ -+/* TM1628 specific constants */ -+#define TM1628_BYTE1_MASK GENMASK(7, 0) -+#define TM1628_BYTE2_MASK GENMASK(13, 8) -+#define TM1628_KEY_READ_LEN 5 -+#define TM1628_KEY_MASK (GENMASK(4, 3) | GENMASK(1, 0)) -+ -+/* TM1638 specific constants */ -+#define TM1638_KEY_READ_LEN 4 -+#define TM1638_KEY_MASK (GENMASK(6, 4) | GENMASK(2, 0)) -+ -+/* FD620 specific constants */ -+#define FD620_BYTE1_MASK GENMASK(6, 0) -+ -+#define FD620_BYTE2_MASK BIT(7) -+#define FD620_BYTE2_SHIFT 5 -+#define FD620_KEY_READ_LEN 4 -+#define FD620_KEY_MASK (BIT(3) | BIT(0)) -+ -+/* I2C controller addresses and control settings */ -+#define TM1650_CMD_CTRL 0x48 -+#define TM1650_CMD_READ 0x4F -+#define TM1650_CMD_ADDR 0x68 -+#define TM1650_CTRL_BR_MASK GENMASK(6, 4) -+#define TM1650_CTRL_ON (1 << 0) -+#define TM1650_CTRL_SLEEP (1 << 2) -+#define TM1650_CTRL_SEG_MASK BIT(3) -+#define TM1650_CTRL_SEG8_MODE (0 << 3) -+#define TM1650_CTRL_SEG7_MODE (1 << 3) -+#define TM1650_KEY_ROW_MASK GENMASK(1, 0) -+#define TM1650_KEY_COL_MASK GENMASK(5, 3) -+#define TM1650_KEY_DOWN_MASK BIT(6) -+#define TM1650_KEY_COMBINED GENMASK(5, 3) -+ -+#define FD655_CMD_CTRL 0x48 -+#define FD655_CMD_ADDR 0x66 -+#define FD655_CTRL_BR_MASK GENMASK(6, 5) -+#define FD655_CTRL_ON (1 << 0) -+ -+#define FD6551_CMD_CTRL 0x48 -+#define FD6551_CTRL_BR_MASK GENMASK(3, 1) -+#define FD6551_CTRL_ON (1 << 0) -+ -+#define HBS658_KEY_COL_MASK GENMASK(7, 5) -+ -+#define TM16XX_CTRL_BRIGHTNESS(on, val, prefix) \ -+ ((on) ? (FIELD_PREP(prefix##_CTRL_BR_MASK, (val)) | prefix##_CTRL_ON) : 0) -+ -+/* Forward declarations */ -+struct tm16xx_display; -+struct tm16xx_digit; -+struct tm16xx_led; -+ -+/** -+ * DOC: struct tm16xx_controller - Controller-specific operations and limits -+ * @max_grids: Maximum number of grids supported by the controller. -+ * @max_segments: Maximum number of segments supported by the controller. -+ * @max_brightness: Maximum brightness level supported by the controller. -+ * @max_key_rows: Maximum number of key input rows supported by the controller. -+ * @max_key_cols: Maximum number of key input columns supported by the controller. -+ * @init: Pointer to controller mode/brightness configuration function. -+ * @data: Pointer to function writing display data to the controller. -+ * @keys: Pointer to function reading controller key state into bitmap. -+ * -+ * Holds function pointers and limits for controller-specific operations. -+ */ -+struct tm16xx_controller { -+ const u8 max_grids; -+ const u8 max_segments; -+ const u8 max_brightness; -+ const u8 max_key_rows; -+ const u8 max_key_cols; -+ int (*const init)(struct tm16xx_display *display); -+ int (*const data)(struct tm16xx_display *display, u8 index, unsigned int grid); -+ int (*const keys)(struct tm16xx_display *display); -+}; -+ -+/** -+ * struct tm16xx_display - Main driver structure for the display -+ * @dev: Pointer to device struct. -+ * @controller: Controller-specific function table and limits. -+ * @linedisp: character line display structure -+ * @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. -+ * @main_led: LED class device for the entire display. -+ * @leds: Array of individual LED icon structures. -+ * @num_leds: Number of individual LED icons. -+ * @digits: Array of 7-segment digit structures. -+ * @num_digits: Number of 7-segment digits. -+ * @flush_init: Work struct for configuration update. -+ * @flush_display: Work struct for display update. -+ * @flush_status: Status/result of last flush work. -+ * @lock: Mutex protecting concurrent access to work operations. -+ * @state: Bitmap holding current raw display state. -+ */ -+struct tm16xx_display { -+ struct device *dev; -+ const struct tm16xx_controller *controller; -+ struct linedisp linedisp; -+ u8 *spi_buffer; -+ u8 num_hwgrid; -+ u8 num_hwseg; -+ struct led_classdev main_led; -+ struct tm16xx_led *leds; -+ u8 num_leds; -+ struct tm16xx_digit *digits; -+ u8 num_digits; -+ struct work_struct flush_init; -+ struct work_struct flush_display; -+ int flush_status; -+ struct mutex lock; /* prevents concurrent work operations */ -+ unsigned long *state; -+}; -+ -+int tm16xx_probe(struct tm16xx_display *display); -+void tm16xx_remove(struct tm16xx_display *display); -+ -+#endif /* _TM16XX_H */ -diff --git a/drivers/auxdisplay/tm16xx_core.c b/drivers/auxdisplay/tm16xx_core.c -new file mode 100644 -index 000000000000..111111111111 ---- /dev/null -+++ b/drivers/auxdisplay/tm16xx_core.c -@@ -0,0 +1,459 @@ -+// 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "line-display.h" -+#include "tm16xx.h" -+ -+#define TM16XX_DIGIT_SEGMENTS 7 -+ -+#define linedisp_to_tm16xx(display) \ -+ container_of(display, struct tm16xx_display, linedisp) -+ -+/** -+ * struct tm16xx_led - Individual LED icon mapping -+ * @cdev: LED class device for sysfs interface. -+ * @hwgrid: Controller grid index of the LED. -+ * @hwseg: Controller segment index of the LED. -+ */ -+struct tm16xx_led { -+ struct led_classdev cdev; -+ u8 hwgrid; -+ u8 hwseg; -+}; -+ -+/** -+ * struct tm16xx_digit - 7-segment digit mapping and value -+ * @hwgrids: Array mapping each 7-segment position to a grid on the controller. -+ * @hwsegs: Array mapping each 7-segment position to a segment on the controller. -+ * @value: Current character value displayed on this digit. -+ */ -+struct tm16xx_digit { -+ u8 hwgrids[TM16XX_DIGIT_SEGMENTS]; -+ u8 hwsegs[TM16XX_DIGIT_SEGMENTS]; -+}; -+ -+/* state bitmap helpers */ -+/** -+ * tm16xx_led_nbits() - Number of bits used for the display state bitmap -+ * @display: pointer to tm16xx_display -+ * -+ * Return: total bits in the display state bitmap (grids * segments) -+ */ -+static inline unsigned int tm16xx_led_nbits(const struct tm16xx_display *display) -+{ -+ return display->num_hwgrid * display->num_hwseg; -+} -+ -+/** -+ * tm16xx_set_seg() - Set the display state for a specific grid/segment -+ * @display: pointer to tm16xx_display -+ * @hwgrid: grid index -+ * @hwseg: segment index -+ * @on: true to turn on, false to turn off -+ */ -+static inline void tm16xx_set_seg(const struct tm16xx_display *display, -+ const u8 hwgrid, const u8 hwseg, const bool on) -+{ -+ assign_bit(hwgrid * display->num_hwseg + hwseg, display->state, on); -+} -+ -+/** -+ * tm16xx_get_grid() - Get the current segment pattern for a grid -+ * @display: pointer to tm16xx_display -+ * @index: grid index -+ * -+ * Return: bit pattern of all segments for the given grid -+ */ -+static inline unsigned int tm16xx_get_grid(const struct tm16xx_display *display, -+ const unsigned int index) -+{ -+ return bitmap_read(display->state, index * display->num_hwseg, -+ display->num_hwseg); -+} -+ -+/* main display */ -+/** -+ * tm16xx_display_flush_init() - Workqueue to configure controller and set brightness -+ * @work: pointer to work_struct -+ */ -+static void tm16xx_display_flush_init(struct work_struct *work) -+{ -+ struct tm16xx_display *display = container_of(work, -+ struct tm16xx_display, -+ flush_init); -+ int ret; -+ -+ if (display->controller->init) { -+ scoped_guard(mutex, &display->lock) { -+ ret = display->controller->init(display); -+ display->flush_status = ret; -+ } -+ if (ret) -+ dev_err(display->dev, -+ "Failed to configure controller: %d\n", ret); -+ } -+} -+ -+/** -+ * tm16xx_display_flush_data() - Workqueue to update display data to controller -+ * @work: pointer to work_struct -+ */ -+static void tm16xx_display_flush_data(struct work_struct *work) -+{ -+ struct tm16xx_display *display = container_of(work, -+ struct tm16xx_display, -+ flush_display); -+ unsigned int grid, i; -+ int ret = 0; -+ -+ scoped_guard(mutex, &display->lock) { -+ if (display->controller->data) { -+ for (i = 0; i < display->num_hwgrid; i++) { -+ grid = tm16xx_get_grid(display, i); -+ ret = display->controller->data(display, i, grid); -+ if (ret) { -+ dev_err(display->dev, -+ "Failed to write display data: %d\n", -+ ret); -+ break; -+ } -+ } -+ } -+ -+ display->flush_status = ret; -+ } -+} -+ -+/** -+ * tm16xx_brightness_set() - Set display main LED brightness -+ * @led_cdev: pointer to led_classdev -+ * @brightness: new brightness value -+ */ -+static void tm16xx_brightness_set(struct led_classdev *led_cdev, -+ enum led_brightness brightness) -+{ -+ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); -+ -+ led_cdev->brightness = brightness; -+ schedule_work(&display->flush_init); -+} -+ -+/** -+ * tm16xx_led_set() - Set state of an individual LED icon -+ * @led_cdev: pointer to led_classdev -+ * @value: new brightness (0/1) -+ */ -+static void tm16xx_led_set(struct led_classdev *led_cdev, -+ enum led_brightness value) -+{ -+ struct tm16xx_led *led = container_of(led_cdev, struct tm16xx_led, cdev); -+ struct tm16xx_display *display = dev_get_drvdata(led_cdev->dev->parent); -+ -+ tm16xx_set_seg(display, led->hwgrid, led->hwseg, value); -+ schedule_work(&display->flush_display); -+} -+ -+static int tm16xx_display_value(struct tm16xx_display *display, const char *buf, size_t count) -+{ -+ struct linedisp *linedisp = &display->linedisp; -+ struct linedisp_map *map = linedisp->map; -+ struct tm16xx_digit *digit; -+ unsigned int i, j; -+ int seg_pattern; -+ bool val; -+ -+ for (i = 0; i < display->num_digits && i < count; i++) { -+ digit = &display->digits[i]; -+ seg_pattern = map_to_seg7(&map->map.seg7, buf[i]); -+ -+ for (j = 0; j < TM16XX_DIGIT_SEGMENTS; j++) { -+ val = seg_pattern & BIT(j); -+ tm16xx_set_seg(display, digit->hwgrids[j], digit->hwsegs[j], val); -+ } -+ } -+ -+ for (; i < display->num_digits; i++) { -+ digit = &display->digits[i]; -+ for (j = 0; j < TM16XX_DIGIT_SEGMENTS; j++) -+ tm16xx_set_seg(display, digit->hwgrids[j], digit->hwsegs[j], 0); -+ } -+ -+ schedule_work(&display->flush_display); -+ return 0; -+} -+ -+static int tm16xx_linedisp_get_map_type(struct linedisp *linedisp) -+{ -+ return LINEDISP_MAP_SEG7; -+} -+ -+static void tm16xx_linedisp_update(struct linedisp *linedisp) -+{ -+ struct tm16xx_display *display = linedisp_to_tm16xx(linedisp); -+ -+ tm16xx_display_value(display, linedisp->buf, linedisp->num_chars); -+} -+ -+static const struct linedisp_ops tm16xx_linedisp_ops = { -+ .get_map_type = tm16xx_linedisp_get_map_type, -+ .update = tm16xx_linedisp_update, -+}; -+ -+static int tm16xx_display_init(struct tm16xx_display *display) -+{ -+ schedule_work(&display->flush_init); -+ flush_work(&display->flush_init); -+ if (display->flush_status) -+ return display->flush_status; -+ -+ return 0; -+} -+ -+static int tm16xx_parse_fwnode(struct device *dev, struct tm16xx_display *display) -+{ -+ struct tm16xx_led *led; -+ struct tm16xx_digit *digit; -+ unsigned int max_hwgrid = 0, max_hwseg = 0; -+ unsigned int i, j; -+ int ret; -+ u32 segments[TM16XX_DIGIT_SEGMENTS * 2]; -+ u32 reg[2]; -+ -+ struct fwnode_handle *digits_node __free(fwnode_handle) = -+ device_get_named_child_node(dev, "digits"); -+ struct fwnode_handle *leds_node __free(fwnode_handle) = -+ device_get_named_child_node(dev, "leds"); -+ -+ /* parse digits */ -+ if (digits_node) { -+ display->num_digits = fwnode_get_child_node_count(digits_node); -+ -+ if (display->num_digits) { -+ display->digits = devm_kcalloc(dev, display->num_digits, -+ sizeof(*display->digits), -+ GFP_KERNEL); -+ if (!display->digits) -+ return -ENOMEM; -+ -+ i = 0; -+ fwnode_for_each_available_child_node_scoped(digits_node, child) { -+ digit = &display->digits[i]; -+ -+ ret = fwnode_property_read_u32(child, "reg", reg); -+ if (ret) -+ return ret; -+ -+ ret = fwnode_property_read_u32_array(child, -+ "segments", segments, -+ TM16XX_DIGIT_SEGMENTS * 2); -+ if (ret < 0) -+ return ret; -+ -+ for (j = 0; j < TM16XX_DIGIT_SEGMENTS; ++j) { -+ digit->hwgrids[j] = segments[2 * j]; -+ digit->hwsegs[j] = segments[2 * j + 1]; -+ max_hwgrid = umax(max_hwgrid, digit->hwgrids[j]); -+ max_hwseg = umax(max_hwseg, digit->hwsegs[j]); -+ } -+ i++; -+ } -+ } -+ } -+ -+ /* parse leds */ -+ if (leds_node) { -+ display->num_leds = fwnode_get_child_node_count(leds_node); -+ -+ if (display->num_leds) { -+ display->leds = devm_kcalloc(dev, display->num_leds, -+ sizeof(*display->leds), -+ GFP_KERNEL); -+ if (!display->leds) -+ return -ENOMEM; -+ -+ i = 0; -+ fwnode_for_each_available_child_node_scoped(leds_node, child) { -+ led = &display->leds[i]; -+ ret = fwnode_property_read_u32_array(child, "reg", reg, 2); -+ if (ret < 0) -+ return ret; -+ -+ led->hwgrid = reg[0]; -+ led->hwseg = reg[1]; -+ max_hwgrid = umax(max_hwgrid, led->hwgrid); -+ max_hwseg = umax(max_hwseg, led->hwseg); -+ i++; -+ } -+ } -+ } -+ -+ if (max_hwgrid >= display->controller->max_grids) { -+ dev_err(dev, "grid %u exceeds controller max_grids %u\n", -+ max_hwgrid, display->controller->max_grids); -+ return -EINVAL; -+ } -+ -+ if (max_hwseg >= display->controller->max_segments) { -+ dev_err(dev, "segment %u exceeds controller max_segments %u\n", -+ max_hwseg, display->controller->max_segments); -+ return -EINVAL; -+ } -+ -+ display->num_hwgrid = max_hwgrid + 1; -+ display->num_hwseg = max_hwseg + 1; -+ -+ return 0; -+} -+ -+/** -+ * tm16xx_probe() - Probe and initialize display device, register LEDs -+ * @display: pointer to tm16xx_display -+ * -+ * Return: 0 on success, negative error code on failure -+ */ -+int tm16xx_probe(struct tm16xx_display *display) -+{ -+ struct device *dev = display->dev; -+ struct led_classdev *main = &display->main_led; -+ struct led_init_data led_init = {0}; -+ struct fwnode_handle *leds_node; -+ struct tm16xx_led *led; -+ unsigned int nbits, i; -+ int ret; -+ -+ ret = tm16xx_parse_fwnode(dev, display); -+ if (ret) -+ return dev_err_probe(dev, ret, "Failed to parse device tree\n"); -+ -+ nbits = tm16xx_led_nbits(display); -+ display->state = devm_bitmap_zalloc(dev, nbits, GFP_KERNEL); -+ if (!display->state) -+ return -ENOMEM; -+ -+ ret = devm_mutex_init(display->dev, &display->lock); -+ if (ret) -+ return dev_err_probe(dev, ret, "Failed to initialize mutex\n"); -+ -+ INIT_WORK(&display->flush_init, tm16xx_display_flush_init); -+ INIT_WORK(&display->flush_display, tm16xx_display_flush_data); -+ -+ /* Initialize main LED properties */ -+ led_init.fwnode = dev_fwnode(dev); /* apply label property */ -+ main->max_brightness = display->controller->max_brightness; -+ device_property_read_u32(dev, "max-brightness", &main->max_brightness); -+ main->max_brightness = umin(main->max_brightness, -+ display->controller->max_brightness); -+ -+ main->brightness = main->max_brightness; -+ device_property_read_u32(dev, "default-brightness", &main->brightness); -+ main->brightness = umin(main->brightness, main->max_brightness); -+ -+ main->brightness_set = tm16xx_brightness_set; -+ main->flags = LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; -+ -+ /* Register individual LEDs from device tree */ -+ ret = led_classdev_register_ext(dev, main, &led_init); -+ if (ret) -+ return dev_err_probe(dev, ret, "Failed to register main LED\n"); -+ -+ i = 0; -+ led_init.devicename = dev_name(main->dev); -+ led_init.devname_mandatory = true; -+ led_init.default_label = "led"; -+ leds_node = device_get_named_child_node(dev, "leds"); -+ fwnode_for_each_available_child_node_scoped(leds_node, child) { -+ led_init.fwnode = child; -+ led = &display->leds[i]; -+ led->cdev.max_brightness = 1; -+ led->cdev.brightness_set = tm16xx_led_set; -+ led->cdev.flags = LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; -+ -+ ret = led_classdev_register_ext(dev, &led->cdev, &led_init); -+ if (ret) { -+ dev_err_probe(dev, ret, "Failed to register LED %s\n", -+ led->cdev.name); -+ goto unregister_leds; -+ } -+ -+ i++; -+ } -+ -+ ret = tm16xx_display_init(display); -+ if (ret) { -+ dev_err_probe(dev, ret, "Failed to initialize display\n"); -+ goto unregister_leds; -+ } -+ -+ ret = linedisp_attach(&display->linedisp, display->main_led.dev, -+ display->num_digits, &tm16xx_linedisp_ops); -+ if (ret) { -+ dev_err_probe(dev, ret, "Failed to initialize line-display\n"); -+ goto unregister_leds; -+ } -+ -+ return 0; -+ -+unregister_leds: -+ while (i--) -+ led_classdev_unregister(&display->leds[i].cdev); -+ -+ led_classdev_unregister(main); -+ return ret; -+} -+EXPORT_SYMBOL_NS(tm16xx_probe, "TM16XX"); -+ -+/** -+ * tm16xx_remove() - Remove display, unregister LEDs, blank output -+ * @display: pointer to tm16xx_display -+ */ -+void tm16xx_remove(struct tm16xx_display *display) -+{ -+ unsigned int nbits = tm16xx_led_nbits(display); -+ struct tm16xx_led *led; -+ -+ linedisp_detach(display->main_led.dev); -+ -+ /* -+ * Unregister LEDs first to immediately stop trigger activity. -+ * This prevents LED triggers from attempting to access hardware -+ * after it's been disconnected or driver unloaded. -+ */ -+ for (int i = 0; i < display->num_leds; i++) { -+ led = &display->leds[i]; -+ led_classdev_unregister(&led->cdev); -+ } -+ led_classdev_unregister(&display->main_led); -+ -+ /* Clear display state */ -+ bitmap_zero(display->state, nbits); -+ schedule_work(&display->flush_display); -+ flush_work(&display->flush_display); -+ -+ /* Turn off display */ -+ display->main_led.brightness = LED_OFF; -+ schedule_work(&display->flush_init); -+ flush_work(&display->flush_init); -+} -+EXPORT_SYMBOL_NS(tm16xx_remove, "TM16XX"); -+ -+MODULE_AUTHOR("Jean-François Lessard"); -+MODULE_DESCRIPTION("TM16xx LED Display Controllers"); -+MODULE_LICENSE("GPL"); -+MODULE_IMPORT_NS("LINEDISP"); --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Fri, 29 Aug 2025 22:28:35 -0400 -Subject: 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-Francois Lessard ---- - 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 ++++++++++ - 5 files changed, 235 insertions(+) - -diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig -index 111111111111..222222222222 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 111111111111..222222222222 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 111111111111..222222222222 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 111111111111..222222222222 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..111111111111 ---- /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 -+#include -+#include -+#include -+#include -+#include -+ -+#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; -+} --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 23 Aug 2025 21:26:59 -0400 -Subject: auxdisplay: TM16xx: Add support for I2C-based controllers - -Add support for TM16xx-compatible auxiliary display controllers connected -via the I2C bus. - -The implementation includes: -- I2C driver registration and initialization -- Probe/remove logic for I2C devices -- Controller-specific handling and communication sequences -- Integration with the TM16xx core driver for common functionality - -This allows platforms using TM16xx or compatible controllers over I2C to be -managed by the TM16xx driver infrastructure. - -Signed-off-by: Jean-Francois Lessard ---- - drivers/auxdisplay/Kconfig | 16 + - drivers/auxdisplay/Makefile | 1 + - drivers/auxdisplay/tm16xx_i2c.c | 332 ++++++++++ - 3 files changed, 349 insertions(+) - -diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/Kconfig -+++ b/drivers/auxdisplay/Kconfig -@@ -544,6 +544,22 @@ config TM16XX_KEYPAD - help - Enable keyscan support for TM16XX driver. - -+config TM16XX_I2C -+ tristate "TM16XX-compatible I2C 7-segment LED controllers with keyscan" -+ depends on I2C -+ select TM16XX -+ help -+ This driver supports the following TM16XX compatible -+ I2C (2-wire) 7-segment led display chips: -+ - Titanmec: TM1650 -+ - Fuda Hisi: FD650, FD655, FD6551 -+ - i-Core: AiP650 -+ - Winrise: HBS658 -+ -+ To compile this driver as a module, choose M here: the module -+ will be called tm16xx_i2c and you will also get tm16xx for the -+ core module. -+ - # - # Character LCD with non-conforming interface section - # -diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/Makefile -+++ b/drivers/auxdisplay/Makefile -@@ -19,3 +19,4 @@ 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 -+obj-$(CONFIG_TM16XX_I2C) += tm16xx_i2c.o -diff --git a/drivers/auxdisplay/tm16xx_i2c.c b/drivers/auxdisplay/tm16xx_i2c.c -new file mode 100644 -index 000000000000..111111111111 ---- /dev/null -+++ b/drivers/auxdisplay/tm16xx_i2c.c -@@ -0,0 +1,332 @@ -+// 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 -+#include -+#include -+#include -+#include -+ -+#include "tm16xx.h" -+ -+static int tm16xx_i2c_probe(struct i2c_client *client) -+{ -+ const struct tm16xx_controller *controller; -+ struct tm16xx_display *display; -+ int ret; -+ -+ controller = i2c_get_match_data(client); -+ if (!controller) -+ return -EINVAL; -+ -+ display = devm_kzalloc(&client->dev, sizeof(*display), GFP_KERNEL); -+ if (!display) -+ return -ENOMEM; -+ -+ display->dev = &client->dev; -+ display->controller = controller; -+ -+ i2c_set_clientdata(client, display); -+ -+ ret = tm16xx_probe(display); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static void tm16xx_i2c_remove(struct i2c_client *client) -+{ -+ struct tm16xx_display *display = i2c_get_clientdata(client); -+ -+ tm16xx_remove(display); -+} -+ -+/** -+ * tm16xx_i2c_write() - I2C write helper for controller -+ * @display: pointer to tm16xx_display structure -+ * @data: command and data bytes to send -+ * @len: number of bytes in @data -+ * -+ * Return: 0 on success, negative error code on failure -+ */ -+static int tm16xx_i2c_write(struct tm16xx_display *display, u8 *data, size_t len) -+{ -+ struct i2c_client *i2c = to_i2c_client(display->dev); -+ -+ /* expected sequence: S Command [A] Data [A] P */ -+ struct i2c_msg msg = { -+ .addr = data[0] >> 1, -+ .flags = 0, -+ .len = len - 1, -+ .buf = &data[1], -+ }; -+ int ret; -+ -+ ret = i2c_transfer(i2c->adapter, &msg, 1); -+ if (ret < 0) -+ return ret; -+ -+ return (ret == 1) ? 0 : -EIO; -+} -+ -+/** -+ * tm16xx_i2c_read() - I2C read helper for controller -+ * @display: pointer to tm16xx_display structure -+ * @cmd: command/address byte to send before reading -+ * @data: buffer to receive data -+ * @len: number of bytes to read into @data -+ * -+ * Return: 0 on success, negative error code on failure -+ */ -+static int tm16xx_i2c_read(struct tm16xx_display *display, u8 cmd, u8 *data, size_t len) -+{ -+ struct i2c_client *i2c = to_i2c_client(display->dev); -+ -+ /* expected sequence: S Command [A] [Data] [A] P */ -+ struct i2c_msg msgs[1] = {{ -+ .addr = cmd >> 1, -+ .flags = I2C_M_RD | I2C_M_NO_RD_ACK, -+ .len = len, -+ .buf = data, -+ }}; -+ int ret; -+ -+ ret = i2c_transfer(i2c->adapter, msgs, ARRAY_SIZE(msgs)); -+ if (ret < 0) -+ return ret; -+ -+ return (ret == ARRAY_SIZE(msgs)) ? 0 : -EIO; -+} -+ -+/* I2C controller-specific functions */ -+static int tm1650_init(struct tm16xx_display *display) -+{ -+ const enum led_brightness brightness = display->main_led.brightness; -+ u8 cmds[2]; -+ -+ cmds[0] = TM1650_CMD_CTRL; -+ cmds[1] = TM16XX_CTRL_BRIGHTNESS(brightness, brightness, TM1650) | -+ TM1650_CTRL_SEG8_MODE; -+ -+ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); -+} -+ -+static int tm1650_data(struct tm16xx_display *display, u8 index, -+ unsigned int grid) -+{ -+ u8 cmds[2]; -+ -+ cmds[0] = TM1650_CMD_ADDR + index * 2; -+ cmds[1] = grid; /* SEG 1 to 8 */ -+ -+ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); -+} -+ -+static int tm1650_keys(struct tm16xx_display *display) -+{ -+ int row, col; -+ bool pressed; -+ u8 keycode; -+ int ret; -+ -+ ret = tm16xx_i2c_read(display, TM1650_CMD_READ, &keycode, 1); -+ if (ret) -+ return ret; -+ -+ if (keycode == 0x00 || keycode == 0xFF) -+ return -EINVAL; -+ -+ row = FIELD_GET(TM1650_KEY_ROW_MASK, keycode); -+ pressed = FIELD_GET(TM1650_KEY_DOWN_MASK, keycode) != 0; -+ if ((keycode & TM1650_KEY_COMBINED) == TM1650_KEY_COMBINED) { -+ tm16xx_set_key(display, row, 0, pressed); -+ tm16xx_set_key(display, row, 1, pressed); -+ } else { -+ col = FIELD_GET(TM1650_KEY_COL_MASK, keycode); -+ tm16xx_set_key(display, row, col, pressed); -+ } -+ -+ return 0; -+} -+ -+static int fd655_init(struct tm16xx_display *display) -+{ -+ const enum led_brightness brightness = display->main_led.brightness; -+ u8 cmds[2]; -+ -+ cmds[0] = FD655_CMD_CTRL; -+ cmds[1] = TM16XX_CTRL_BRIGHTNESS(brightness, brightness % 3, FD655); -+ -+ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); -+} -+ -+static int fd655_data(struct tm16xx_display *display, u8 index, -+ unsigned int grid) -+{ -+ u8 cmds[2]; -+ -+ cmds[0] = FD655_CMD_ADDR + index * 2; -+ cmds[1] = grid; /* SEG 1 to 8 */ -+ -+ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); -+} -+ -+static int fd6551_init(struct tm16xx_display *display) -+{ -+ const enum led_brightness brightness = display->main_led.brightness; -+ u8 cmds[2]; -+ -+ cmds[0] = FD6551_CMD_CTRL; -+ cmds[1] = TM16XX_CTRL_BRIGHTNESS(brightness, ~(brightness - 1), FD6551); -+ -+ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); -+} -+ -+static void hbs658_swap_nibbles(u8 *data, size_t len) -+{ -+ for (size_t i = 0; i < len; i++) -+ data[i] = (data[i] << 4) | (data[i] >> 4); -+} -+ -+static int hbs658_init(struct tm16xx_display *display) -+{ -+ const enum led_brightness brightness = display->main_led.brightness; -+ u8 cmd; -+ int ret; -+ -+ /* Set data command */ -+ cmd = TM16XX_CMD_WRITE | TM16XX_DATA_ADDR_AUTO; -+ hbs658_swap_nibbles(&cmd, 1); -+ ret = tm16xx_i2c_write(display, &cmd, 1); -+ if (ret) -+ return ret; -+ -+ /* Set control command with brightness */ -+ cmd = TM16XX_CMD_CTRL | -+ TM16XX_CTRL_BRIGHTNESS(brightness, brightness - 1, TM16XX); -+ hbs658_swap_nibbles(&cmd, 1); -+ ret = tm16xx_i2c_write(display, &cmd, 1); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int hbs658_data(struct tm16xx_display *display, u8 index, -+ unsigned int grid) -+{ -+ u8 cmds[2]; -+ -+ cmds[0] = TM16XX_CMD_ADDR + index * 2; -+ cmds[1] = grid; -+ -+ hbs658_swap_nibbles(cmds, ARRAY_SIZE(cmds)); -+ return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); -+} -+ -+static int hbs658_keys(struct tm16xx_display *display) -+{ -+ u8 cmd, keycode; -+ int col; -+ int ret; -+ -+ cmd = TM16XX_CMD_READ; -+ hbs658_swap_nibbles(&cmd, 1); -+ ret = tm16xx_i2c_read(display, cmd, &keycode, 1); -+ if (ret) -+ return ret; -+ -+ hbs658_swap_nibbles(&keycode, 1); -+ -+ if (keycode != 0xFF) { -+ col = FIELD_GET(HBS658_KEY_COL_MASK, keycode); -+ tm16xx_set_key(display, 0, col, true); -+ } -+ -+ return 0; -+} -+ -+/* I2C controller definitions */ -+static const struct tm16xx_controller tm1650_controller = { -+ .max_grids = 4, -+ .max_segments = 8, -+ .max_brightness = 8, -+ .max_key_rows = 4, -+ .max_key_cols = 7, -+ .init = tm1650_init, -+ .data = tm1650_data, -+ .keys = tm1650_keys, -+}; -+ -+static const struct tm16xx_controller fd655_controller = { -+ .max_grids = 5, -+ .max_segments = 7, -+ .max_brightness = 3, -+ .max_key_rows = 5, -+ .max_key_cols = 7, -+ .init = fd655_init, -+ .data = fd655_data, -+ .keys = tm1650_keys, -+}; -+ -+static const struct tm16xx_controller fd6551_controller = { -+ .max_grids = 5, -+ .max_segments = 7, -+ .max_brightness = 8, -+ .max_key_rows = 0, -+ .max_key_cols = 0, -+ .init = fd6551_init, -+ .data = fd655_data, -+}; -+ -+static const struct tm16xx_controller hbs658_controller = { -+ .max_grids = 5, -+ .max_segments = 8, -+ .max_brightness = 8, -+ .max_key_rows = 1, -+ .max_key_cols = 8, -+ .init = hbs658_init, -+ .data = hbs658_data, -+ .keys = hbs658_keys, -+}; -+ -+static const struct of_device_id tm16xx_i2c_of_match[] = { -+ { .compatible = "titanmec,tm1650", .data = &tm1650_controller }, -+ { .compatible = "fdhisi,fd6551", .data = &fd6551_controller }, -+ { .compatible = "fdhisi,fd655", .data = &fd655_controller }, -+ { .compatible = "winrise,hbs658", .data = &hbs658_controller }, -+ { /* sentinel */ } -+}; -+MODULE_DEVICE_TABLE(of, tm16xx_i2c_of_match); -+ -+static const struct i2c_device_id tm16xx_i2c_id[] = { -+ { "tm1650", (kernel_ulong_t)&tm1650_controller }, -+ { "fd6551", (kernel_ulong_t)&fd6551_controller }, -+ { "fd655", (kernel_ulong_t)&fd655_controller }, -+ { "hbs658", (kernel_ulong_t)&hbs658_controller }, -+ { /* sentinel */ } -+}; -+MODULE_DEVICE_TABLE(i2c, tm16xx_i2c_id); -+ -+static struct i2c_driver tm16xx_i2c_driver = { -+ .driver = { -+ .name = "tm16xx-i2c", -+ .of_match_table = tm16xx_i2c_of_match, -+ }, -+ .probe = tm16xx_i2c_probe, -+ .remove = tm16xx_i2c_remove, -+ .shutdown = tm16xx_i2c_remove, -+ .id_table = tm16xx_i2c_id, -+}; -+module_i2c_driver(tm16xx_i2c_driver); -+ -+MODULE_AUTHOR("Jean-François Lessard"); -+MODULE_DESCRIPTION("TM16xx-i2c LED Display Controllers"); -+MODULE_LICENSE("GPL"); -+MODULE_IMPORT_NS("TM16XX"); --- -Armbian - -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= -Date: Sat, 23 Aug 2025 21:55:14 -0400 -Subject: auxdisplay: TM16xx: Add support for SPI-based controllers - -Add support for TM16xx-compatible auxiliary display controllers connected -via the SPI bus. - -The implementation includes: -- SPI driver registration and initialization -- Probe/remove logic for SPI devices -- Controller-specific handling and communication sequences -- Integration with the TM16xx core driver for common functionality - -This allows platforms using TM16xx or compatible controllers over SPI to be -managed by the TM16xx driver infrastructure. - -Signed-off-by: Jean-Francois Lessard ---- - drivers/auxdisplay/Kconfig | 16 + - drivers/auxdisplay/Makefile | 1 + - drivers/auxdisplay/tm16xx_spi.c | 397 ++++++++++ - 3 files changed, 414 insertions(+) - -diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/Kconfig -+++ b/drivers/auxdisplay/Kconfig -@@ -560,6 +560,22 @@ config TM16XX_I2C - will be called tm16xx_i2c and you will also get tm16xx for the - core module. - -+config TM16XX_SPI -+ tristate "TM16XX-compatible SPI 7-segment LED controllers with keyscan" -+ depends on SPI -+ select TM16XX -+ help -+ This driver supports the following TM16XX compatible -+ SPI (3-wire) 7-segment led display chips: -+ - Titanmec: TM1618, TM1620, TM1628, TM1638 -+ - Fuda Hisi: FD620, FD628 -+ - i-Core: AiP1618, AiP1628 -+ - Princeton: PT6964 -+ -+ To compile this driver as a module, choose M here: the module -+ will be called tm16xx_spi and you will also get tm16xx for the -+ core module. -+ - # - # Character LCD with non-conforming interface section - # -diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile -index 111111111111..222222222222 100644 ---- a/drivers/auxdisplay/Makefile -+++ b/drivers/auxdisplay/Makefile -@@ -20,3 +20,4 @@ obj-$(CONFIG_TM16XX) += tm16xx.o - tm16xx-y += tm16xx_core.o - tm16xx-$(CONFIG_TM16XX_KEYPAD) += tm16xx_keypad.o - obj-$(CONFIG_TM16XX_I2C) += tm16xx_i2c.o -+obj-$(CONFIG_TM16XX_SPI) += tm16xx_spi.o -diff --git a/drivers/auxdisplay/tm16xx_spi.c b/drivers/auxdisplay/tm16xx_spi.c -new file mode 100644 -index 000000000000..111111111111 ---- /dev/null -+++ b/drivers/auxdisplay/tm16xx_spi.c -@@ -0,0 +1,397 @@ -+// 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 -+#include -+#include -+#include -+#include -+ -+#include "tm16xx.h" -+ -+#define TM16XX_SPI_BUFFER_SIZE 8 -+#define TM16XX_SPI_TWAIT_US 2 -+ -+static int tm16xx_spi_probe(struct spi_device *spi) -+{ -+ const struct tm16xx_controller *controller; -+ struct tm16xx_display *display; -+ int ret; -+ -+ controller = spi_get_device_match_data(spi); -+ if (!controller) -+ return -EINVAL; -+ -+ display = devm_kzalloc(&spi->dev, sizeof(*display), GFP_KERNEL); -+ if (!display) -+ return -ENOMEM; -+ -+ /* Allocate DMA-safe buffer */ -+ display->spi_buffer = devm_kzalloc(&spi->dev, TM16XX_SPI_BUFFER_SIZE, GFP_KERNEL); -+ if (!display->spi_buffer) -+ return -ENOMEM; -+ -+ display->dev = &spi->dev; -+ display->controller = controller; -+ -+ spi_set_drvdata(spi, display); -+ -+ ret = tm16xx_probe(display); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static void tm16xx_spi_remove(struct spi_device *spi) -+{ -+ struct tm16xx_display *display = spi_get_drvdata(spi); -+ -+ tm16xx_remove(display); -+} -+ -+/** -+ * tm16xx_spi_read() - SPI read helper for controller -+ * @display: pointer to tm16xx_display -+ * @cmd: command to send -+ * @cmd_len: length of command -+ * @data: buffer for received data -+ * @data_len: length of data to read -+ * -+ * Return: 0 on success, negative error code on failure -+ */ -+static int tm16xx_spi_read(struct tm16xx_display *display, u8 *cmd, -+ size_t cmd_len, u8 *data, size_t data_len) -+{ -+ struct spi_device *spi = to_spi_device(display->dev); -+ struct spi_message msg; -+ int ret; -+ -+ /* If STB is high during transmission, command is invalid. -+ * Reading requires a minimum 2 microseconds wait (Twait) -+ * after the 8th CLK rising edge before reading on falling edge. -+ */ -+ struct spi_transfer xfers[2] = { -+ { -+ .tx_buf = cmd, -+ .len = cmd_len, -+ .cs_change = 0, /* NO CS toggle */ -+ .delay.value = TM16XX_SPI_TWAIT_US, -+ .delay.unit = SPI_DELAY_UNIT_USECS, -+ }, { -+ .rx_buf = data, -+ .len = data_len, -+ } -+ }; -+ -+ spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers)); -+ -+ ret = spi_sync(spi, &msg); -+ -+ return ret; -+} -+ -+/** -+ * tm16xx_spi_write() - SPI write helper for controller -+ * @display: pointer to tm16xx_display -+ * @data: data to write -+ * @len: number of bytes to write -+ * -+ * Return: 0 on success, negative error code on failure -+ */ -+static int tm16xx_spi_write(struct tm16xx_display *display, u8 *data, size_t len) -+{ -+ struct spi_device *spi = to_spi_device(display->dev); -+ -+ return spi_write(spi, data, len); -+} -+ -+/* SPI controller-specific functions */ -+static int tm1628_init(struct tm16xx_display *display) -+{ -+ const enum led_brightness brightness = display->main_led.brightness; -+ const u8 num_hwgrid = display->num_hwgrid; -+ u8 *cmd = display->spi_buffer; -+ int ret; -+ -+ /* Set mode command based on grid count */ -+ cmd[0] = TM16XX_CMD_MODE; -+ if (num_hwgrid <= 4) -+ cmd[0] |= TM16XX_MODE_4GRIDS; -+ else if (num_hwgrid == 5) -+ cmd[0] |= TM16XX_MODE_5GRIDS; -+ else if (num_hwgrid == 6) -+ cmd[0] |= TM16XX_MODE_6GRIDS; -+ else -+ cmd[0] |= TM16XX_MODE_7GRIDS; -+ -+ ret = tm16xx_spi_write(display, cmd, 1); -+ if (ret) -+ return ret; -+ -+ /* Set data command */ -+ cmd[0] = TM16XX_CMD_WRITE | TM16XX_DATA_ADDR_AUTO; -+ ret = tm16xx_spi_write(display, cmd, 1); -+ if (ret) -+ return ret; -+ -+ /* Set control command with brightness */ -+ cmd[0] = TM16XX_CMD_CTRL | -+ TM16XX_CTRL_BRIGHTNESS(brightness, brightness - 1, TM16XX); -+ ret = tm16xx_spi_write(display, cmd, 1); -+ if (ret) -+ return ret; -+ -+ return 0; -+} -+ -+static int tm1618_data(struct tm16xx_display *display, u8 index, -+ unsigned int grid) -+{ -+ u8 *cmd = display->spi_buffer; -+ -+ cmd[0] = TM16XX_CMD_ADDR + index * 2; -+ cmd[1] = FIELD_GET(TM1618_BYTE1_MASK, grid); -+ cmd[2] = FIELD_GET(TM1618_BYTE2_MASK, grid) << TM1618_BYTE2_SHIFT; -+ -+ return tm16xx_spi_write(display, cmd, 3); -+} -+ -+static int tm1628_data(struct tm16xx_display *display, u8 index, -+ unsigned int grid) -+{ -+ u8 *cmd = display->spi_buffer; -+ -+ cmd[0] = TM16XX_CMD_ADDR + index * 2; -+ cmd[1] = FIELD_GET(TM1628_BYTE1_MASK, grid); -+ cmd[2] = FIELD_GET(TM1628_BYTE2_MASK, grid); -+ -+ return tm16xx_spi_write(display, cmd, 3); -+} -+ -+static int tm1628_keys(struct tm16xx_display *display) -+{ -+ u8 *cmd = display->spi_buffer; -+ u8 *codes = display->spi_buffer; -+ unsigned int i; -+ int bit, byte; -+ bool value; -+ int ret; -+ -+ cmd[0] = TM16XX_CMD_READ; -+ ret = tm16xx_spi_read(display, cmd, 1, codes, TM1628_KEY_READ_LEN); -+ if (ret) -+ return ret; -+ -+ /* prevent false readings */ -+ for (i = 0; i < TM1628_KEY_READ_LEN; i++) { -+ if (codes[i] & ~TM1628_KEY_MASK) -+ return -EINVAL; -+ } -+ -+ tm16xx_for_each_key(display, row, col) { -+ byte = col >> 1; -+ bit = row + ((col & 1) * 3); -+ value = !!(codes[byte] & BIT(bit)); -+ -+ tm16xx_set_key(display, row, col, value); -+ } -+ -+ return 0; -+} -+ -+static int tm1638_keys(struct tm16xx_display *display) -+{ -+ u8 *cmd = display->spi_buffer; -+ u8 *codes = display->spi_buffer; -+ unsigned int i; -+ int bit, byte; -+ bool value; -+ int ret; -+ -+ cmd[0] = TM16XX_CMD_READ; -+ ret = tm16xx_spi_read(display, cmd, 1, codes, TM1638_KEY_READ_LEN); -+ if (ret) -+ return ret; -+ -+ /* prevent false readings */ -+ for (i = 0; i < TM1638_KEY_READ_LEN; i++) { -+ if (codes[i] & ~TM1638_KEY_MASK) -+ return -EINVAL; -+ } -+ -+ tm16xx_for_each_key(display, row, col) { -+ byte = col >> 1; -+ bit = (2 - row) + ((col & 1) << 2); -+ value = !!(codes[byte] & BIT(bit)); -+ -+ tm16xx_set_key(display, row, col, value); -+ } -+ -+ return 0; -+} -+ -+static int tm1618_keys(struct tm16xx_display *display) -+{ -+ u8 *cmd = display->spi_buffer; -+ u8 *codes = display->spi_buffer; -+ unsigned int i; -+ int ret; -+ -+ cmd[0] = TM16XX_CMD_READ; -+ ret = tm16xx_spi_read(display, cmd, 1, codes, TM1618_KEY_READ_LEN); -+ if (ret) -+ return ret; -+ -+ /* prevent false readings */ -+ for (i = 0; i < TM1618_KEY_READ_LEN; i++) { -+ if (codes[i] & ~TM1618_KEY_MASK) -+ return -EINVAL; -+ } -+ -+ tm16xx_set_key(display, 0, 0, !!(codes[0] & BIT(1))); -+ tm16xx_set_key(display, 0, 1, !!(codes[0] & BIT(4))); -+ tm16xx_set_key(display, 0, 2, !!(codes[1] & BIT(1))); -+ tm16xx_set_key(display, 0, 3, !!(codes[1] & BIT(4))); -+ tm16xx_set_key(display, 0, 4, !!(codes[2] & BIT(1))); -+ -+ return 0; -+} -+ -+static int fd620_data(struct tm16xx_display *display, u8 index, -+ unsigned int grid) -+{ -+ u8 *cmd = display->spi_buffer; -+ -+ cmd[0] = TM16XX_CMD_ADDR + index * 2; -+ cmd[1] = FIELD_GET(FD620_BYTE1_MASK, grid); -+ cmd[2] = FIELD_GET(FD620_BYTE2_MASK, grid) << FD620_BYTE2_SHIFT; -+ -+ return tm16xx_spi_write(display, cmd, 3); -+} -+ -+static int fd620_keys(struct tm16xx_display *display) -+{ -+ u8 *cmd = display->spi_buffer; -+ u8 *codes = display->spi_buffer; -+ unsigned int i; -+ int ret; -+ -+ cmd[0] = TM16XX_CMD_READ; -+ ret = tm16xx_spi_read(display, cmd, 1, codes, FD620_KEY_READ_LEN); -+ if (ret) -+ return ret; -+ -+ /* prevent false readings */ -+ for (i = 0; i < FD620_KEY_READ_LEN; i++) { -+ if (codes[i] & ~FD620_KEY_MASK) -+ return -EINVAL; -+ } -+ -+ tm16xx_set_key(display, 0, 0, codes[0] & BIT(0)); -+ tm16xx_set_key(display, 0, 1, codes[0] & BIT(3)); -+ tm16xx_set_key(display, 0, 2, codes[1] & BIT(0)); -+ tm16xx_set_key(display, 0, 3, codes[1] & BIT(3)); -+ tm16xx_set_key(display, 0, 4, codes[2] & BIT(0)); -+ tm16xx_set_key(display, 0, 5, codes[2] & BIT(3)); -+ tm16xx_set_key(display, 0, 6, codes[3] & BIT(0)); -+ -+ return 0; -+} -+ -+/* SPI controller definitions */ -+static const struct tm16xx_controller tm1618_controller = { -+ .max_grids = 7, -+ .max_segments = 8, -+ .max_brightness = 8, -+ .max_key_rows = 1, -+ .max_key_cols = 5, -+ .init = tm1628_init, -+ .data = tm1618_data, -+ .keys = tm1618_keys, -+}; -+ -+static const struct tm16xx_controller tm1620_controller = { -+ .max_grids = 6, -+ .max_segments = 10, -+ .max_brightness = 8, -+ .max_key_rows = 0, -+ .max_key_cols = 0, -+ .init = tm1628_init, -+ .data = tm1628_data, -+}; -+ -+static const struct tm16xx_controller tm1628_controller = { -+ .max_grids = 7, -+ .max_segments = 14, /* seg 11 unused */ -+ .max_brightness = 8, -+ .max_key_rows = 2, -+ .max_key_cols = 10, -+ .init = tm1628_init, -+ .data = tm1628_data, -+ .keys = tm1628_keys, -+}; -+ -+static const struct tm16xx_controller tm1638_controller = { -+ .max_grids = 8, -+ .max_segments = 10, -+ .max_brightness = 8, -+ .max_key_rows = 3, -+ .max_key_cols = 8, -+ .init = tm1628_init, -+ .data = tm1628_data, -+ .keys = tm1638_keys, -+}; -+ -+static const struct tm16xx_controller fd620_controller = { -+ .max_grids = 5, -+ .max_segments = 8, -+ .max_brightness = 8, -+ .max_key_rows = 1, -+ .max_key_cols = 7, -+ .init = tm1628_init, -+ .data = fd620_data, -+ .keys = fd620_keys, -+}; -+ -+static const struct of_device_id tm16xx_spi_of_match[] = { -+ { .compatible = "titanmec,tm1618", .data = &tm1618_controller }, -+ { .compatible = "titanmec,tm1620", .data = &tm1620_controller }, -+ { .compatible = "titanmec,tm1628", .data = &tm1628_controller }, -+ { .compatible = "titanmec,tm1638", .data = &tm1638_controller }, -+ { .compatible = "fdhisi,fd620", .data = &fd620_controller }, -+ { /* sentinel */ } -+}; -+MODULE_DEVICE_TABLE(of, tm16xx_spi_of_match); -+ -+static const struct spi_device_id tm16xx_spi_id[] = { -+ { "tm1618", (kernel_ulong_t)&tm1618_controller }, -+ { "tm1620", (kernel_ulong_t)&tm1620_controller }, -+ { "tm1628", (kernel_ulong_t)&tm1628_controller }, -+ { "tm1638", (kernel_ulong_t)&tm1638_controller }, -+ { "fd620", (kernel_ulong_t)&fd620_controller }, -+ { /* sentinel */ } -+}; -+MODULE_DEVICE_TABLE(spi, tm16xx_spi_id); -+ -+static struct spi_driver tm16xx_spi_driver = { -+ .driver = { -+ .name = "tm16xx-spi", -+ .of_match_table = tm16xx_spi_of_match, -+ }, -+ .probe = tm16xx_spi_probe, -+ .remove = tm16xx_spi_remove, -+ .shutdown = tm16xx_spi_remove, -+ .id_table = tm16xx_spi_id, -+}; -+module_spi_driver(tm16xx_spi_driver); -+ -+MODULE_AUTHOR("Jean-François Lessard"); -+MODULE_DESCRIPTION("TM16xx-spi LED Display Controllers"); -+MODULE_LICENSE("GPL"); -+MODULE_IMPORT_NS("TM16XX"); --- -Armbian -