lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20260113040242.19156-4-email@sirat.me>
Date: Tue, 13 Jan 2026 10:02:41 +0600
From: Siratul Islam <email@...at.me>
To: andy@...nel.org,
	geert@...ux-m68k.org,
	robh@...nel.org,
	krzk+dt@...nel.org,
	conor+dt@...nel.org
Cc: linux-kernel@...r.kernel.org,
	devicetree@...r.kernel.org,
	Siratul Islam <email@...at.me>
Subject: [PATCH 3/4] auxdisplay: tm1637: Add driver for TM1637

Add a driver for the Titanmec TM1637 7-segment display controller.

The TM1637 uses a custom two-wire protocol (similar to I2C but without
a slave address) which requires bit-banging via GPIOs. This driver
implements the protocol manually as it does not fit the standard I2C
subsystem.

The driver exposes a character device interface via sysfs:
- 'message': Write a string to display (supports alphanumeric & dots).
- 'brightness': Control intensity (0-8).

Standard 7-segment mapping is handled using the kernel's map_to_7segment
utility.

Signed-off-by: Siratul Islam <email@...at.me>
---
 .../ABI/testing/sysfs-platform-tm1637         |  20 ++
 drivers/auxdisplay/Kconfig                    |  11 +
 drivers/auxdisplay/Makefile                   |   1 +
 drivers/auxdisplay/tm1637.c                   | 297 ++++++++++++++++++
 4 files changed, 329 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-platform-tm1637
 create mode 100644 drivers/auxdisplay/tm1637.c

diff --git a/Documentation/ABI/testing/sysfs-platform-tm1637 b/Documentation/ABI/testing/sysfs-platform-tm1637
new file mode 100644
index 000000000000..4941fd15518d
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-tm1637
@@ -0,0 +1,20 @@
+What:		/sys/bus/platform/devices/.../message
+Date:		January 2026
+KernelVersion:	6.19
+Contact:	Siratul Islam <email@...at.me>
+Description:
+		Write a text string to display on the 7-segment display.
+		Supports alphanumeric characters and decimal points.
+		A decimal point can be added after any character by
+		following it with a dot (e.g., "12.34").
+
+		Reading returns the current segment data in hex format.
+
+What:		/sys/bus/platform/devices/.../brightness
+Date:		January 2026
+KernelVersion:	6.19
+Contact:	Siratul Islam <email@...at.me>
+Description:
+		Control display brightness. Valid range is 0-8.
+		Writing 0 turns off the display.
+		Writing 1-8 sets brightness levels, with 8 being maximum.
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index bedc6133f970..1450591a0a25 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -526,6 +526,17 @@ config SEG_LED_GPIO
 	  This driver can also be built as a module. If so, the module
 	  will be called seg-led-gpio.
 
