Linux-LEDs Archive on lore.kernel.org
 help / color / Atom feed
From: "Andreas Färber" <afaerber@suse.de>
To: linux-realtek-soc@lists.infradead.org, linux-leds@vger.kernel.org
Cc: "Jacek Anaszewski" <jacek.anaszewski@gmail.com>,
	"Pavel Machek" <pavel@ucw.cz>, "Dan Murphy" <dmurphy@ti.com>,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, "Andreas Färber" <afaerber@suse.de>,
	zypeng@titanmec.com
Subject: [RFC 07/25] leds: Add Titan Micro Electronics TM1628
Date: Thu, 12 Dec 2019 04:39:34 +0100
Message-ID: <20191212033952.5967-8-afaerber@suse.de> (raw)
In-Reply-To: <20191212033952.5967-1-afaerber@suse.de>

Add a driver for TM1628 LED controller.

Cc: zypeng@titanmec.com
Signed-off-by: Andreas Färber <afaerber@suse.de>
---
 drivers/leds/Kconfig       |  11 ++
 drivers/leds/Makefile      |   1 +
 drivers/leds/leds-tm1628.c | 420 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 432 insertions(+)
 create mode 100644 drivers/leds/leds-tm1628.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 4b68520ac251..f3afb419a9a1 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -836,6 +836,17 @@ config LEDS_LM36274
 	  Say Y to enable the LM36274 LED driver for TI LMU devices.
 	  This supports the LED device LM36274.
 
+config LEDS_TM1628
+	tristate "LED driver for TM1628"
+	depends on LEDS_CLASS
+	depends on SPI
+	depends on OF || COMPILE_TEST
+	help
+	  Say Y to enable support for Titan Micro Electronics TM1628
+	  LED controllers.
+	  They are 3-wire SPI devices controlling a two-dimensional grid of
+	  LEDs. Dimming is applied to all outputs through an internal PWM.
+
 comment "LED Triggers"
 source "drivers/leds/trigger/Kconfig"
 
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 2da39e896ce8..4c931002ef44 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -90,6 +90,7 @@ obj-$(CONFIG_LEDS_LM36274)		+= leds-lm36274.o
 obj-$(CONFIG_LEDS_CR0014114)		+= leds-cr0014114.o
 obj-$(CONFIG_LEDS_DAC124S085)		+= leds-dac124s085.o
 obj-$(CONFIG_LEDS_EL15203000)		+= leds-el15203000.o
+obj-$(CONFIG_LEDS_TM1628)		+= leds-tm1628.o
 
 # LED Userspace Drivers
 obj-$(CONFIG_LEDS_USER)			+= uleds.o
