All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Jean-François Lessard" <jefflessard3@gmail.com>
To: Andy Shevchenko <andy@kernel.org>,
	Geert Uytterhoeven <geert@linux-m68k.org>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>
Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org,
	devicetree@vger.kernel.org
Subject: [PATCH v4 6/6] auxdisplay: TM16xx: Add support for SPI-based controllers
Date: Sun, 24 Aug 2025 23:32:32 -0400	[thread overview]
Message-ID: <20250825033237.60143-7-jefflessard3@gmail.com> (raw)
In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com>

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-François Lessard <jefflessard3@gmail.com>
---

Notes:
    include <linux/of.h> is required for of_match_ptr

 MAINTAINERS                     |   1 +
 drivers/auxdisplay/Kconfig      |   7 +
 drivers/auxdisplay/Makefile     |   1 +
 drivers/auxdisplay/tm16xx_spi.c | 398 ++++++++++++++++++++++++++++++++
 4 files changed, 407 insertions(+)
 create mode 100644 drivers/auxdisplay/tm16xx_spi.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 51cc910e2..1ee45be14 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -25411,6 +25411,7 @@ F:	drivers/auxdisplay/tm16xx.h
 F:	drivers/auxdisplay/tm16xx_core.c
 F:	drivers/auxdisplay/tm16xx_i2c.c
 F:	drivers/auxdisplay/tm16xx_keypad.c
+F:	drivers/auxdisplay/tm16xx_spi.c
 
 TMIO/SDHI MMC DRIVER
 M:	Wolfram Sang <wsa+renesas@sang-engineering.com>
diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig
index 6238e753d..868625596 100644
--- a/drivers/auxdisplay/Kconfig
+++ b/drivers/auxdisplay/Kconfig
@@ -536,6 +536,7 @@ config TM16XX
 	select INPUT_MATRIXKMAP
 	select TM16XX_KEYPAD if (INPUT)
 	select TM16XX_I2C if (I2C)
+	select TM16XX_SPI if (SPI)
 	help
 	  This driver supports the following TM16XX compatible
 	  I2C and SPI 7-segment led display chips:
@@ -560,6 +561,12 @@ config TM16XX_I2C
 	help
 	  Enable I2C support for TM16XX driver.
 
+config TM16XX_SPI
+	tristate
+	depends on TM16XX
+	help
+	  Enable SPI support for TM16XX driver.
+
 #
 # Character LCD with non-conforming interface section
 #
diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile
index ba7b310f5..2485a3a67 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 000000000..fcc69c5f6
--- /dev/null
+++ b/drivers/auxdisplay/tm16xx_spi.c
@@ -0,0 +1,398 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TM16xx and compatible LED display/keypad controller driver
+ * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips.
+ *
+ * Copyright (C) 2024 Jean-François Lessard
+ */
+
+#include <linux/mod_devicetable.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+#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->client.spi = spi;
+	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 = display->client.spi;
+	struct spi_message msg;
+	int ret;
+
+	dev_dbg(display->dev, "spi_write %*ph", (char)cmd_len, cmd);
+
+	/* 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);
+
+	dev_dbg(display->dev, "spi_read %*ph", (char)data_len, data);
+
+	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)
+{
+	dev_dbg(display->dev, "spi_write %*ph", (char)len, data);
+
+	struct spi_device *spi = display->client.spi;
+
+	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_grids = display->num_grids;
+	u8 *cmd = display->spi_buffer;
+	int ret;
+
+	/* Set mode command based on grid count */
+	cmd[0] = TM16XX_CMD_MODE;
+	if (num_grids <= 4)
+		cmd[0] |= TM16XX_MODE_4GRIDS;
+	else if (num_grids == 5)
+		cmd[0] |= TM16XX_MODE_5GRIDS;
+	else if (num_grids == 6)
+		cmd[0] |= TM16XX_MODE_6GRIDS;
+	else
+		cmd[0] |= TM16XX_MODE_7GRIDS;
+
+	ret = tm16xx_spi_write(display, cmd, 1);
+	if (ret < 0)
+		return ret;
+
+	/* Set data command */
+	cmd[0] = TM16XX_CMD_WRITE | TM16XX_DATA_ADDR_AUTO;
+	ret = tm16xx_spi_write(display, cmd, 1);
+	if (ret < 0)
+		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 < 0)
+		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;
+	int ret, i;
+
+	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) {
+		int byte = col >> 1;
+		int bit = row + ((col & 1) * 3);
+		bool 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;
+	int ret, i;
+
+	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) {
+		int byte = col >> 1;
+		int bit = (2 - row) + ((col & 1) << 2);
+		bool 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;
+	int ret, i;
+
+	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;
+	int ret, i;
+
+	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,
+	.keys = NULL,
+};
+
+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,
+};
+
+#if IS_ENABLED(CONFIG_OF)
+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);
+#endif
+
+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 = of_match_ptr(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");
-- 
2.43.0


  parent reply	other threads:[~2025-08-25  3:32 UTC|newest]

Thread overview: 30+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-25  3:32 [PATCH v4 0/6] auxdisplay: Add TM16xx 7-segment LED matrix display controllers driver Jean-François Lessard
2025-08-25  3:32 ` [PATCH v4 1/6] dt-bindings: vendor-prefixes: Add fdhisi, titanmec, princeton, winrise, wxicore Jean-François Lessard
2025-08-25 13:53   ` Andy Shevchenko
2025-08-26  2:57     ` Jean-François Lessard
2025-08-25  3:32 ` [PATCH v4 2/6] dt-bindings: auxdisplay: add Titan Micro Electronics TM16xx Jean-François Lessard
2025-08-25 18:26   ` Rob Herring
2025-08-26  1:33     ` Jean-François Lessard
2025-08-26 14:37       ` Jean-François Lessard
2025-08-29 15:26       ` Rob Herring
2025-08-29 16:26         ` Jean-François Lessard
2025-08-25 19:08   ` Per Larsson
2025-08-26  1:53     ` Jean-François Lessard
2025-08-25  3:32 ` [PATCH v4 3/6] auxdisplay: Add TM16xx 7-segment LED matrix display controllers driver Jean-François Lessard
2025-08-25 15:14   ` Andy Shevchenko
2025-08-25 17:48     ` Jean-François Lessard
2025-08-26 15:22       ` Andy Shevchenko
2025-08-26 20:44         ` Jean-François Lessard
2025-08-27 18:37       ` Jean-François Lessard
2025-09-01  6:04         ` Andy Shevchenko
2025-08-25  3:32 ` [PATCH v4 4/6] auxdisplay: TM16xx: Add keypad support for scanning matrix keys Jean-François Lessard
2025-08-25  3:32 ` [PATCH v4 5/6] auxdisplay: TM16xx: Add support for I2C-based controllers Jean-François Lessard
2025-08-25 15:18   ` Andy Shevchenko
2025-08-26  4:01     ` Jean-François Lessard
2025-08-26 15:30       ` Andy Shevchenko
2025-08-26 17:38         ` Jean-François Lessard
2025-08-26 18:26           ` Andy Shevchenko
2025-08-26 20:21             ` Jean-François Lessard
2025-08-25  3:32 ` Jean-François Lessard [this message]
2025-08-25 15:19   ` [PATCH v4 6/6] auxdisplay: TM16xx: Add support for SPI-based controllers Andy Shevchenko
2025-08-26  4:04     ` Jean-François Lessard

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250825033237.60143-7-jefflessard3@gmail.com \
    --to=jefflessard3@gmail.com \
    --cc=andy@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=geert@linux-m68k.org \
    --cc=krzk+dt@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.