+config TM1637
+	tristate "Shenzhen Titan Micro Electronics TM1637 7-Segment Display"
+	depends on GPIOLIB || COMPILE_TEST
+	select AUXDISPLAY
+	help
+	  This driver provides support for the Titanmec TM1637 7-segment
+	  display controller connected via GPIO bit-banging.
+
+	  This driver exposes a character interface for controlling the
+	  display content and brightness via sysfs.
+
 #
 # Character LCD with non-conforming interface section
 #
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index f5c13ed1cd4f..5baa77da4343 100644
--- a/drivers/auxdisplay/Makefile
+++ b/drivers/auxdisplay/Makefile
@@ -16,3 +16,4 @@ 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_TM1637)		+= tm1637.o
diff --git a/drivers/auxdisplay/tm1637.c b/drivers/auxdisplay/tm1637.c
new file mode 100644
index 000000000000..f8623fea1d2a
--- /dev/null
+++ b/drivers/auxdisplay/tm1637.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+/*
+ * TM1637 7-segment display driver
+ *
+ * Copyright (C) 2026 Siratul Islam <email@...at.me>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/map_to_7segment.h>
+
+/* Commands */
+#define TM1637_CMD_DATA_AUTO_INC 0x40
+#define TM1637_CMD_ADDR_BASE 0xC0
+#define TM1637_CMD_DISPLAY_CTRL 0x80
+
+/* Display control bits */
+#define TM1637_DISPLAY_ON BIT(3)
+#define TM1637_BRIGHTNESS_MASK GENMASK(2, 0)
+#define TM1637_BRIGHTNESS_MAX 7
+
+#define TM1637_SEG_DP BIT(7)
+
+/* Protocol timing */
+#define TM1637_BIT_DELAY_MIN 100
+#define TM1637_BIT_DELAY_MAX 120
+
+#define TM1637_DIGITS 4
+
+struct tm1637 {
+	struct device *dev;
+	struct gpio_desc *clk;
+	struct gpio_desc *dio;
+	struct mutex lock; /* Protects display buffer and brightness */
+	u8 brightness;
+	u8 buf[TM1637_DIGITS];
+};
+
+/* Defines a static const 'initial_map' variable */
+static const SEG7_DEFAULT_MAP(initial_map);
+
+static void tm1637_delay(void)
+{
+	usleep_range(TM1637_BIT_DELAY_MIN, TM1637_BIT_DELAY_MAX);
+}
+
+static void tm1637_start(struct tm1637 *tm)
+{
+	gpiod_direction_output(tm->dio, 1);
+	gpiod_set_value(tm->clk, 1);
+	tm1637_delay();
+	gpiod_set_value(tm->dio, 0);
+	tm1637_delay();
+	gpiod_set_value(tm->clk, 0);
+	tm1637_delay();
+}
+
+static void tm1637_stop(struct tm1637 *tm)
+{
+	gpiod_direction_output(tm->dio, 0);
+	gpiod_set_value(tm->clk, 1);
+	tm1637_delay();
+	gpiod_set_value(tm->dio, 1);
+	tm1637_delay();
+}
+
+static bool tm1637_write_byte(struct tm1637 *tm, u8 data)
+{
+	bool ack;
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		gpiod_set_value(tm->clk, 0);
+		tm1637_delay();
+
+		if (data & BIT(i))
+			gpiod_direction_input(tm->dio);
+		else
+			gpiod_direction_output(tm->dio, 0);
+
+		tm1637_delay();
+		gpiod_set_value(tm->clk, 1);
+		tm1637_delay();
+	}
+
+	gpiod_set_value(tm->clk, 0);
+	gpiod_direction_input(tm->dio);
+	tm1637_delay();
+
+	gpiod_set_value(tm->clk, 1);
+	tm1637_delay();
+
+	ack = !gpiod_get_value(tm->dio);
+
+	if (!ack)
+		gpiod_direction_output(tm->dio, 0);
+
+	tm1637_delay();
+	gpiod_set_value(tm->clk, 0);
+
+	return ack;
+}
+
+static void tm1637_update_display_locked(struct tm1637 *tm)
+{
+	u8 ctrl_cmd;
+	int i;
+
+	tm1637_start(tm);
+	tm1637_write_byte(tm, TM1637_CMD_DATA_AUTO_INC);
+	tm1637_stop(tm);
+
+	tm1637_start(tm);
+	tm1637_write_byte(tm, TM1637_CMD_ADDR_BASE);
+	for (i = 0; i < TM1637_DIGITS; i++)
+		tm1637_write_byte(tm, tm->buf[i]);
+	tm1637_stop(tm);
+
+	if (tm->brightness == 0)
+		ctrl_cmd = 0;
+	else
+		ctrl_cmd = TM1637_DISPLAY_ON | ((tm->brightness - 1) & TM1637_BRIGHTNESS_MASK);
+
+	tm1637_start(tm);
+	tm1637_write_byte(tm, ctrl_cmd);
+	tm1637_stop(tm);
+}
+
+static void tm1637_update_display(struct tm1637 *tm)
+{
+	mutex_lock(&tm->lock);
+	tm1637_update_display_locked(tm);
+	mutex_unlock(&tm->lock);
+}
+
+static ssize_t message_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct tm1637 *tm = dev_get_drvdata(dev);
+	int i, pos = 0;
+
+	mutex_lock(&tm->lock);
+	for (i = 0; i < TM1637_DIGITS; i++) {
+		pos += sysfs_emit_at(buf, pos, "0x%02x", tm->buf[i]);
+		if (i < TM1637_DIGITS - 1)
+			pos += sysfs_emit_at(buf, pos, " ");
+	}
+	pos += sysfs_emit_at(buf, pos, "\n");
+	mutex_unlock(&tm->lock);
+
+	return pos;
+}
+
+static ssize_t message_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct tm1637 *tm = dev_get_drvdata(dev);
+	size_t i, pos = 0;
+	size_t len;
+	u8 segment_data[TM1637_DIGITS] = {0};
+
+	len = count;
+	if (len > 0 && buf[len - 1] == '\n')
+		len--;
+
+	for (i = 0; i < len && pos < TM1637_DIGITS; i++) {
+		char c = buf[i];
+
+		if (c == '.')
+			continue;
+
+		segment_data[pos] = map_to_seg7(&initial_map, c);
+
+		if (i + 1 < len && buf[i + 1] == '.')
+			segment_data[pos] |= TM1637_SEG_DP;
+
+		pos++;
+	}
+
+	mutex_lock(&tm->lock);
+	memcpy(tm->buf, segment_data, sizeof(tm->buf));
+	tm1637_update_display_locked(tm);
+	mutex_unlock(&tm->lock);
+
+	return count;
+}
+static DEVICE_ATTR_RW(message);
+
+static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct tm1637 *tm = dev_get_drvdata(dev);
+	unsigned int brightness;
+
+	mutex_lock(&tm->lock);
+	brightness = tm->brightness;
+	mutex_unlock(&tm->lock);
+
+	return sysfs_emit(buf, "%u\n", brightness);
+}
+
+static ssize_t brightness_store(struct device *dev, struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct tm1637 *tm = dev_get_drvdata(dev);
+	unsigned int brightness;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &brightness);
+	if (ret)
+		return ret;
+
+	if (brightness > TM1637_BRIGHTNESS_MAX + 1)
+		brightness = TM1637_BRIGHTNESS_MAX + 1;
+
+	mutex_lock(&tm->lock);
+	if (tm->brightness != brightness) {
+		tm->brightness = brightness;
+		tm1637_update_display_locked(tm);
+	}
+	mutex_unlock(&tm->lock);
+
+	return count;
+}
+static DEVICE_ATTR_RW(brightness);
+
+static struct attribute *tm1637_attrs[] = {
+	&dev_attr_message.attr,
+	&dev_attr_brightness.attr,
+	NULL};
+ATTRIBUTE_GROUPS(tm1637);
+
+static int tm1637_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct tm1637 *tm;
+
+	tm = devm_kzalloc(dev, sizeof(*tm), GFP_KERNEL);
+	if (!tm)
+		return -ENOMEM;
+
+	tm->dev = dev;
+
+	tm->clk = devm_gpiod_get(dev, "clk", GPIOD_OUT_LOW);
+	if (IS_ERR(tm->clk))
+		return dev_err_probe(dev, PTR_ERR(tm->clk), "Failed to get clk GPIO\n");
+
+	tm->dio = devm_gpiod_get(dev, "dio", GPIOD_OUT_LOW);
+	if (IS_ERR(tm->dio))
+		return dev_err_probe(dev, PTR_ERR(tm->dio), "Failed to get dio GPIO\n");
+
+	mutex_init(&tm->lock);
+
+	tm->brightness = TM1637_BRIGHTNESS_MAX + 1;
+
+	platform_set_drvdata(pdev, tm);
+	tm1637_update_display(tm);
+
+	return 0;
+}
+
+static void tm1637_remove(struct platform_device *pdev)
+{
+	struct tm1637 *tm = platform_get_drvdata(pdev);
+
+	mutex_lock(&tm->lock);
+	tm->brightness = 0;
+	memset(tm->buf, 0, sizeof(tm->buf));
+	tm1637_update_display_locked(tm);
+	mutex_unlock(&tm->lock);
+
+	mutex_destroy(&tm->lock);
+}
+
+static const struct of_device_id tm1637_of_match[] = {
+	{.compatible = "titanmec,tm1637"},
+	{}};
+MODULE_DEVICE_TABLE(of, tm1637_of_match);
+
+static struct platform_driver tm1637_driver = {
+	.probe = tm1637_probe,
+	.remove = tm1637_remove,
+	.driver = {
+		.name = "tm1637",
+		.of_match_table = tm1637_of_match,
+		.dev_groups = tm1637_groups,
+	},
+};
+module_platform_driver(tm1637_driver);
+
+MODULE_DESCRIPTION("TM1637 7-segment display driver");
+MODULE_AUTHOR("Siratul Islam <email@...at.me>");
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.47.3


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