diff --git a/drivers/leds/leds-tm1628.c b/drivers/leds/leds-tm1628.c
new file mode 100644
index 000000000000..319bf34ce835
--- /dev/null
+++ b/drivers/leds/leds-tm1628.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Titan Micro Electronics TM1628 LED controller
+ *
+ * Copyright (c) 2019 Andreas Färber
+ */
+
+#include <linux/bitops.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/spi/spi.h>
+
+#define TM1628_CMD_MASK			GENMASK(7, 6)
+#define TM1628_CMD_DISPLAY_MODE		(0x0 << 6)
+#define TM1628_CMD_DATA_SETTING		(0x1 << 6)
+#define TM1628_CMD_DISPLAY_CTRL		(0x2 << 6)
+#define TM1628_CMD_ADDRESS_SETTING	(0x3 << 6)
+
+#define TM1628_DISPLAY_MODE_MODE_MASK	GENMASK(1, 0)
+
+#define TM1628_DATA_SETTING_MODE_MASK	GENMASK(1, 0)
+#define TM1628_DATA_SETTING_WRITE_DATA	0x0
+#define TM1628_DATA_SETTING_READ_DATA	0x2
+#define TM1628_DATA_SETTING_FIXED_ADDR	BIT(2)
+#define TM1628_DATA_SETTING_TEST_MODE	BIT(3)
+
+#define TM1628_DISPLAY_CTRL_PW_MASK	GENMASK(2, 0)
+
+#define TM1628_DISPLAY_CTRL_DISPLAY_ON	BIT(3)
+
+struct tm1628_mode {
+	u8	grid_mask;
+	u16	seg_mask;
+};
+
+struct tm1628_info {
+	u8				grid_mask;
+	u16				seg_mask;
+	const struct tm1628_mode	*modes;
+	int				default_mode;
+	const struct pwm_capture	*pwm_map;
+	int				default_pwm;
+};
+
+struct tm1628_led {
+	struct led_classdev	leddev;
+	struct tm1628		*ctrl;
+	u32			grid;
+	u32			seg;
+};
+
+struct tm1628 {
+	struct spi_device		*spi;
+	const struct tm1628_info	*info;
+	u32				grids;
+	unsigned int			segments;
+	int				mode_index;
+	int				pwm_index;
+	u8				data[14];
+	unsigned int			num_leds;
+	struct tm1628_led		leds[];
+};
+
+/* Command 1: Display Mode Setting */
+static int tm1628_set_display_mode(struct spi_device *spi, u8 grid_mode)
+{
+	u8 cmd = TM1628_CMD_DISPLAY_MODE;
+
+	if (unlikely(grid_mode & ~TM1628_DISPLAY_MODE_MODE_MASK))
+		return -EINVAL;
+
+	cmd |= grid_mode;
+
+	return spi_write(spi, &cmd, 1);
+}
+
+/* Command 2: Data Setting */
+static int tm1628_write_data(struct spi_device *spi, const u8 *data, unsigned int len)
+{
+	u8 cmd = TM1628_CMD_DATA_SETTING | TM1628_DATA_SETTING_WRITE_DATA;
+	struct spi_transfer xfers[] = {
+		{
+			.tx_buf = &cmd,
+			.len = 1,
+		},
+		{
+			.tx_buf = data,
+			.len = len,
+		},
+	};
+
+	if (len > 14)
+		return -EINVAL;
+
+	return spi_sync_transfer(spi, xfers, ARRAY_SIZE(xfers));
+}
+
+/* Command 3: Address Setting */
+static int tm1628_set_address(struct spi_device *spi, u8 addr)
+{
+	u8 cmd = TM1628_CMD_ADDRESS_SETTING;
+
+	cmd |= (addr & GENMASK(3, 0));
+
+	return spi_write(spi, &cmd, 1);
+}
+
+/* Command 4: Display Control */
+static int tm1628_set_display_ctrl(struct spi_device *spi, bool on, u8 pwm_index)
+{
+	u8 cmd = TM1628_CMD_DISPLAY_CTRL;
+
+	if (on)
+		cmd |= TM1628_DISPLAY_CTRL_DISPLAY_ON;
+
+	if (pwm_index & ~TM1628_DISPLAY_CTRL_PW_MASK)
+		return -EINVAL;
+
+	cmd |= pwm_index;
+
+	return spi_write(spi, &cmd, 1);
+}
+
+static inline bool tm1628_is_valid_grid(struct tm1628 *s, unsigned int grid)
+{
+	return s->info->modes[s->mode_index].grid_mask & BIT(grid);
+}
+
+static inline bool tm1628_is_valid_seg(struct tm1628 *s, unsigned int seg)
+{
+	return s->info->modes[s->mode_index].seg_mask & BIT(seg);
+}
+
+static int tm1628_get_led_offset(struct tm1628 *s,
+	unsigned int grid, unsigned int seg, int *poffset, int *pbit)
+{
+	int offset, bit;
+
+	if (grid == 0 || grid > 7 || seg == 0 || seg > 16)
+		return -EINVAL;
+
+	offset = (grid - 1) * 2;
+	bit = seg - 1;
+	if (bit >= 8) {
+		bit -= 8;
+		offset++;
+	}
+
+	*poffset = offset;
+	if (pbit)
+		*pbit = bit;
+
+	return 0;
+}
+
+static int tm1628_get_led(struct tm1628 *s,
+	unsigned int grid, unsigned int seg, bool *on)
+{
+	int offset, bit;
+	int ret;
+
+	ret = tm1628_get_led_offset(s, grid, seg, &offset, &bit);
+	if (ret)
+		return ret;
+
+	*on = !!(s->data[offset] & BIT(bit));
+
+	return 0;
+}
+
+static int tm1628_set_led(struct tm1628 *s,
+	unsigned int grid, unsigned int seg, bool on)
+{
+	int offset, bit;
+	int ret;
+
+	ret = tm1628_get_led_offset(s, grid, seg, &offset, &bit);
+	if (ret)
+		return ret;
+
+	if (on)
+		s->data[offset] |=  BIT(bit);
+	else
+		s->data[offset] &= ~BIT(bit);
+
+	return 0;
+}
+
+static int tm1628_led_set_brightness(struct led_classdev *led_cdev,
+	enum led_brightness brightness)
+{
+	struct tm1628_led *led = container_of(led_cdev, struct tm1628_led, leddev);
+	struct tm1628 *s = led->ctrl;
+	int ret, offset;
+
+	ret = tm1628_set_led(s, led->grid, led->seg, brightness != LED_OFF);
+	if (ret)
+		return ret;
+
+	ret = tm1628_get_led_offset(s, led->grid, led->seg, &offset, NULL);
+	if (unlikely(ret))
+		return ret;
+
+	ret = tm1628_set_address(s->spi, offset);
+	if (ret)
+		return ret;
+
+	return tm1628_write_data(s->spi, s->data + offset, 1);
+}
+
+static enum led_brightness tm1628_led_get_brightness(struct led_classdev *led_cdev)
+{
+	struct tm1628_led *led = container_of(led_cdev, struct tm1628_led, leddev);
+	struct tm1628 *s = led->ctrl;
+	bool on;
+	int ret;
+
+	ret = tm1628_get_led(s, led->grid, led->seg, &on);
+	if (ret)
+		return ret;
+
+	return on ? LED_ON : LED_OFF;
+}
+
+static int tm1628_register_led(struct tm1628 *s,
+	struct fwnode_handle *node, u32 grid, u32 seg, struct tm1628_led *led)
+{
+	struct device *dev = &s->spi->dev;
+	struct led_init_data init_data = {0};
+
+	if (!tm1628_is_valid_grid(s, grid) || !tm1628_is_valid_seg(s, seg)) {
+		dev_warn(dev, "%s reg out of range\n", fwnode_get_name(node));
+		return -EINVAL;
+	}
+
+	led->ctrl = s;
+	led->grid = grid;
+	led->seg  = seg;
+	led->leddev.max_brightness = LED_ON;
+	led->leddev.brightness_set_blocking = tm1628_led_set_brightness;
+	led->leddev.brightness_get = tm1628_led_get_brightness;
+
+	fwnode_property_read_string(node, "linux,default-trigger", &led->leddev.default_trigger);
+
+	init_data.fwnode = node;
+	init_data.devicename = "tm1628";
+
+	return devm_led_classdev_register_ext(dev, &led->leddev, &init_data);
+}
+
+/* Work around __builtin_popcount() */
+static u32 tm1628_grid_popcount(u8 grid_mask)
+{
+	int i, n = 0;
+
+	while (grid_mask) {
+		i = __ffs(grid_mask);
+		grid_mask &= ~BIT(i);
+		n++;
+	}
+
+	return n;
+}
+
+static int tm1628_spi_probe(struct spi_device *spi)
+{
+	struct tm1628 *s;
+	struct fwnode_handle *child;
+	u32 grids;
+	u32 reg[2];
+	size_t leds;
+	int ret, i;
+
+	leds = device_get_child_node_count(&spi->dev);
+
+	s = devm_kzalloc(&spi->dev, struct_size(s, leds, leds), GFP_KERNEL);
+	if (!s)
+		return -ENOMEM;
+
+	s->spi = spi;
+
+	s->info = device_get_match_data(&spi->dev);
+	if (!s->info)
+		return -EINVAL;
+
+	s->pwm_index = s->info->default_pwm;
+
+	ret = tm1628_set_display_ctrl(spi, false, s->pwm_index);
+	if (ret) {
+		dev_err(&spi->dev, "Turning display off failed (%d)\n", ret);
+		return ret;
+	}
+
+	ret = device_property_read_u32(&spi->dev, "#grids", &grids);
+	if (ret && ret != -EINVAL) {
+		dev_err(&spi->dev, "Error reading #grids property (%d)\n", ret);
+		return ret;
+	}
+
+	s->mode_index = -1;
+	for (i = 0; i < 4; i++) {
+		if (tm1628_grid_popcount(s->info->modes[i].grid_mask) != grids)
+			continue;
+		s->mode_index = i;
+		break;
+	}
+	if (s->mode_index == -1) {
+		dev_err(&spi->dev, "#grids out of range (%u)\n", grids);
+		return -EINVAL;
+	}
+
+	spi_set_drvdata(spi, s);
+
+	device_for_each_child_node(&spi->dev, child) {
+		ret = fwnode_property_read_u32_array(child, "reg", reg, 2);
+		if (ret) {
+			dev_err(&spi->dev, "Reading %s reg property failed (%d)\n",
+				fwnode_get_name(child), ret);
+			fwnode_handle_put(child);
+			return ret;
+		}
+
+		if (fwnode_property_count_u32(child, "reg") == 2) {
+			ret = tm1628_register_led(s, child, reg[0], reg[1], &s->leds[i++]);
+			if (ret && ret != -EINVAL) {
+				dev_err(&spi->dev, "Failed to register LED %s (%d)\n",
+					fwnode_get_name(child), ret);
+				fwnode_handle_put(child);
+				return ret;
+			}
+			s->num_leds++;
+		}
+	}
+
+	ret = tm1628_set_address(spi, 0x0);
+	if (ret) {
+		dev_err(&spi->dev, "Setting address failed (%d)\n", ret);
+		return ret;
+	}
+
+	ret = tm1628_write_data(spi, s->data, sizeof(s->data));
+	if (ret) {
+		dev_err(&spi->dev, "Writing data failed (%d)\n", ret);
+		return ret;
+	}
+
+	ret = tm1628_set_display_mode(spi, s->mode_index);
+	if (ret) {
+		dev_err(&spi->dev, "Setting display mode failed (%d)\n", ret);
+		return ret;
+	}
+
+	ret = tm1628_set_display_ctrl(spi, true, s->pwm_index);
+	if (ret) {
+		dev_err(&spi->dev, "Turning display on failed (%d)\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct pwm_capture tm1628_pwm_map[8] = {
+	{ .duty_cycle =  1, .period = 16 },
+	{ .duty_cycle =  2, .period = 16 },
+	{ .duty_cycle =  4, .period = 16 },
+	{ .duty_cycle = 10, .period = 16 },
+	{ .duty_cycle = 11, .period = 16 },
+	{ .duty_cycle = 12, .period = 16 },
+	{ .duty_cycle = 13, .period = 16 },
+	{ .duty_cycle = 14, .period = 16 },
+};
+
+static const struct tm1628_mode tm1628_modes[4] = {
+	{
+		.grid_mask = GENMASK(4, 1),
+		.seg_mask = GENMASK(14, 12) | GENMASK(10, 1),
+	},
+	{
+		.grid_mask = GENMASK(5, 1),
+		.seg_mask = GENMASK(13, 12) | GENMASK(10, 1),
+	},
+	{
+		.grid_mask = GENMASK(6, 1),
+		.seg_mask = BIT(12) | GENMASK(10, 1),
+	},
+	{
+		.grid_mask = GENMASK(7, 1),
+		.seg_mask = GENMASK(10, 1),
+	},
+};
+
+static const struct tm1628_info tm1628_info = {
+	.grid_mask = GENMASK(7, 1),
+	.seg_mask = GENMASK(14, 12) | GENMASK(10, 1),
+	.modes = tm1628_modes,
+	.default_mode = 3,
+	.pwm_map = tm1628_pwm_map,
+	.default_pwm = 0,
+};
+
+static const struct of_device_id tm1628_spi_of_matches[] = {
+	{ .compatible = "titanmec,tm1628", .data = &tm1628_info },
+	{}
+};
+MODULE_DEVICE_TABLE(of, tm1628_spi_of_matches);
+
+static struct spi_driver tm1628_spi_driver = {
+	.probe = tm1628_spi_probe,
+	.driver = {
+		.name = "tm1628",
+		.of_match_table = tm1628_spi_of_matches,
+	},
+};
+module_spi_driver(tm1628_spi_driver);
+
+MODULE_DESCRIPTION("TM1628 LED controller driver");
+MODULE_AUTHOR("Andreas Färber");
+MODULE_LICENSE("GPL");
-- 
2.16.4


  parent reply index

Thread overview: 58+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-12-12  3:39 [RFC 00/25] arm64: realtek: Add Xnano X5 and implement TM1628/FD628/AiP1618 LED controllers Andreas Färber
2019-12-12  3:39 ` [RFC 01/25] dt-bindings: vendor-prefixes: Add Xnano Andreas Färber
2019-12-19 22:26   ` Rob Herring
2019-12-12  3:39 ` [RFC 02/25] dt-bindings: arm: realtek: Add Xnano X5 Andreas Färber
2019-12-19 22:27   ` Rob Herring
2019-12-12  3:39 ` [RFC 03/25] arm64: dts: realtek: rtd1295: " Andreas Färber
2019-12-12  3:39 ` [RFC 04/25] spi: gpio: Implement LSB First bitbang support Andreas Färber
2019-12-12  8:40   ` Geert Uytterhoeven
2019-12-12 15:14     ` Andreas Färber
2019-12-12 17:19       ` Mark Brown
2019-12-12 21:08         ` Andreas Färber
2019-12-13 11:42           ` Mark Brown
2019-12-12  3:39 ` [RFC 05/25] dt-bindings: vendor-prefixes: Add Titan Micro Electronics Andreas Färber
2019-12-19 22:31   ` Rob Herring
2019-12-12  3:39 ` [RFC 06/25] dt-bindings: leds: Add Titan Micro Electronics TM1628 Andreas Färber
2019-12-19 23:04   ` Rob Herring
2019-12-12  3:39 ` Andreas Färber [this message]
2019-12-14  9:48   ` [RFC 07/25] " Andreas Färber
2019-12-12  3:39 ` [RFC 08/25] arm64: dts: realtek: rtd129x-zidoo-x9s: Add TM1628 LED controller Andreas Färber
2019-12-12  3:39 ` [RFC 09/25] arm64: dts: realtek: rtd1295-zidoo-x9s: Add regular LEDs to TM1628 Andreas Färber
2019-12-12  3:39 ` [RFC 10/25] dt-bindings: vendor-prefixes: Add Fuda Hisi Microelectronics Andreas Färber
2019-12-19 23:04   ` Rob Herring
2019-12-12  3:39 ` [RFC 11/25] dt-bindings: leds: tm1628: Add Fuda Hisi Microelectronics FD628 Andreas Färber
2019-12-19 23:05   ` Rob Herring
2019-12-12  3:39 ` [RFC 12/25] " Andreas Färber
2019-12-12  3:39 ` [RFC 13/25] arm64: dts: realtek: rtd1295-xnano-x5: Add FD628 LED controller Andreas Färber
2019-12-12  3:39 ` [RFC 14/25] arm64: dts: realtek: rtd1295-xnano-x5: Add regular LEDs to FD628 Andreas Färber
2019-12-21 20:21   ` Pavel Machek
2019-12-12  3:39 ` [RFC 15/25] dt-bindings: vendor-prefixes: Add Fude Microelectronics Andreas Färber
2019-12-19 23:05   ` Rob Herring
2019-12-12  3:39 ` [RFC 16/25] dt-bindings: leds: tm1628: Add Fude Microelectronics AiP1618 Andreas Färber
2019-12-19 23:06   ` Rob Herring
2019-12-12  3:39 ` [RFC 17/25] leds: tm1628: Prepare " Andreas Färber
2019-12-21 19:55   ` Andreas Färber
2019-12-12  3:39 ` [RFC 18/25] dt-bindings: leds: tm1628: Define display child nodes Andreas Färber
2019-12-12  3:39 ` [RFC 19/25] leds: tm1628: Add 7-segment display support Andreas Färber
2019-12-12  8:33   ` Geert Uytterhoeven
2019-12-12 14:10     ` Andreas Färber
2019-12-21 20:23   ` Pavel Machek
2019-12-12  3:39 ` [RFC 20/25] arm64: dts: realtek: rtd1295-zidoo-x9s: Add display to TM1628 Andreas Färber
2019-12-12  3:39 ` [RFC 21/25] arm64: dts: realtek: rtd1295-xnano-x5: Add display to FD628 Andreas Färber
2019-12-12  3:39 ` [RFC 22/25] leds: tm1826: Add combined glyph support Andreas Färber
2019-12-21 20:27   ` Pavel Machek
2019-12-21 20:41     ` Andreas Färber
2019-12-21 21:04       ` Pavel Machek
2019-12-21 21:49         ` Andreas Färber
     [not found]           ` <CANiq72nA9OLa0SjY8W055J_2A32tcp7S98SruKSdWH2dm25VKw@mail.gmail.com>
2019-12-22  3:14             ` Andreas Färber
2020-01-15 14:31               ` Miguel Ojeda
2019-12-12  3:39 ` [RFC 23/25] WIP: leds: tm1628: Prepare TM1628 keys Andreas Färber
2019-12-12  3:39 ` [RFC 24/25] WIP: leds: tm1628: Prepare FD628 keys Andreas Färber
2019-12-12  3:39 ` [RFC 25/25] WIP: leds: tm1628: Prepare AiP1618 keys Andreas Färber
2019-12-12 13:14 ` [RFC 00/25] arm64: realtek: Add Xnano X5 and implement TM1628/FD628/AiP1618 LED controllers Robin Murphy
2019-12-12 20:55   ` Andreas Färber
2019-12-13 14:07     ` Robin Murphy
2019-12-13 14:36       ` Geert Uytterhoeven
2019-12-21 18:20 ` Pavel Machek
2019-12-21 21:07   ` Andreas Färber
2020-01-15 13:34 ` Andreas Färber

Reply instructions:

You may reply publically 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=20191212033952.5967-8-afaerber@suse.de \
    --to=afaerber@suse.de \
    --cc=dmurphy@ti.com \
    --cc=jacek.anaszewski@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=linux-realtek-soc@lists.infradead.org \
    --cc=pavel@ucw.cz \
    --cc=zypeng@titanmec.com \
    /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

Linux-LEDs Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-leds/0 linux-leds/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-leds linux-leds/ https://lore.kernel.org/linux-leds \
		linux-leds@vger.kernel.org
	public-inbox-index linux-leds

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-leds


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git