From nobody Tue Feb 10 12:40:28 2026 Received: from mail-pf1-f179.google.com (mail-pf1-f179.google.com [209.85.210.179]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE62D34F26F for ; Tue, 13 Jan 2026 04:03:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768277023; cv=none; b=EuR+8TZ6/hND1FNxpcLdvDtrVKByZZWuVERRD6KwG6G7hR+MSkZPdNb3dIYWgXD2y41F/75QHnRaHz92zpZtqfU8kjzfrciWfRHkGbfEAzn2K1O9AXeILxMHfYcvvUYIGPaSBx2XUVOlcy7nE9pgwnvn769V/GvMZwEu22WpZ00= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768277023; c=relaxed/simple; bh=iMd9GL/2XUKG9j4Iduimcqhz68DwGmVOF34m+VrDYLE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NRobG7k1+dcjAOOiD5zb9o6KapovGfhCGq1AZh/fCxmeC6AcwaYLsCz1AfQ+jUAu+AJaur91UvHCXcobnzH/yAUechaahtIDYh/Sf3zxU5X+/WQUSrClCHy5XS07VgcV2lgHcHC21XOWzgj0g5zPezHh9W7iHdtLerwUnz6wCNs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sirat.me; spf=pass smtp.mailfrom=gmail.com; arc=none smtp.client-ip=209.85.210.179 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=sirat.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Received: by mail-pf1-f179.google.com with SMTP id d2e1a72fcca58-81f4f4d4822so850338b3a.3 for ; Mon, 12 Jan 2026 20:03:37 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768277017; x=1768881817; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=ci7K6yNOqrdNSBFsOIy5Tf+/ZTimH+SlCTUY32nt1Bs=; b=Drwozol2Jm27RMHXycAlgSTqYsP+xFiUgPgrfL0OgEKbX0zG+sbQIdAxk3vRZXY4NL ADaKsek8i9Bceg0vfQzdquu3erDu8Mla5TCHVqM/doPRUWgdiiQ6CySPx/d3+Uyg5Rff zNlTwYPvAdsO/qb9YOLZbbFarWYTiRIydT1GJqZ6CsDAeLsPr/5oXp3/TeYuXdDfazd1 C/Umm6QK9dyO2s+Yi10OI8Pq3OLZfJStCCqcLpZkHdyFG+Ewn7yRXK6DHoeKGcpUU27T KUid/pv2qXNHCMecoWE7QgkQXa5E3ayayeXYFxN+6bIbRkKaFx5aMj5HeYZUGzjpha69 CA9w== X-Gm-Message-State: AOJu0Yy+GBvmKg1RvNBRL6R1xRDq3tZz96iPLLK6pR0tNis1HStuckf2 7SNBh/2ytuvpzXJEkNtMlGct6cMT5hmePlgwXu8ZztyIumvDcnaaMJyG X-Gm-Gg: AY/fxX4XKDfBbgccsBR5TOhB8i1NbvCpV99dH4trirjfQwni7CVRGdQ5gCyQEEBT6kY edY4EZ7kPW3LoJKlU6VEM/LnNcRSi4NEDApFCU2492fQ/l2D/aOS1jUyqSY80E4zgSh4qDqKrFG y8qXjt9sssmzRoPgipOoNRNhw2B2cHXDunWFuyhWOjRSXYSPxI0NXne7x38HyvUpaeqJE4wPxhN JyX+HMuiZlvFZ4y9gYBVddI9I1CEOuUsIoAdOOOMAclSeJViRt9qQZl6H5YRb1QRQvTS+Klya+r +giENEOxlFJDb71k2DruHwKPxPS7vVB0FrkDVRxzCp5tSKBQCaVqZ+JYrVwjp6WLPr4MlBI9y0c wQW3webSFtbFL3BuaMDv94iclOyousWMD44BWyO/NwfodE0KIk/tihVeLBXXbqhmcYYepqfTfA6 DsiQ5ayG0jnGhI2HggupituBo= X-Google-Smtp-Source: AGHT+IE7qK7U4JJNovFRHQ32/LZXLKq933uf4WmVu5S9jXshh5wylEPkN1qu2iQgq/r4UDnx1ghbQw== X-Received: by 2002:a05:6a00:1d1d:b0:81f:3fbd:ccf with SMTP id d2e1a72fcca58-81f3fbd19f4mr5980432b3a.23.1768277016789; Mon, 12 Jan 2026 20:03:36 -0800 (PST) Received: from DESKTOP-LUHV3PD.localdomain ([59.152.111.50]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-81f3ce8df8bsm7776016b3a.40.2026.01.12.20.03.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 12 Jan 2026 20:03:36 -0800 (PST) From: Siratul Islam To: andy@kernel.org, geert@linux-m68k.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org Cc: linux-kernel@vger.kernel.org, devicetree@vger.kernel.org, Siratul Islam Subject: [PATCH 3/4] auxdisplay: tm1637: Add driver for TM1637 Date: Tue, 13 Jan 2026 10:02:41 +0600 Message-ID: <20260113040242.19156-4-email@sirat.me> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260113040242.19156-1-email@sirat.me> References: <20260113040242.19156-1-email@sirat.me> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- .../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/Documentatio= n/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 +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 +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. =20 +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) +=3D line-display.o obj-$(CONFIG_MAX6959) +=3D max6959.o obj-$(CONFIG_PARPORT_PANEL) +=3D panel.o obj-$(CONFIG_SEG_LED_GPIO) +=3D seg-led-gpio.o +obj-$(CONFIG_TM1637) +=3D 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 =3D 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 =3D !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 =3D 0; i < TM1637_DIGITS; i++) + tm1637_write_byte(tm, tm->buf[i]); + tm1637_stop(tm); + + if (tm->brightness =3D=3D 0) + ctrl_cmd =3D 0; + else + ctrl_cmd =3D TM1637_DISPLAY_ON | ((tm->brightness - 1) & TM1637_BRIGHTNE= SS_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 *a= ttr, char *buf) +{ + struct tm1637 *tm =3D dev_get_drvdata(dev); + int i, pos =3D 0; + + mutex_lock(&tm->lock); + for (i =3D 0; i < TM1637_DIGITS; i++) { + pos +=3D sysfs_emit_at(buf, pos, "0x%02x", tm->buf[i]); + if (i < TM1637_DIGITS - 1) + pos +=3D sysfs_emit_at(buf, pos, " "); + } + pos +=3D 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 =3D dev_get_drvdata(dev); + size_t i, pos =3D 0; + size_t len; + u8 segment_data[TM1637_DIGITS] =3D {0}; + + len =3D count; + if (len > 0 && buf[len - 1] =3D=3D '\n') + len--; + + for (i =3D 0; i < len && pos < TM1637_DIGITS; i++) { + char c =3D buf[i]; + + if (c =3D=3D '.') + continue; + + segment_data[pos] =3D map_to_seg7(&initial_map, c); + + if (i + 1 < len && buf[i + 1] =3D=3D '.') + segment_data[pos] |=3D 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 =3D dev_get_drvdata(dev); + unsigned int brightness; + + mutex_lock(&tm->lock); + brightness =3D tm->brightness; + mutex_unlock(&tm->lock); + + return sysfs_emit(buf, "%u\n", brightness); +} + +static ssize_t brightness_store(struct device *dev, struct device_attribut= e *attr, + const char *buf, size_t count) +{ + struct tm1637 *tm =3D dev_get_drvdata(dev); + unsigned int brightness; + int ret; + + ret =3D kstrtouint(buf, 10, &brightness); + if (ret) + return ret; + + if (brightness > TM1637_BRIGHTNESS_MAX + 1) + brightness =3D TM1637_BRIGHTNESS_MAX + 1; + + mutex_lock(&tm->lock); + if (tm->brightness !=3D brightness) { + tm->brightness =3D brightness; + tm1637_update_display_locked(tm); + } + mutex_unlock(&tm->lock); + + return count; +} +static DEVICE_ATTR_RW(brightness); + +static struct attribute *tm1637_attrs[] =3D { + &dev_attr_message.attr, + &dev_attr_brightness.attr, + NULL}; +ATTRIBUTE_GROUPS(tm1637); + +static int tm1637_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct tm1637 *tm; + + tm =3D devm_kzalloc(dev, sizeof(*tm), GFP_KERNEL); + if (!tm) + return -ENOMEM; + + tm->dev =3D dev; + + tm->clk =3D 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 =3D 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 =3D 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 =3D platform_get_drvdata(pdev); + + mutex_lock(&tm->lock); + tm->brightness =3D 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[] =3D { + {.compatible =3D "titanmec,tm1637"}, + {}}; +MODULE_DEVICE_TABLE(of, tm1637_of_match); + +static struct platform_driver tm1637_driver =3D { + .probe =3D tm1637_probe, + .remove =3D tm1637_remove, + .driver =3D { + .name =3D "tm1637", + .of_match_table =3D tm1637_of_match, + .dev_groups =3D tm1637_groups, + }, +}; +module_platform_driver(tm1637_driver); + +MODULE_DESCRIPTION("TM1637 7-segment display driver"); +MODULE_AUTHOR("Siratul Islam "); +MODULE_LICENSE("Dual BSD/GPL"); --=20 2.47.3