From: Lee Jones <lee@kernel.org>
To: Steffen Trumtrar <s.trumtrar@pengutronix.de>
Cc: Nam Tran <trannamatk@gmail.com>, Pavel Machek <pavel@kernel.org>,
linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org
Subject: Re: [PATCH v6] leds: add support for TI LP5860 LED driver chip
Date: Thu, 26 Mar 2026 10:47:13 +0000 [thread overview]
Message-ID: <20260326104713.GI1141718@google.com> (raw)
In-Reply-To: <20260217-v6-14-topic-ti-lp5860-v6-1-54a766d8b602@pengutronix.de>
On Tue, 17 Feb 2026, Steffen Trumtrar wrote:
> Add support for the Texas Instruments LP5860 LED driver chip
> via SPI interfaces.
>
> The LP5860 is an LED matrix driver for up to 196 LEDs, which supports
> short and open detection of the individual channel select lines.
>
> It can be connected to SPI or I2C bus. For now add support for SPI only.
>
> The original driver is from an unknown author at Texas Instruments. Only
> the cryptic handle 'zlzx' is known.
This paragraph seems a bit informal for a commit message. Is it necessary to
include the history of the original author? It might be better to keep the
commit message focused on the technical aspects of the change.
>
> Signed-off-by: Steffen Trumtrar <s.trumtrar@pengutronix.de>
> ---
> Changes since v6:
> - fix compilation as module
> - remove unnecessary select on FW_LOADER
> - Link to v6: https://lore.kernel.org/r/20251201-v6-14-topic-ti-lp5860-v6-0-be9a21218157@pengutronix.de
>
> Changes since v5:
> - Kconfig depends -> select
> - change some function/variable names
> - change line breaks (80char -> 100char)
> - call led_init_default_state_get once
> - rename index variable i -> led_index
> - don't fail on missing dt-properties
> - remove sysfs_create_group residue from v5
> - Link to v5: https://lore.kernel.org/r/20251110-v6-14-topic-ti-lp5860-v5-0-5b777b99a905@pengutronix.de
>
> Changes since v4:
> - remove global_brightness code and sysfs ABI
> - rebase to v6.18-rc1
> - Link to v4: https://lore.kernel.org/r/20251007-v6-14-topic-ti-lp5860-v4-0-967758069b60@pengutronix.de
>
> Changes since v3:
> - move to drivers/leds/rgb
> - fix some upper/lowercase
> - use ATTRIBUTE_GROUPS macro
> - unwrap some lines
> - Link to v3: https://lore.kernel.org/r/20250911-v6-14-topic-ti-lp5860-v3-0-390738ef9d71@pengutronix.de
>
> Changes since v2:
> - fix c-styling errors
> - rename functions/defines/variables
> - split out ABI documentation
> - rename [rgb]_current* to [rgb]_global_brightness*
> - rework multi-led probing
> - Link to v2: https://lore.kernel.org/r/20250514-v6-14-topic-ti-lp5860-v2-0-72ecc8fa4ad7@pengutronix.de
>
> Changes since v1:
> - add open and short detection
> - add ABI documentation
> - fix devicetree binding (maxItems/minItems)
> - fix commit message to imperative mood
> - minor cleanup
> - Link to v1: https://lore.kernel.org/r/20250220-v6-14-topic-ti-lp5860-v1-0-42874bdc7513@pengutronix.de
> ---
> drivers/leds/rgb/Kconfig | 25 +++
> drivers/leds/rgb/Makefile | 2 +
> drivers/leds/rgb/leds-lp5860-core.c | 200 ++++++++++++++++++++++
> drivers/leds/rgb/leds-lp5860-spi.c | 89 ++++++++++
> include/linux/platform_data/leds-lp5860.h | 268 ++++++++++++++++++++++++++++++
> 5 files changed, 584 insertions(+)
>
> diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
> index 28ef4c487367c..e9386c8c4bbbb 100644
> --- a/drivers/leds/rgb/Kconfig
> +++ b/drivers/leds/rgb/Kconfig
> @@ -39,6 +39,31 @@ config LEDS_LP5812
>
> If unsure, say N.
>
> +config LEDS_LP5860_CORE
> + tristate "Core Driver for TI LP5860"
> + depends on LEDS_CLASS
> + depends on OF
> + select REGMAP
> + help
> + This option supports common operations for LP5860 devices.
> + The LP5860 is a LED matrix driver with 18 constant current
> + sinks and 11 scan switches for 198 LED dots. Each dot can be
> + controlled individually and supports 8/16-bit PWM dimming.
> + The chip supports individual LED open and short detection.
> +
> + The device can be used with SPI or I2C bus.
> +
> +config LEDS_LP5860_SPI
> + tristate "LED Support for TI LP5860 SPI LED driver chip"
> + depends on SPI
> + select LEDS_LP5860_CORE
> + help
> + If you say yes here you get support for the Texas Instruments
> + LP5860 LED driver for SPI bus connections.
> +
> + To compile this driver as a module, choose M here: the
> + module will be called lp5860_spi.
> +
> config LEDS_NCP5623
> tristate "LED support for NCP5623"
> depends on I2C
> diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
> index be45991f63f50..f3b365ea082d1 100644
> --- a/drivers/leds/rgb/Makefile
> +++ b/drivers/leds/rgb/Makefile
> @@ -3,6 +3,8 @@
> obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o
> obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o
> obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o
> +obj-$(CONFIG_LEDS_LP5860_CORE) += leds-lp5860-core.o
> +obj-$(CONFIG_LEDS_LP5860_SPI) += leds-lp5860-spi.o
Nit: Please keep this list sorted alphabetically.
> obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o
> obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
> obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
> diff --git a/drivers/leds/rgb/leds-lp5860-core.c b/drivers/leds/rgb/leds-lp5860-core.c
> new file mode 100644
> index 0000000000000..28b4d86e11f1a
> --- /dev/null
> +++ b/drivers/leds/rgb/leds-lp5860-core.c
> @@ -0,0 +1,200 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025 Pengutronix
> + *
> + * Author: Steffen Trumtrar <kernel@pengutronix.de>
> + */
> +
> +#include <linux/gpio.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/module.h>
> +#include <linux/of_gpio.h>
Are `gpio.h` and `of_gpio.h` needed here? I don't see any GPIO functions
being used in this file.
> +#include <linux/of_platform.h>
> +#include <linux/regmap.h>
> +
> +#include <linux/platform_data/leds-lp5860.h>
> +
> +static struct lp5860_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
> +{
> + return container_of(mc_cdev, struct lp5860_led, mc_cdev);
> +}
> +
> +static int lp5860_set_dot_onoff(struct lp5860_led *led, unsigned int dot, bool enable)
> +{
> + unsigned int offset = dot / LP5860_MAX_DOT_ONOFF_GROUP_NUM;
> + unsigned int mask = BIT(dot % LP5860_MAX_DOT_ONOFF_GROUP_NUM);
> +
> + if (dot > LP5860_REG_DOT_ONOFF_MAX)
Using a register value as an LED max value seems odd, even if it does
corrilate. Can I suggest you create a new define or at least rename
this one to LP5860_MAX_LED or something?
> + return -EINVAL;
> +
> + return regmap_update_bits(led->chip->regmap,
> + LP5860_REG_DOT_ONOFF_START + offset, mask,
> + enable ? LP5860_DOT_ALL_ON : LP5860_DOT_ALL_OFF);
> +}
> +
> +static int lp5860_set_mc_brightness(struct led_classdev *cdev,
> + enum led_brightness brightness)
> +{
> + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
> + struct lp5860_led *led = mcled_cdev_to_led(mc_cdev);
> + int i;
Nit: `for (int i = 0; ...)` is preferred for loop counters these days.
> +
> + led_mc_calc_color_components(mc_cdev, brightness);
> +
> + for (i = 0; i < led->mc_cdev.num_colors; i++) {
> + unsigned int channel = mc_cdev->subled_info[i].channel;
> + unsigned int led_brightness = mc_cdev->subled_info[i].brightness;
> + int ret;
> +
> + ret = lp5860_set_dot_onoff(led, channel, led_brightness);
`lp5860_set_dot_onoff()` expects a boolean for its 'enable' argument, but
'led_brightness' is an integer. While this works due to implicit conversion, it
would be clearer to be explicit. Like `led_brightness > 0` or `!!led_brightness`.
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(led->chip->regmap,
> + LP5860_REG_PWM_BRI_START + channel, led_brightness);
> + if (ret)
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int lp5860_chip_enable_toggle(struct lp5860 *led, int enable)
The name `*_toggle` suggests flipping the state, but this function sets it
based on the `enable` parameter. Perhaps `lp5860_chip_enable` would be a
better name? Also, `enable` should be a bool.
> +{
> + return regmap_write(led->regmap, LP5860_REG_CHIP_EN, enable);
> +}
> +
> +static int lp5860_led_init(struct lp5860_led *led, struct fwnode_handle *fwnode,
> + unsigned int channel)
> +{
> + enum led_default_state default_state;
> + unsigned int brightness;
> + int ret;
> +
> + ret = regmap_read(led->chip->regmap, LP5860_REG_PWM_BRI_START + channel, &brightness);
> + if (ret)
> + return ret;
> +
> + default_state = led_init_default_state_get(fwnode);
> +
> + switch (default_state) {
> + case LEDS_DEFSTATE_ON:
> + led->brightness = LP5860_MAX_BRIGHTNESS;
> + break;
> + case LEDS_DEFSTATE_KEEP:
> + led->brightness = min(brightness, LP5860_MAX_BRIGHTNESS);
> + break;
> + default:
> + led->brightness = 0;
> + break;
> + }
> +
> + return lp5860_set_mc_brightness(&led->mc_cdev.led_cdev, led->brightness);
> +}
> +
> +static int lp5860_init_dt(struct lp5860 *lp)
> +{
> + struct fwnode_handle *led_node = NULL;
> + struct led_init_data init_data = {};
> + struct led_classdev *led_cdev;
> + struct mc_subled *mc_led_info;
> + struct lp5860_led *led;
> + int led_index = 0;
> + int chan;
> + int ret;
> +
> + device_for_each_child_node_scoped(lp->dev, multi_led) {
> + led = &lp->leds[led_index];
> +
> + init_data.fwnode = multi_led;
> +
> + /* Count the number of channels in this multi_led */
> + chan = fwnode_get_child_node_count(multi_led);
> + if (!chan || chan > LP5860_MAX_LED_CHANNELS)
> + return -EINVAL;
> +
> + led->mc_cdev.num_colors = chan;
> +
> + mc_led_info = devm_kcalloc(lp->dev, chan, sizeof(*mc_led_info), GFP_KERNEL);
> + if (!mc_led_info)
> + return -ENOMEM;
> +
> + led->chip = lp;
> + led->mc_cdev.subled_info = mc_led_info;
> + led_cdev = &led->mc_cdev.led_cdev;
> + led_cdev->max_brightness = LP5860_MAX_BRIGHTNESS;
> + led_cdev->brightness_set_blocking = lp5860_set_mc_brightness;
> +
> + chan = 0;
> + /* Initialize all channels of this multi_led */
> + fwnode_for_each_child_node(multi_led, led_node) {
> + u32 channel;
> + u32 color_index;
> +
> + ret = fwnode_property_read_u32(led_node, "color", &color_index);
> + if (ret) {
> + dev_err(lp->dev, "%pfwP: Cannot read 'color' property. Skipping.\n",
> + led_node);
> + fwnode_handle_put(led_node);
Doesn't `fwnode_for_each_child_node()` already handle this?
> + continue;
> + }
> +
> + ret = fwnode_property_read_u32(led_node, "reg", &channel);
> + if (ret < 0) {
> + dev_err(lp->dev, "%pfwP: 'reg' property is missing. Skipping.\n",
> + led_node);
> + fwnode_handle_put(led_node);
As above.
> + continue;
> + }
> +
> + mc_led_info[chan].color_index = color_index;
> + mc_led_info[chan].channel = channel;
> + lp5860_led_init(led, init_data.fwnode, chan);
There seems to be a logic issue here. `lp5860_led_init`'s third argument is
'channel', which is then used as an offset from `LP5860_REG_PWM_BRI_START`.
However, you are passing `chan`, which is just the loop index (0, 1, 2, ...).
Shouldn't you be passing `channel`, the value read from the 'reg' property?
The use of `chan` and `channel` in here is confusing at best.
This logic is also appears to be highly inefficient since you end up
calling `lp5860_set_mc_brightness()` for each sub-LED and
`lp5860_set_mc_brightness()` iterates over and sets *all* colors for the
multi-color LED.
> +
> + chan++;
> + }
> +
> + ret = devm_led_classdev_multicolor_register_ext(lp->dev, &led->mc_cdev, &init_data);
> + if (ret) {
> + dev_err(lp->dev, "%pfwP: Failed to register Multi-Color LEDs\n", multi_led);
> + return ret;
> + }
> + led_index++;
> + }
> +
> + return 0;
> +}
> +
> +int lp5860_device_init(struct device *dev)
> +{
> + struct lp5860 *lp = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = lp5860_chip_enable_toggle(lp, LP5860_CHIP_ENABLE);
> + if (ret)
> + return ret;
> +
> + /*
> + * Set to 8-bit PWM data without VSYNC.
> + * Data is sent out for display instantly after received.
> + */
> + ret = regmap_update_bits(lp->regmap, LP5860_REG_DEV_INITIAL, LP5860_MODE_MASK,
> + LP5860_MODE_1 << LP5860_MODE_OFFSET);
> + if (ret < 0)
Nit: `if (ret)` is the preferred style for checking error codes.
> + return ret;
> +
> + return lp5860_init_dt(lp);
> +}
> +EXPORT_SYMBOL_GPL(lp5860_device_init);
> +
> +void lp5860_device_remove(struct device *dev)
> +{
> + struct lp5860 *lp = dev_get_drvdata(dev);
> +
> + lp5860_chip_enable_toggle(lp, LP5860_CHIP_DISABLE);
> +}
> +EXPORT_SYMBOL_GPL(lp5860_device_remove);
> +
> +MODULE_AUTHOR("Steffen Trumtrar <kernel@pengutronix.de>");
> +MODULE_DESCRIPTION("TI LP5860 RGB LED core driver");
> +MODULE_LICENSE("GPL");
Should this be `GPL v2`?
> diff --git a/drivers/leds/rgb/leds-lp5860-spi.c b/drivers/leds/rgb/leds-lp5860-spi.c
> new file mode 100644
> index 0000000000000..1a35a18f50fde
> --- /dev/null
> +++ b/drivers/leds/rgb/leds-lp5860-spi.c
> @@ -0,0 +1,89 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2025 Pengutronix
> + *
> + * Author: Steffen Trumtrar <kernel@pengutronix.de>
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/spi/spi.h>
> +
> +#include <linux/platform_data/leds-lp5860.h>
> +
> +#define LP5860_SPI_WRITE_FLAG BIT(5)
> +
> +static const struct regmap_config lp5860_regmap_config = {
> + .name = "lp5860-spi",
> + .reg_bits = 10,
> + .pad_bits = 6,
> + .val_bits = 8,
> + .write_flag_mask = (__force unsigned long)cpu_to_be16(LP5860_SPI_WRITE_FLAG),
This is quite difficult to understand. `cpu_to_be16(BIT(5))` on a little-endian
system results in `0x2000`, which is `BIT(13)`. This seems to be an overly
complex way of setting bit 13 in the 16-bit address word.
Could you simplify this to just `BIT(13)` and add a comment explaining the
SPI protocol's write flag position? The casts are also unnecessary.
> + .reg_format_endian = REGMAP_ENDIAN_BIG,
> + .max_register = LP5860_MAX_REG,
> +};
> +
> +static int lp5860_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct lp5860 *lp5860;
> + unsigned int multi_leds;
> +
> + multi_leds = device_get_child_node_count(dev);
> + if (!multi_leds) {
> + dev_err(dev, "LEDs are not defined in Device Tree!");
> + return -ENODEV;
> + }
> +
> + if (multi_leds > LP5860_MAX_LED) {
> + dev_err(dev, "Too many LEDs specified.\n");
> + return -EINVAL;
> + }
> +
> + lp5860 = devm_kzalloc(dev, struct_size(lp5860, leds, multi_leds),
> + GFP_KERNEL);
> + if (!lp5860)
> + return -ENOMEM;
> +
> + lp5860->leds_count = multi_leds;
> +
> + lp5860->regmap = devm_regmap_init_spi(spi, &lp5860_regmap_config);
> + if (IS_ERR(lp5860->regmap)) {
> + dev_err(&spi->dev, "Failed to initialise Regmap.\n");
`dev_err_probe()`?
> + return PTR_ERR(lp5860->regmap);
> + }
> +
> + lp5860->dev = dev;
Where is this used?
> +
> + spi_set_drvdata(spi, lp5860);
> +
> + return lp5860_device_init(dev);
> +}
> +
> +static void lp5860_remove(struct spi_device *spi)
> +{
> + lp5860_device_remove(&spi->dev);
> +}
> +
> +static const struct of_device_id lp5860_of_match[] = {
> + { .compatible = "ti,lp5860" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, lp5860_of_match);
> +
> +static struct spi_driver lp5860_driver = {
> + .driver = {
> + .name = "lp5860-spi",
> + .of_match_table = lp5860_of_match,
> + },
> + .probe = lp5860_probe,
> + .remove = lp5860_remove,
> +};
> +module_spi_driver(lp5860_driver);
> +
> +MODULE_AUTHOR("Steffen Trumtrar <kernel@pengutronix.de>");
> +MODULE_DESCRIPTION("TI LP5860 RGB LED spi driver");
> +MODULE_LICENSE("GPL");
"GPL v2"?
> diff --git a/include/linux/platform_data/leds-lp5860.h b/include/linux/platform_data/leds-lp5860.h
The `platform_data` directory is generally for legacy board files. For a new
DT-based driver, the header file would normally live in a more specific
subsystem directory, like `include/linux/leds/`.
> new file mode 100644
> index 0000000000000..7bc69a7a550dd
> --- /dev/null
> +++ b/include/linux/platform_data/leds-lp5860.h
> @@ -0,0 +1,268 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) 2025 Pengutronix
Is the copyright year correct? `2025` is in the future. This is present in all
new files.
> + *
> + * Author: Steffen Trumtrar <kernel@pengutronix.de>
> + */
> +
> +#ifndef _LEDS_LP5860_COMMON_H
> +#define _LEDS_LP5860_COMMON_H
I'd drop the COMMON_ part.
> +#include <linux/led-class-multicolor.h>
> +#include <linux/regmap.h>
> +
> +#define LP5860_REG_CHIP_EN 0x00
> +#define LP5860_REG_DEV_INITIAL 0x01
> +#define LP5860_REG_DEV_CONFIG1 0x02
> +#define LP5860_REG_DEV_CONFIG2 0x03
> +#define LP5860_REG_DEV_CONFIG3 0x04
> +#define LP5860_REG_GLOBAL_BRI 0x05
> +#define LP5860_REG_GROUP0_BRI 0x06
> +#define LP5860_REG_GROUP1_BRI 0x07
> +#define LP5860_REG_GROUP2_BRI 0x08
> +#define LP5860_REG_R_CURRENT_SET 0x09
> +#define LP5860_REG_G_CURRENT_SET 0x0A
> +#define LP5860_REG_B_CURRENT_SET 0x0B
> +#define LP5860_REG_GRP_SEL_START 0x0C
> +#define LP5860_REG_DOT_ONOFF_START 0x43
> +#define LP5860_REG_DOT_ONOFF_MAX 0x63
A name like `..._MAX` usually implies a count or a maximum value, but this is a
register address. To avoid confusion, perhaps rename it to something like
`LP5860_REG_DOT_ONOFF_END`?
> +#define LP5860_REG_FAULT_STATE 0x64
> +#define LP5860_REG_DOT_LOD_START 0x65
> +#define LP5860_REG_DOT_LSD_START 0x86
> +#define LP5860_REG_LOD_CLEAR 0xA7
> +#define LP5860_REG_LSD_CLEAR 0xA8
> +#define LP5860_REG_RESET 0xA9
> +#define LP5860_REG_DC_START 0x0100
> +#define LP5860_REG_PWM_BRI_START 0x0200
> +#define LP5860_MAX_REG 0x038B
[...]
--
Lee Jones [李琼斯]
prev parent reply other threads:[~2026-03-26 10:47 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-17 14:46 [PATCH v6] leds: add support for TI LP5860 LED driver chip Steffen Trumtrar
2026-03-05 8:58 ` Steffen Trumtrar
2026-03-26 10:47 ` Lee Jones [this message]
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=20260326104713.GI1141718@google.com \
--to=lee@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=pavel@kernel.org \
--cc=s.trumtrar@pengutronix.de \
--cc=trannamatk@gmail.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
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.