summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-16 22:15:19 +0300
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-16 22:15:19 +0300
commit2228d9cf7a562d1b0ca86bd529f6acb94f4bb80f (patch)
tree9a11ea72e8f20a422f463faa31a10a8d3d398cbe /drivers
parent4bfa4a54b02029b3996b9f9021f5f745c71e9064 (diff)
parentb2c87f5e98cd88095dbc6802197526703d5e4e48 (diff)
downloadlinux-2228d9cf7a562d1b0ca86bd529f6acb94f4bb80f.tar.xz
Merge tag 'leds-next-6.20' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds
Pull LED updates from Lee Jones: "New Support & Features: - Add support for the TI LP5812 4x3 matrix RGB LED driver, including autonomous animation engine control and extensive scan multiplexing modes - Add a new driver for the ams Osram AS3668 4-channel I2C LED controller - Extend the is31fl32xx driver to support the is31fl3293 variant, which features 3 channels and 12-bit PWM resolution Improvements & Fixes: - Prevent the ExpressWire KTD2801 chip from entering an undefined state by disabling interrupts during time-sensitive communication - Ensure the Qualcomm LPG driver detects hardware write failures by checking the return value of regmap_bulk_write() during LUT programming - Fix kernel-doc warnings in the lm3692x driver by documenting missing struct members and standardizing the comment style - Update the ExpressWire library to use fsleep() and unexport internal-only functions - Improve the is31fl32xx driver by reordering code to eliminate unnecessary forward declarations Cleanups & Refactoring: - Simplify the LP55XX common LED driver by utilizing the for_each_available_child_of_node_scoped() macro for more concise node iteration Device Tree Bindings Updates: - Add new YAML bindings for the TI LP5860 and LP5812 LED controllers, and the ams Osram AS3668 - Convert the TI LM3697 white LED driver binding to DT schema format - Allow multicolor LED nodes to be named with numeric suffixes (e.g., multi-led-0) to handle multiple instances without unit addresses - Document support for the PMH0101 variant in the Qualcomm LPG PWM and SPMI Flash LED bindings - Add the issi,is31fl3293 compatible string to the is31fl32xx binding" * tag 'leds-next-6.20' of git://git.kernel.org/pub/scm/linux/kernel/git/lee/leds: dt-bindings: leds: Convert ti,lm3697 to DT schema leds: as3668: Driver for the ams Osram 4-channel i2c LED driver dt-bindings: leds: Add new as3668 support docs: leds: Document TI LP5812 LED driver leds: Add basic support for TI/National Semiconductor LP5812 LED Driver leds: qcom-lpg: Check the return value of regmap_bulk_write() dt-bindings: leds: qcom,spmi-flash-led: Add PMH0101 compatible dt-bindings: leds: leds-qcom-lpg: Add support for PMH0101 PWM dt-bindings: leds: Allow differently named multicolor LEDs leds: lp55xx: Simplify with scoped for each OF child loop dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver leds: is31f132xx: Add support for is31fl3293 leds: is31f132xx: Re-order code to remove forward declarations dt-bindings: leds: Add issi,is31fl3293 to leds-is31fl32xx leds: expresswire: Fix chip state breakage dt-bindings: leds: Add LP5860 LED controller leds: lm3692x: Fix kernel-doc for struct lm3692x_led
Diffstat (limited to 'drivers')
-rw-r--r--drivers/leds/Kconfig13
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-as3668.c202
-rw-r--r--drivers/leds/leds-expresswire.c24
-rw-r--r--drivers/leds/leds-is31fl32xx.c266
-rw-r--r--drivers/leds/leds-lm3692x.c3
-rw-r--r--drivers/leds/leds-lp55xx-common.c7
-rw-r--r--drivers/leds/rgb/Kconfig13
-rw-r--r--drivers/leds/rgb/Makefile1
-rw-r--r--drivers/leds/rgb/leds-lp5812.c642
-rw-r--r--drivers/leds/rgb/leds-lp5812.h172
-rw-r--r--drivers/leds/rgb/leds-qcom-lpg.c8
12 files changed, 1263 insertions, 89 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 11e7282dc297..597d7a79c988 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -107,6 +107,19 @@ config LEDS_ARIEL
Say Y to if your machine is a Dell Wyse 3020 thin client.
+config LEDS_OSRAM_AMS_AS3668
+ tristate "LED support for Osram AMS AS3668"
+ depends on LEDS_CLASS
+ depends on I2C
+ help
+ This option enables support for the Osram AMS AS3668 LED controller.
+ The AS3668 provides up to four LED channels and is controlled via
+ the I2C bus. This driver offers basic brightness control for each
+ channel, without support for blinking or other advanced features.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-as3668.
+
config LEDS_AW200XX
tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 9a0333ec1a86..8fdb45d5b439 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
obj-$(CONFIG_LEDS_APU) += leds-apu.o
obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
+obj-$(CONFIG_LEDS_AS3668) += leds-as3668.o
obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
diff --git a/drivers/leds/leds-as3668.c b/drivers/leds/leds-as3668.c
new file mode 100644
index 000000000000..b2794492370e
--- /dev/null
+++ b/drivers/leds/leds-as3668.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Osram AMS AS3668 LED Driver IC
+ *
+ * Copyright (C) 2025 Lukas Timmermann <linux@timmermann.space>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/uleds.h>
+
+#define AS3668_MAX_LEDS 4
+
+/* Chip Ident */
+
+#define AS3668_CHIP_ID1_REG 0x3e
+#define AS3668_CHIP_ID 0xa5
+
+/* Current Control */
+
+#define AS3668_CURR_MODE_REG 0x01
+#define AS3668_CURR_MODE_OFF 0x0
+#define AS3668_CURR_MODE_ON 0x1
+#define AS3668_CURR1_MODE_MASK GENMASK(1, 0)
+#define AS3668_CURR2_MODE_MASK GENMASK(3, 2)
+#define AS3668_CURR3_MODE_MASK GENMASK(5, 4)
+#define AS3668_CURR4_MODE_MASK GENMASK(7, 6)
+#define AS3668_CURR1_REG 0x02
+#define AS3668_CURR2_REG 0x03
+#define AS3668_CURR3_REG 0x04
+#define AS3668_CURR4_REG 0x05
+
+#define AS3668_CURR_MODE_PACK(mode) (((mode) << 0) | \
+ ((mode) << 2) | \
+ ((mode) << 4) | \
+ ((mode) << 6))
+
+struct as3668_led {
+ struct led_classdev cdev;
+ struct as3668 *chip;
+ struct fwnode_handle *fwnode;
+ u8 mode_mask;
+ u8 current_reg;
+};
+
+struct as3668 {
+ struct i2c_client *client;
+ struct as3668_led leds[AS3668_MAX_LEDS];
+};
+
+static int as3668_channel_mode_set(struct as3668_led *led, u8 mode)
+{
+ int ret;
+ u8 channel_modes;
+
+ ret = i2c_smbus_read_byte_data(led->chip->client, AS3668_CURR_MODE_REG);
+ if (ret < 0) {
+ dev_err(led->cdev.dev, "failed to read channel modes\n");
+ return ret;
+ }
+ channel_modes = (u8)ret;
+
+ channel_modes &= ~led->mode_mask;
+ channel_modes |= led->mode_mask & (AS3668_CURR_MODE_PACK(mode));
+
+ return i2c_smbus_write_byte_data(led->chip->client, AS3668_CURR_MODE_REG, channel_modes);
+}
+
+static enum led_brightness as3668_brightness_get(struct led_classdev *cdev)
+{
+ struct as3668_led *led = container_of(cdev, struct as3668_led, cdev);
+
+ return i2c_smbus_read_byte_data(led->chip->client, led->current_reg);
+}
+
+static void as3668_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct as3668_led *led = container_of(cdev, struct as3668_led, cdev);
+ int err;
+
+ err = as3668_channel_mode_set(led, !!brightness);
+ if (err)
+ dev_err(cdev->dev, "failed to set channel mode: %d\n", err);
+
+ err = i2c_smbus_write_byte_data(led->chip->client, led->current_reg, brightness);
+ if (err)
+ dev_err(cdev->dev, "failed to set brightness: %d\n", err);
+}
+
+static int as3668_dt_init(struct as3668 *as3668)
+{
+ struct device *dev = &as3668->client->dev;
+ struct as3668_led *led;
+ struct led_init_data init_data = {};
+ int err;
+ u32 reg;
+
+ for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
+ err = of_property_read_u32(child, "reg", &reg);
+ if (err)
+ return dev_err_probe(dev, err, "failed to read 'reg' property");
+
+ if (reg < 0 || reg >= AS3668_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL,
+ "unsupported LED: %d\n", reg);
+
+ led = &as3668->leds[reg];
+ led->fwnode = of_fwnode_handle(child);
+
+ led->current_reg = reg + AS3668_CURR1_REG;
+ led->mode_mask = AS3668_CURR1_MODE_MASK << (reg * 2);
+ led->chip = as3668;
+
+ led->cdev.max_brightness = U8_MAX;
+ led->cdev.brightness_get = as3668_brightness_get;
+ led->cdev.brightness_set = as3668_brightness_set;
+
+ init_data.fwnode = led->fwnode;
+ init_data.default_label = ":";
+
+ err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (err)
+ return dev_err_probe(dev, err, "failed to register LED %d\n", reg);
+ }
+
+ return 0;
+}
+
+static int as3668_probe(struct i2c_client *client)
+{
+ struct as3668 *as3668;
+ int err;
+ u8 chip_id;
+
+ chip_id = i2c_smbus_read_byte_data(client, AS3668_CHIP_ID1_REG);
+ if (chip_id != AS3668_CHIP_ID)
+ return dev_err_probe(&client->dev, -ENODEV,
+ "expected chip ID 0x%02x, got 0x%02x\n",
+ AS3668_CHIP_ID, chip_id);
+
+ as3668 = devm_kzalloc(&client->dev, sizeof(*as3668), GFP_KERNEL);
+ if (!as3668)
+ return -ENOMEM;
+
+ as3668->client = client;
+
+ err = as3668_dt_init(as3668);
+ if (err)
+ return err;
+
+ /* Set all four channel modes to 'off' */
+ err = i2c_smbus_write_byte_data(client, AS3668_CURR_MODE_REG,
+ FIELD_PREP(AS3668_CURR1_MODE_MASK, AS3668_CURR_MODE_OFF) |
+ FIELD_PREP(AS3668_CURR2_MODE_MASK, AS3668_CURR_MODE_OFF) |
+ FIELD_PREP(AS3668_CURR3_MODE_MASK, AS3668_CURR_MODE_OFF) |
+ FIELD_PREP(AS3668_CURR4_MODE_MASK, AS3668_CURR_MODE_OFF));
+
+ /* Set initial currents to 0mA */
+ err |= i2c_smbus_write_byte_data(client, AS3668_CURR1_REG, 0);
+ err |= i2c_smbus_write_byte_data(client, AS3668_CURR2_REG, 0);
+ err |= i2c_smbus_write_byte_data(client, AS3668_CURR3_REG, 0);
+ err |= i2c_smbus_write_byte_data(client, AS3668_CURR4_REG, 0);
+
+ if (err)
+ return dev_err_probe(&client->dev, -EIO, "failed to set zero initial current levels\n");
+
+ return 0;
+}
+
+static void as3668_remove(struct i2c_client *client)
+{
+ i2c_smbus_write_byte_data(client, AS3668_CURR_MODE_REG, 0);
+}
+
+static const struct i2c_device_id as3668_idtable[] = {
+ { "as3668" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, as3668_idtable);
+
+static const struct of_device_id as3668_match_table[] = {
+ { .compatible = "ams,as3668" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, as3668_match_table);
+
+static struct i2c_driver as3668_driver = {
+ .driver = {
+ .name = "leds_as3668",
+ .of_match_table = as3668_match_table,
+ },
+ .probe = as3668_probe,
+ .remove = as3668_remove,
+ .id_table = as3668_idtable,
+};
+module_i2c_driver(as3668_driver);
+
+MODULE_AUTHOR("Lukas Timmermann <linux@timmermann.space>");
+MODULE_DESCRIPTION("AS3668 LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-expresswire.c b/drivers/leds/leds-expresswire.c
index bb69be228a6d..25c6b159a6ee 100644
--- a/drivers/leds/leds-expresswire.c
+++ b/drivers/leds/leds-expresswire.c
@@ -9,6 +9,7 @@
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/gpio/consumer.h>
+#include <linux/irqflags.h>
#include <linux/types.h>
#include <linux/leds-expresswire.h>
@@ -16,37 +17,41 @@
void expresswire_power_off(struct expresswire_common_props *props)
{
gpiod_set_value_cansleep(props->ctrl_gpio, 0);
- usleep_range(props->timing.poweroff_us, props->timing.poweroff_us * 2);
+ fsleep(props->timing.poweroff_us);
}
EXPORT_SYMBOL_NS_GPL(expresswire_power_off, "EXPRESSWIRE");
void expresswire_enable(struct expresswire_common_props *props)
{
+ unsigned long flags;
+
+ local_irq_save(flags);
+
gpiod_set_value(props->ctrl_gpio, 1);
udelay(props->timing.detect_delay_us);
gpiod_set_value(props->ctrl_gpio, 0);
udelay(props->timing.detect_us);
gpiod_set_value(props->ctrl_gpio, 1);
+
+ local_irq_restore(flags);
}
EXPORT_SYMBOL_NS_GPL(expresswire_enable, "EXPRESSWIRE");
-void expresswire_start(struct expresswire_common_props *props)
+static void expresswire_start(struct expresswire_common_props *props)
{
gpiod_set_value(props->ctrl_gpio, 1);
udelay(props->timing.data_start_us);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_start, "EXPRESSWIRE");
-void expresswire_end(struct expresswire_common_props *props)
+static void expresswire_end(struct expresswire_common_props *props)
{
gpiod_set_value(props->ctrl_gpio, 0);
udelay(props->timing.end_of_data_low_us);
gpiod_set_value(props->ctrl_gpio, 1);
udelay(props->timing.end_of_data_high_us);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_end, "EXPRESSWIRE");
-void expresswire_set_bit(struct expresswire_common_props *props, bool bit)
+static void expresswire_set_bit(struct expresswire_common_props *props, bool bit)
{
if (bit) {
gpiod_set_value(props->ctrl_gpio, 0);
@@ -60,13 +65,18 @@ void expresswire_set_bit(struct expresswire_common_props *props, bool bit)
udelay(props->timing.short_bitset_us);
}
}
-EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, "EXPRESSWIRE");
void expresswire_write_u8(struct expresswire_common_props *props, u8 val)
{
+ unsigned long flags;
+
+ local_irq_save(flags);
+
expresswire_start(props);
for (int i = 7; i >= 0; i--)
expresswire_set_bit(props, val & BIT(i));
expresswire_end(props);
+
+ local_irq_restore(flags);
}
EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, "EXPRESSWIRE");
diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
index dc9349f9d350..fe07acbb103a 100644
--- a/drivers/leds/leds-is31fl32xx.c
+++ b/drivers/leds/leds-is31fl32xx.c
@@ -34,10 +34,26 @@
#define IS31FL32XX_PWM_FREQUENCY_22KHZ 0x01
+/* Registers for IS31FL3293 */
+#define IS31FL3293_SHUTDOWN_REG 0x01
+#define IS31FL3293_SHUTDOWN_SSD_DISABLE BIT(0)
+#define IS31FL3293_SHUTDOWN_EN1 BIT(4)
+#define IS31FL3293_SHUTDOWN_EN2 BIT(5)
+#define IS31FL3293_SHUTDOWN_EN3 BIT(6)
+#define IS31FL3293_GCC_REG 0x03
+#define IS31FL3293_GCC_LEVEL_MAX 0x3f
+#define IS31FL3293_CL_REG 0x10
+#define IS31FL3293_COLOR_UPDATE_REG 0x27
+#define IS31FL3293_COLOR_UPDATE_MAGIC 0xc5
+#define IS31FL3293_RESET_REG 0x3c
+#define IS31FL3293_RESET_MAGIC 0xc5
+#define IS31FL3293_MAX_MICROAMP 20000
+
struct is31fl32xx_priv;
struct is31fl32xx_led_data {
struct led_classdev cdev;
u8 channel; /* 1-based, max priv->cdef->channels */
+ u32 max_microamp;
struct is31fl32xx_priv *priv;
};
@@ -53,6 +69,7 @@ struct is31fl32xx_priv {
* @channels : Number of LED channels
* @shutdown_reg : address of Shutdown register (optional)
* @pwm_update_reg : address of PWM Update register
+ * @pwm_update_value : value to write to PWM Update register
* @global_control_reg : address of Global Control register (optional)
* @reset_reg : address of Reset register (optional)
* @output_frequency_setting_reg: address of output frequency register (optional)
@@ -60,6 +77,7 @@ struct is31fl32xx_priv {
* @pwm_registers_reversed: : true if PWM registers count down instead of up
* @led_control_register_base : address of first LED control register (optional)
* @enable_bits_per_led_control_register: number of LEDs enable bits in each
+ * @brightness_steps : number of brightness steps supported by the chip
* @reset_func : pointer to reset function
* @sw_shutdown_func : pointer to software shutdown function
*
@@ -77,6 +95,7 @@ struct is31fl32xx_chipdef {
u8 channels;
u8 shutdown_reg;
u8 pwm_update_reg;
+ u8 pwm_update_value;
u8 global_control_reg;
u8 reset_reg;
u8 output_frequency_setting_reg;
@@ -84,76 +103,11 @@ struct is31fl32xx_chipdef {
bool pwm_registers_reversed;
u8 led_control_register_base;
u8 enable_bits_per_led_control_register;
+ u16 brightness_steps;
int (*reset_func)(struct is31fl32xx_priv *priv);
int (*sw_shutdown_func)(struct is31fl32xx_priv *priv, bool enable);
};
-static const struct is31fl32xx_chipdef is31fl3236_cdef = {
- .channels = 36,
- .shutdown_reg = 0x00,
- .pwm_update_reg = 0x25,
- .global_control_reg = 0x4a,
- .reset_reg = 0x4f,
- .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
- .pwm_register_base = 0x01,
- .led_control_register_base = 0x26,
- .enable_bits_per_led_control_register = 1,
-};
-
-static const struct is31fl32xx_chipdef is31fl3236a_cdef = {
- .channels = 36,
- .shutdown_reg = 0x00,
- .pwm_update_reg = 0x25,
- .global_control_reg = 0x4a,
- .reset_reg = 0x4f,
- .output_frequency_setting_reg = 0x4b,
- .pwm_register_base = 0x01,
- .led_control_register_base = 0x26,
- .enable_bits_per_led_control_register = 1,
-};
-
-static const struct is31fl32xx_chipdef is31fl3235_cdef = {
- .channels = 28,
- .shutdown_reg = 0x00,
- .pwm_update_reg = 0x25,
- .global_control_reg = 0x4a,
- .reset_reg = 0x4f,
- .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
- .pwm_register_base = 0x05,
- .led_control_register_base = 0x2a,
- .enable_bits_per_led_control_register = 1,
-};
-
-static const struct is31fl32xx_chipdef is31fl3218_cdef = {
- .channels = 18,
- .shutdown_reg = 0x00,
- .pwm_update_reg = 0x16,
- .global_control_reg = IS31FL32XX_REG_NONE,
- .reset_reg = 0x17,
- .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
- .pwm_register_base = 0x01,
- .led_control_register_base = 0x13,
- .enable_bits_per_led_control_register = 6,
-};
-
-static int is31fl3216_reset(struct is31fl32xx_priv *priv);
-static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,
- bool enable);
-static const struct is31fl32xx_chipdef is31fl3216_cdef = {
- .channels = 16,
- .shutdown_reg = IS31FL32XX_REG_NONE,
- .pwm_update_reg = 0xB0,
- .global_control_reg = IS31FL32XX_REG_NONE,
- .reset_reg = IS31FL32XX_REG_NONE,
- .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
- .pwm_register_base = 0x10,
- .pwm_registers_reversed = true,
- .led_control_register_base = 0x01,
- .enable_bits_per_led_control_register = 8,
- .reset_func = is31fl3216_reset,
- .sw_shutdown_func = is31fl3216_software_shutdown,
-};
-
static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
{
int ret;
@@ -219,6 +173,62 @@ static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,
}
/*
+ * Custom Reset function for IS31FL3293. We need to set the global current limit
+ * and write to the color update register once.
+ */
+static int is31fl3293_reset(struct is31fl32xx_priv *priv)
+{
+ int i, ret;
+
+ ret = is31fl32xx_write(priv, IS31FL3293_RESET_REG,
+ IS31FL3293_RESET_MAGIC);
+ if (ret)
+ return ret;
+
+ /* Set the global current limit to maximum */
+ ret = is31fl32xx_write(priv, IS31FL3293_GCC_REG,
+ IS31FL3293_GCC_LEVEL_MAX);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < priv->num_leds; i++) {
+ struct is31fl32xx_led_data *led_data = &priv->leds[i];
+ int current_level_reg = IS31FL3293_CL_REG + led_data->channel - 1;
+ int microamp = max(led_data->max_microamp, IS31FL3293_MAX_MICROAMP);
+ int current_level = (microamp * 0xff) / IS31FL3293_MAX_MICROAMP;
+
+ ret = is31fl32xx_write(priv, current_level_reg, current_level);
+ if (ret)
+ return ret;
+ }
+
+ ret = is31fl32xx_write(priv, IS31FL3293_COLOR_UPDATE_REG,
+ IS31FL3293_COLOR_UPDATE_MAGIC);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Custom Software-Shutdown function for IS31FL3293 because the SHUTDOWN
+ * register of this device also has bits to enable the channels.
+ */
+static int is31fl3293_software_shutdown(struct is31fl32xx_priv *priv,
+ bool enable)
+{
+ u8 value = 0;
+
+ if (!enable)
+ value = IS31FL3293_SHUTDOWN_SSD_DISABLE |
+ IS31FL3293_SHUTDOWN_EN1 |
+ IS31FL3293_SHUTDOWN_EN2 |
+ IS31FL3293_SHUTDOWN_EN3;
+
+ return is31fl32xx_write(priv, IS31FL3293_SHUTDOWN_REG, value);
+}
+
+/*
* NOTE: A mutex is not needed in this function because:
* - All referenced data is read-only after probe()
* - The I2C core has a mutex on to protect the bus
@@ -256,13 +266,36 @@ static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
else
pwm_register_offset = led_data->channel - 1;
- ret = is31fl32xx_write(led_data->priv,
- cdef->pwm_register_base + pwm_register_offset,
- brightness);
- if (ret)
- return ret;
+ switch (cdef->brightness_steps) {
+ case 256:
+ ret = is31fl32xx_write(led_data->priv,
+ cdef->pwm_register_base + pwm_register_offset,
+ brightness);
+ if (ret)
+ return ret;
- return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
+ break;
+ case 4096:
+ /* IS31FL329x devices use two registers to store 12 bits of brightness */
+ pwm_register_offset *= 2;
+
+ ret = is31fl32xx_write(led_data->priv,
+ cdef->pwm_register_base + pwm_register_offset,
+ brightness & 0xff);
+ if (ret)
+ return ret;
+
+ ret = is31fl32xx_write(led_data->priv,
+ cdef->pwm_register_base + pwm_register_offset + 1,
+ (brightness >> 8) & 0xf);
+ if (ret)
+ return ret;
+
+ break;
+ }
+
+ return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg,
+ cdef->pwm_update_value);
}
static int is31fl32xx_reset_regs(struct is31fl32xx_priv *priv)
@@ -361,6 +394,8 @@ static int is31fl32xx_parse_child_dt(const struct device *dev,
}
led_data->channel = reg;
+ of_property_read_u32(child, "led-max-microamp", &led_data->max_microamp);
+
cdev->brightness_set_blocking = is31fl32xx_brightness_set;
return 0;
@@ -405,6 +440,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
const struct is31fl32xx_led_data *other_led_data;
led_data->priv = priv;
+ led_data->cdev.max_brightness = priv->cdef->brightness_steps - 1;
ret = is31fl32xx_parse_child_dt(dev, child, led_data);
if (ret)
@@ -435,8 +471,89 @@ static int is31fl32xx_parse_dt(struct device *dev,
return 0;
}
+static const struct is31fl32xx_chipdef is31fl3236_cdef = {
+ .channels = 36,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x25,
+ .global_control_reg = 0x4a,
+ .reset_reg = 0x4f,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x01,
+ .led_control_register_base = 0x26,
+ .enable_bits_per_led_control_register = 1,
+};
+
+static const struct is31fl32xx_chipdef is31fl3236a_cdef = {
+ .channels = 36,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x25,
+ .global_control_reg = 0x4a,
+ .reset_reg = 0x4f,
+ .output_frequency_setting_reg = 0x4b,
+ .pwm_register_base = 0x01,
+ .led_control_register_base = 0x26,
+ .enable_bits_per_led_control_register = 1,
+ .brightness_steps = 256,
+};
+
+static const struct is31fl32xx_chipdef is31fl3235_cdef = {
+ .channels = 28,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x25,
+ .global_control_reg = 0x4a,
+ .reset_reg = 0x4f,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x05,
+ .led_control_register_base = 0x2a,
+ .enable_bits_per_led_control_register = 1,
+ .brightness_steps = 256,
+};
+
+static const struct is31fl32xx_chipdef is31fl3218_cdef = {
+ .channels = 18,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x16,
+ .global_control_reg = IS31FL32XX_REG_NONE,
+ .reset_reg = 0x17,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x01,
+ .led_control_register_base = 0x13,
+ .enable_bits_per_led_control_register = 6,
+ .brightness_steps = 256,
+};
+
+static const struct is31fl32xx_chipdef is31fl3216_cdef = {
+ .channels = 16,
+ .shutdown_reg = IS31FL32XX_REG_NONE,
+ .pwm_update_reg = 0xB0,
+ .global_control_reg = IS31FL32XX_REG_NONE,
+ .reset_reg = IS31FL32XX_REG_NONE,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x10,
+ .pwm_registers_reversed = true,
+ .led_control_register_base = 0x01,
+ .enable_bits_per_led_control_register = 8,
+ .reset_func = is31fl3216_reset,
+ .sw_shutdown_func = is31fl3216_software_shutdown,
+ .brightness_steps = 256,
+};
+
+static const struct is31fl32xx_chipdef is31fl3293_cdef = {
+ .channels = 3,
+ .shutdown_reg = IS31FL32XX_REG_NONE,
+ .pwm_update_reg = 0x28,
+ .pwm_update_value = 0xc5,
+ .global_control_reg = IS31FL32XX_REG_NONE,
+ .reset_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x19,
+ .led_control_register_base = IS31FL32XX_REG_NONE,
+ .brightness_steps = 4096,
+ .reset_func = is31fl3293_reset,
+ .sw_shutdown_func = is31fl3293_software_shutdown,
+};
static const struct of_device_id of_is31fl32xx_match[] = {
+ { .compatible = "issi,is31fl3293", .data = &is31fl3293_cdef, },
{ .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
{ .compatible = "issi,is31fl3236a", .data = &is31fl3236a_cdef, },
{ .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
@@ -472,11 +589,11 @@ static int is31fl32xx_probe(struct i2c_client *client)
priv->cdef = cdef;
i2c_set_clientdata(client, priv);
- ret = is31fl32xx_init_regs(priv);
+ ret = is31fl32xx_parse_dt(dev, priv);
if (ret)
return ret;
- ret = is31fl32xx_parse_dt(dev, priv);
+ ret = is31fl32xx_init_regs(priv);
if (ret)
return ret;
@@ -499,6 +616,7 @@ static void is31fl32xx_remove(struct i2c_client *client)
* even though it is not used for DeviceTree based instantiation.
*/
static const struct i2c_device_id is31fl32xx_id[] = {
+ { "is31fl3293" },
{ "is31fl3236" },
{ "is31fl3236a" },
{ "is31fl3235" },
diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c
index c319ff4d70b2..1d64ceb5ac85 100644
--- a/drivers/leds/leds-lm3692x.c
+++ b/drivers/leds/leds-lm3692x.c
@@ -104,6 +104,9 @@
* @regulator: LED supply regulator pointer
* @led_enable: LED sync to be enabled
* @model_id: Current device model ID enumerated
+ * @boost_ctrl: Cached configuration for the boost control register
+ * @brightness_ctrl: Cached configuration for brightness/brightness control
+ * @enabled: Cached enable state of the device
*/
struct lm3692x_led {
struct mutex lock;
diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
index fd447eb7eb15..ea131177de96 100644
--- a/drivers/leds/leds-lp55xx-common.c
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -1204,7 +1204,6 @@ static struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
struct device_node *np,
struct lp55xx_chip *chip)
{
- struct device_node *child;
struct lp55xx_platform_data *pdata;
struct lp55xx_led_config *cfg;
int num_channels;
@@ -1229,12 +1228,10 @@ static struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
pdata->num_channels = num_channels;
cfg->max_channel = chip->cfg->max_channel;
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
ret = lp55xx_parse_logical_led(child, cfg, i);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ERR_PTR(-EINVAL);
- }
i++;
}
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 222d943d826a..28ef4c487367 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -26,6 +26,19 @@ config LEDS_KTD202X
To compile this driver as a module, choose M here: the module
will be called leds-ktd202x.
+config LEDS_LP5812
+ tristate "LED support for Texas Instruments LP5812"
+ depends on I2C
+ help
+ If you say Y here you get support for TI LP5812 LED driver.
+ The LP5812 is a 4x3 matrix RGB LED driver with autonomous
+ animation engine control.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-lp5812.
+
+ If unsure, say N.
+
config LEDS_NCP5623
tristate "LED support for NCP5623"
depends on I2C
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
index a501fd27f179..be45991f63f5 100644
--- a/drivers/leds/rgb/Makefile
+++ b/drivers/leds/rgb/Makefile
@@ -2,6 +2,7 @@
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_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-lp5812.c b/drivers/leds/rgb/leds-lp5812.c
new file mode 100644
index 000000000000..ce6d703641e8
--- /dev/null
+++ b/drivers/leds/rgb/leds-lp5812.c
@@ -0,0 +1,642 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LP5812 LED driver
+ *
+ * Copyright (C) 2025 Texas Instruments
+ *
+ * Author: Jared Zhou <jared-zhou@ti.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#include "leds-lp5812.h"
+
+static const struct lp5812_mode_mapping chip_mode_map[] = {
+ {"direct_mode", 0, 0, 0, 0, 0, 0},
+ {"tcm:1:0", 1, 0, 0, 0, 0, 0},
+ {"tcm:1:1", 1, 1, 0, 0, 0, 0},
+ {"tcm:1:2", 1, 2, 0, 0, 0, 0},
+ {"tcm:1:3", 1, 3, 0, 0, 0, 0},
+ {"tcm:2:0:1", 2, 0, 1, 0, 0, 0},
+ {"tcm:2:0:2", 2, 0, 2, 0, 0, 0},
+ {"tcm:2:0:3", 2, 0, 3, 0, 0, 0},
+ {"tcm:2:1:2", 2, 1, 2, 0, 0, 0},
+ {"tcm:2:1:3", 2, 1, 3, 0, 0, 0},
+ {"tcm:2:2:3", 2, 2, 3, 0, 0, 0},
+ {"tcm:3:0:1:2", 3, 0, 1, 2, 0, 0},
+ {"tcm:3:0:1:3", 3, 0, 1, 3, 0, 0},
+ {"tcm:3:0:2:3", 3, 0, 2, 3, 0, 0},
+ {"tcm:4:0:1:2:3", 4, 0, 1, 2, 3, 0},
+ {"mix:1:0:1", 5, 1, 0, 0, 0, 0},
+ {"mix:1:0:2", 5, 2, 0, 0, 0, 0},
+ {"mix:1:0:3", 5, 3, 0, 0, 0, 0},
+ {"mix:1:1:0", 5, 0, 0, 0, 0, 1},
+ {"mix:1:1:2", 5, 2, 0, 0, 0, 1},
+ {"mix:1:1:3", 5, 3, 0, 0, 0, 1},
+ {"mix:1:2:0", 5, 0, 0, 0, 0, 2},
+ {"mix:1:2:1", 5, 1, 0, 0, 0, 2},
+ {"mix:1:2:3", 5, 3, 0, 0, 0, 2},
+ {"mix:1:3:0", 5, 0, 0, 0, 0, 3},
+ {"mix:1:3:1", 5, 1, 0, 0, 0, 3},
+ {"mix:1:3:2", 5, 2, 0, 0, 0, 3},
+ {"mix:2:0:1:2", 6, 1, 2, 0, 0, 0},
+ {"mix:2:0:1:3", 6, 1, 3, 0, 0, 0},
+ {"mix:2:0:2:3", 6, 2, 3, 0, 0, 0},
+ {"mix:2:1:0:2", 6, 0, 2, 0, 0, 1},
+ {"mix:2:1:0:3", 6, 0, 3, 0, 0, 1},
+ {"mix:2:1:2:3", 6, 2, 3, 0, 0, 1},
+ {"mix:2:2:0:1", 6, 0, 1, 0, 0, 2},
+ {"mix:2:2:0:3", 6, 0, 3, 0, 0, 2},
+ {"mix:2:2:1:3", 6, 1, 3, 0, 0, 2},
+ {"mix:2:3:0:1", 6, 0, 1, 0, 0, 3},
+ {"mix:2:3:0:2", 6, 0, 2, 0, 0, 3},
+ {"mix:2:3:1:2", 6, 1, 2, 0, 0, 3},
+ {"mix:3:0:1:2:3", 7, 1, 2, 3, 0, 0},
+ {"mix:3:1:0:2:3", 7, 0, 2, 3, 0, 1},
+ {"mix:3:2:0:1:3", 7, 0, 1, 3, 0, 2},
+ {"mix:3:3:0:1:2", 7, 0, 1, 2, 0, 3}
+};
+
+static int lp5812_write(struct lp5812_chip *chip, u16 reg, u8 val)
+{
+ struct device *dev = &chip->client->dev;
+ struct i2c_msg msg;
+ u8 buf[LP5812_DATA_LENGTH];
+ u8 reg_addr_bit8_9;
+ int ret;
+
+ /* Extract register address bits 9 and 8 for Address Byte 1 */
+ reg_addr_bit8_9 = (reg >> LP5812_REG_ADDR_HIGH_SHIFT) & LP5812_REG_ADDR_BIT_8_9_MASK;
+
+ /* Prepare payload: Address Byte 2 (bits [7:0]) and value to write */
+ buf[LP5812_DATA_BYTE_0_IDX] = (u8)(reg & LP5812_REG_ADDR_LOW_MASK);
+ buf[LP5812_DATA_BYTE_1_IDX] = val;
+
+ /* Construct I2C message for a write operation */
+ msg.addr = (chip->client->addr << LP5812_CHIP_ADDR_SHIFT) | reg_addr_bit8_9;
+ msg.flags = 0;
+ msg.len = sizeof(buf);
+ msg.buf = buf;
+
+ ret = i2c_transfer(chip->client->adapter, &msg, 1);
+ if (ret == 1)
+ return 0;
+
+ dev_err(dev, "I2C write error, ret=%d\n", ret);
+ return ret < 0 ? ret : -EIO;
+}
+
+static int lp5812_read(struct lp5812_chip *chip, u16 reg, u8 *val)
+{
+ struct device *dev = &chip->client->dev;
+ struct i2c_msg msgs[LP5812_READ_MSG_LENGTH];
+ u8 ret_val;
+ u8 reg_addr_bit8_9;
+ u8 converted_reg;
+ int ret;
+
+ /* Extract register address bits 9 and 8 for Address Byte 1 */
+ reg_addr_bit8_9 = (reg >> LP5812_REG_ADDR_HIGH_SHIFT) & LP5812_REG_ADDR_BIT_8_9_MASK;
+
+ /* Lower 8 bits go in Address Byte 2 */
+ converted_reg = (u8)(reg & LP5812_REG_ADDR_LOW_MASK);
+
+ /* Prepare I2C write message to set register address */
+ msgs[LP5812_MSG_0_IDX].addr =
+ (chip->client->addr << LP5812_CHIP_ADDR_SHIFT) | reg_addr_bit8_9;
+ msgs[LP5812_MSG_0_IDX].flags = 0;
+ msgs[LP5812_MSG_0_IDX].len = 1;
+ msgs[LP5812_MSG_0_IDX].buf = &converted_reg;
+
+ /* Prepare I2C read message to retrieve register value */
+ msgs[LP5812_MSG_1_IDX].addr =
+ (chip->client->addr << LP5812_CHIP_ADDR_SHIFT) | reg_addr_bit8_9;
+ msgs[LP5812_MSG_1_IDX].flags = I2C_M_RD;
+ msgs[LP5812_MSG_1_IDX].len = 1;
+ msgs[LP5812_MSG_1_IDX].buf = &ret_val;
+
+ ret = i2c_transfer(chip->client->adapter, msgs, LP5812_READ_MSG_LENGTH);
+ if (ret == LP5812_READ_MSG_LENGTH) {
+ *val = ret_val;
+ return 0;
+ }
+
+ dev_err(dev, "I2C read error, ret=%d\n", ret);
+ *val = 0;
+ return ret < 0 ? ret : -EIO;
+}
+
+static int lp5812_read_tsd_config_status(struct lp5812_chip *chip, u8 *reg_val)
+{
+ return lp5812_read(chip, LP5812_TSD_CONFIG_STATUS, reg_val);
+}
+
+static int lp5812_update_regs_config(struct lp5812_chip *chip)
+{
+ u8 reg_val;
+ int ret;
+
+ ret = lp5812_write(chip, LP5812_CMD_UPDATE, LP5812_UPDATE_CMD_VAL);
+ if (ret)
+ return ret;
+
+ ret = lp5812_read_tsd_config_status(chip, &reg_val);
+ if (ret)
+ return ret;
+
+ return reg_val & LP5812_CFG_ERR_STATUS_MASK;
+}
+
+static ssize_t parse_drive_mode(struct lp5812_chip *chip, const char *str)
+{
+ int i;
+
+ chip->drive_mode.bits.mix_sel_led_0 = false;
+ chip->drive_mode.bits.mix_sel_led_1 = false;
+ chip->drive_mode.bits.mix_sel_led_2 = false;
+ chip->drive_mode.bits.mix_sel_led_3 = false;
+
+ if (sysfs_streq(str, LP5812_MODE_DIRECT_NAME)) {
+ chip->drive_mode.bits.led_mode = LP5812_MODE_DIRECT_VALUE;
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(chip_mode_map); i++) {
+ if (!sysfs_streq(str, chip_mode_map[i].mode_name))
+ continue;
+
+ chip->drive_mode.bits.led_mode = chip_mode_map[i].mode;
+ chip->scan_order.bits.order0 = chip_mode_map[i].scan_order_0;
+ chip->scan_order.bits.order1 = chip_mode_map[i].scan_order_1;
+ chip->scan_order.bits.order2 = chip_mode_map[i].scan_order_2;
+ chip->scan_order.bits.order3 = chip_mode_map[i].scan_order_3;
+
+ switch (chip_mode_map[i].selection_led) {
+ case LP5812_MODE_MIX_SELECT_LED_0:
+ chip->drive_mode.bits.mix_sel_led_0 = true;
+ break;
+ case LP5812_MODE_MIX_SELECT_LED_1:
+ chip->drive_mode.bits.mix_sel_led_1 = true;
+ break;
+ case LP5812_MODE_MIX_SELECT_LED_2:
+ chip->drive_mode.bits.mix_sel_led_2 = true;
+ break;
+ case LP5812_MODE_MIX_SELECT_LED_3:
+ chip->drive_mode.bits.mix_sel_led_3 = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int lp5812_set_drive_mode_scan_order(struct lp5812_chip *chip)
+{
+ u8 val;
+ int ret;
+
+ val = chip->drive_mode.val;
+ ret = lp5812_write(chip, LP5812_DEV_CONFIG1, val);
+ if (ret)
+ return ret;
+
+ val = chip->scan_order.val;
+ ret = lp5812_write(chip, LP5812_DEV_CONFIG2, val);
+
+ return ret;
+}
+
+static int lp5812_set_led_mode(struct lp5812_chip *chip, int led_number,
+ enum control_mode mode)
+{
+ u8 reg_val;
+ u16 reg;
+ int ret;
+
+ /*
+ * Select device configuration register.
+ * Reg3 for LED_0–LED_3, LED_A0–A2, LED_B0
+ * Reg4 for LED_B1–B2, LED_C0–C2, LED_D0–D2
+ */
+ if (led_number < LP5812_NUMBER_LED_IN_REG)
+ reg = LP5812_DEV_CONFIG3;
+ else
+ reg = LP5812_DEV_CONFIG4;
+
+ ret = lp5812_read(chip, reg, &reg_val);
+ if (ret)
+ return ret;
+
+ if (mode == LP5812_MODE_MANUAL)
+ reg_val &= ~(LP5812_ENABLE << (led_number % LP5812_NUMBER_LED_IN_REG));
+ else
+ reg_val |= (LP5812_ENABLE << (led_number % LP5812_NUMBER_LED_IN_REG));
+
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret)
+ return ret;
+
+ ret = lp5812_update_regs_config(chip);
+
+ return ret;
+}
+
+static int lp5812_manual_dc_pwm_control(struct lp5812_chip *chip, int led_number,
+ u8 val, enum dimming_type dimming_type)
+{
+ u16 led_base_reg;
+ int ret;
+
+ if (dimming_type == LP5812_DIMMING_ANALOG)
+ led_base_reg = LP5812_MANUAL_DC_BASE;
+ else
+ led_base_reg = LP5812_MANUAL_PWM_BASE;
+
+ ret = lp5812_write(chip, led_base_reg + led_number, val);
+
+ return ret;
+}
+
+static int lp5812_multicolor_brightness(struct lp5812_led *led)
+{
+ struct lp5812_chip *chip = led->chip;
+ int ret, i;
+
+ guard(mutex)(&chip->lock);
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ ret = lp5812_manual_dc_pwm_control(chip, led->mc_cdev.subled_info[i].channel,
+ led->mc_cdev.subled_info[i].brightness,
+ LP5812_DIMMING_PWM);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lp5812_led_brightness(struct lp5812_led *led)
+{
+ struct lp5812_chip *chip = led->chip;
+ struct lp5812_led_config *led_cfg;
+ int ret;
+
+ led_cfg = &chip->led_config[led->chan_nr];
+
+ guard(mutex)(&chip->lock);
+ ret = lp5812_manual_dc_pwm_control(chip, led_cfg->led_id[0],
+ led->brightness, LP5812_DIMMING_PWM);
+
+ return ret;
+}
+
+static int lp5812_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct lp5812_led *led = container_of(cdev, struct lp5812_led, cdev);
+
+ led->brightness = (u8)brightness;
+
+ return lp5812_led_brightness(led);
+}
+
+static int lp5812_set_mc_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
+ struct lp5812_led *led = container_of(mc_dev, struct lp5812_led, mc_cdev);
+
+ led_mc_calc_color_components(&led->mc_cdev, brightness);
+
+ return lp5812_multicolor_brightness(led);
+}
+
+static int lp5812_init_led(struct lp5812_led *led, struct lp5812_chip *chip, int chan)
+{
+ struct device *dev = &chip->client->dev;
+ struct mc_subled *mc_led_info;
+ struct led_classdev *led_cdev;
+ int i, ret;
+
+ if (chip->led_config[chan].name) {
+ led->cdev.name = chip->led_config[chan].name;
+ } else {
+ led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:channel%d",
+ chip->label ? : chip->client->name, chan);
+ if (!led->cdev.name)
+ return -ENOMEM;
+ }
+
+ if (!chip->led_config[chan].is_sc_led) {
+ mc_led_info = devm_kcalloc(dev, chip->led_config[chan].num_colors,
+ sizeof(*mc_led_info), GFP_KERNEL);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ led_cdev = &led->mc_cdev.led_cdev;
+ led_cdev->name = led->cdev.name;
+ led_cdev->brightness_set_blocking = lp5812_set_mc_brightness;
+ led->mc_cdev.num_colors = chip->led_config[chan].num_colors;
+
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ mc_led_info[i].color_index = chip->led_config[chan].color_id[i];
+ mc_led_info[i].channel = chip->led_config[chan].led_id[i];
+ }
+
+ led->mc_cdev.subled_info = mc_led_info;
+ } else {
+ led->cdev.brightness_set_blocking = lp5812_set_brightness;
+ }
+
+ led->chan_nr = chan;
+
+ if (chip->led_config[chan].is_sc_led) {
+ ret = devm_led_classdev_register(dev, &led->cdev);
+ if (ret == 0)
+ led->cdev.dev->platform_data = led;
+ } else {
+ ret = devm_led_classdev_multicolor_register(dev, &led->mc_cdev);
+ if (ret == 0)
+ led->mc_cdev.led_cdev.dev->platform_data = led;
+ }
+
+ return ret;
+}
+
+static int lp5812_register_leds(struct lp5812_led *leds, struct lp5812_chip *chip)
+{
+ struct lp5812_led *led;
+ int num_channels = chip->num_channels;
+ u8 reg_val;
+ u16 reg;
+ int ret, i, j;
+
+ for (i = 0; i < num_channels; i++) {
+ led = &leds[i];
+ ret = lp5812_init_led(led, chip, i);
+ if (ret)
+ goto err_init_led;
+
+ led->chip = chip;
+
+ for (j = 0; j < chip->led_config[i].num_colors; j++) {
+ ret = lp5812_write(chip,
+ LP5812_AUTO_DC_BASE + chip->led_config[i].led_id[j],
+ chip->led_config[i].max_current[j]);
+ if (ret)
+ goto err_init_led;
+
+ ret = lp5812_manual_dc_pwm_control(chip, chip->led_config[i].led_id[j],
+ chip->led_config[i].max_current[j],
+ LP5812_DIMMING_ANALOG);
+ if (ret)
+ goto err_init_led;
+
+ ret = lp5812_set_led_mode(chip, chip->led_config[i].led_id[j],
+ LP5812_MODE_MANUAL);
+ if (ret)
+ goto err_init_led;
+
+ reg = (chip->led_config[i].led_id[j] < LP5812_NUMBER_LED_IN_REG) ?
+ LP5812_LED_EN_1 : LP5812_LED_EN_2;
+
+ ret = lp5812_read(chip, reg, &reg_val);
+ if (ret)
+ goto err_init_led;
+
+ reg_val |= (LP5812_ENABLE << (chip->led_config[i].led_id[j] %
+ LP5812_NUMBER_LED_IN_REG));
+
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret)
+ goto err_init_led;
+ }
+ }
+
+ return 0;
+
+err_init_led:
+ return ret;
+}
+
+static int lp5812_init_device(struct lp5812_chip *chip)
+{
+ int ret;
+
+ usleep_range(LP5812_WAIT_DEVICE_STABLE_MIN, LP5812_WAIT_DEVICE_STABLE_MAX);
+
+ ret = lp5812_write(chip, LP5812_REG_ENABLE, LP5812_ENABLE);
+ if (ret) {
+ dev_err(&chip->client->dev, "failed to enable LP5812 device\n");
+ return ret;
+ }
+
+ ret = lp5812_write(chip, LP5812_DEV_CONFIG12, LP5812_LSD_LOD_START_UP);
+ if (ret) {
+ dev_err(&chip->client->dev, "failed to configure device safety thresholds\n");
+ return ret;
+ }
+
+ ret = parse_drive_mode(chip, chip->scan_mode);
+ if (ret)
+ return ret;
+
+ ret = lp5812_set_drive_mode_scan_order(chip);
+ if (ret)
+ return ret;
+
+ ret = lp5812_update_regs_config(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "failed to apply configuration updates\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void lp5812_deinit_device(struct lp5812_chip *chip)
+{
+ lp5812_write(chip, LP5812_LED_EN_1, LP5812_DISABLE);
+ lp5812_write(chip, LP5812_LED_EN_2, LP5812_DISABLE);
+ lp5812_write(chip, LP5812_REG_ENABLE, LP5812_DISABLE);
+}
+
+static int lp5812_parse_led_channel(struct device_node *np,
+ struct lp5812_led_config *cfg,
+ int color_number)
+{
+ int color_id, reg, ret;
+ u32 max_cur;
+
+ ret = of_property_read_u32(np, "reg", &reg);
+ if (ret)
+ return ret;
+
+ cfg->led_id[color_number] = reg;
+
+ ret = of_property_read_u32(np, "led-max-microamp", &max_cur);
+ if (ret)
+ max_cur = 0;
+ /* Convert microamps to driver units */
+ cfg->max_current[color_number] = max_cur / 100;
+
+ ret = of_property_read_u32(np, "color", &color_id);
+ if (ret)
+ color_id = 0;
+ cfg->color_id[color_number] = color_id;
+
+ return 0;
+}
+
+static int lp5812_parse_led(struct device_node *np,
+ struct lp5812_led_config *cfg,
+ int led_index)
+{
+ int num_colors, ret;
+
+ of_property_read_string(np, "label", &cfg[led_index].name);
+
+ ret = of_property_read_u32(np, "reg", &cfg[led_index].chan_nr);
+ if (ret)
+ return ret;
+
+ num_colors = 0;
+ for_each_available_child_of_node_scoped(np, child) {
+ ret = lp5812_parse_led_channel(child, &cfg[led_index], num_colors);
+ if (ret)
+ return ret;
+
+ num_colors++;
+ }
+
+ if (num_colors == 0) {
+ ret = lp5812_parse_led_channel(np, &cfg[led_index], 0);
+ if (ret)
+ return ret;
+
+ num_colors = 1;
+ cfg[led_index].is_sc_led = true;
+ } else {
+ cfg[led_index].is_sc_led = false;
+ }
+
+ cfg[led_index].num_colors = num_colors;
+
+ return 0;
+}
+
+static int lp5812_of_probe(struct device *dev,
+ struct device_node *np,
+ struct lp5812_chip *chip)
+{
+ struct lp5812_led_config *cfg;
+ int num_channels, i = 0, ret;
+
+ num_channels = of_get_available_child_count(np);
+ if (num_channels == 0) {
+ dev_err(dev, "no LED channels\n");
+ return -EINVAL;
+ }
+
+ cfg = devm_kcalloc(dev, num_channels, sizeof(*cfg), GFP_KERNEL);
+ if (!cfg)
+ return -ENOMEM;
+
+ chip->led_config = &cfg[0];
+ chip->num_channels = num_channels;
+
+ for_each_available_child_of_node_scoped(np, child) {
+ ret = lp5812_parse_led(child, cfg, i);
+ if (ret)
+ return -EINVAL;
+ i++;
+ }
+
+ ret = of_property_read_string(np, "ti,scan-mode", &chip->scan_mode);
+ if (ret)
+ chip->scan_mode = LP5812_MODE_DIRECT_NAME;
+
+ of_property_read_string(np, "label", &chip->label);
+
+ return 0;
+}
+
+static int lp5812_probe(struct i2c_client *client)
+{
+ struct lp5812_chip *chip;
+ struct device_node *np = dev_of_node(&client->dev);
+ struct lp5812_led *leds;
+ int ret;
+
+ if (!np)
+ return -EINVAL;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ ret = lp5812_of_probe(&client->dev, np, chip);
+ if (ret)
+ return ret;
+
+ leds = devm_kcalloc(&client->dev, chip->num_channels, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ chip->client = client;
+ mutex_init(&chip->lock);
+ i2c_set_clientdata(client, chip);
+
+ ret = lp5812_init_device(chip);
+ if (ret)
+ return ret;
+
+ ret = lp5812_register_leds(leds, chip);
+ if (ret)
+ goto err_out;
+
+ return 0;
+
+err_out:
+ lp5812_deinit_device(chip);
+ return ret;
+}
+
+static void lp5812_remove(struct i2c_client *client)
+{
+ struct lp5812_chip *chip = i2c_get_clientdata(client);
+
+ lp5812_deinit_device(chip);
+}
+
+static const struct of_device_id of_lp5812_match[] = {
+ { .compatible = "ti,lp5812" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_lp5812_match);
+
+static struct i2c_driver lp5812_driver = {
+ .driver = {
+ .name = "lp5812",
+ .of_match_table = of_lp5812_match,
+ },
+ .probe = lp5812_probe,
+ .remove = lp5812_remove,
+};
+module_i2c_driver(lp5812_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP5812 LED Driver");
+MODULE_AUTHOR("Jared Zhou <jared-zhou@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/rgb/leds-lp5812.h b/drivers/leds/rgb/leds-lp5812.h
new file mode 100644
index 000000000000..d8728ecd90b3
--- /dev/null
+++ b/drivers/leds/rgb/leds-lp5812.h
@@ -0,0 +1,172 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * LP5812 Driver Header
+ *
+ * Copyright (C) 2025 Texas Instruments
+ *
+ * Author: Jared Zhou <jared-zhou@ti.com>
+ */
+
+#ifndef _LP5812_H_
+#define _LP5812_H_
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+#define LP5812_REG_ENABLE 0x0000
+#define LP5812_REG_RESET 0x0023
+#define LP5812_DEV_CONFIG0 0x0001
+#define LP5812_DEV_CONFIG1 0x0002
+#define LP5812_DEV_CONFIG2 0x0003
+#define LP5812_DEV_CONFIG3 0x0004
+#define LP5812_DEV_CONFIG4 0x0005
+#define LP5812_DEV_CONFIG5 0x0006
+#define LP5812_DEV_CONFIG6 0x0007
+#define LP5812_DEV_CONFIG7 0x0008
+#define LP5812_DEV_CONFIG8 0x0009
+#define LP5812_DEV_CONFIG9 0x000A
+#define LP5812_DEV_CONFIG10 0x000B
+#define LP5812_DEV_CONFIG11 0x000c
+#define LP5812_DEV_CONFIG12 0x000D
+#define LP5812_CMD_UPDATE 0x0010
+#define LP5812_LED_EN_1 0x0020
+#define LP5812_LED_EN_2 0x0021
+#define LP5812_FAULT_CLEAR 0x0022
+#define LP5812_MANUAL_DC_BASE 0x0030
+#define LP5812_AUTO_DC_BASE 0x0050
+#define LP5812_MANUAL_PWM_BASE 0x0040
+
+#define LP5812_TSD_CONFIG_STATUS 0x0300
+#define LP5812_LOD_STATUS 0x0301
+#define LP5812_LSD_STATUS 0x0303
+
+#define LP5812_ENABLE 0x01
+#define LP5812_DISABLE 0x00
+#define FAULT_CLEAR_ALL 0x07
+#define TSD_CLEAR_VAL 0x04
+#define LSD_CLEAR_VAL 0x02
+#define LOD_CLEAR_VAL 0x01
+#define LP5812_RESET 0x66
+#define LP5812_DEV_CONFIG12_DEFAULT 0x08
+
+#define LP5812_UPDATE_CMD_VAL 0x55
+#define LP5812_REG_ADDR_HIGH_SHIFT 8
+#define LP5812_REG_ADDR_BIT_8_9_MASK 0x03
+#define LP5812_REG_ADDR_LOW_MASK 0xFF
+#define LP5812_CHIP_ADDR_SHIFT 2
+#define LP5812_DATA_LENGTH 2
+#define LP5812_DATA_BYTE_0_IDX 0
+#define LP5812_DATA_BYTE_1_IDX 1
+
+#define LP5812_READ_MSG_LENGTH 2
+#define LP5812_MSG_0_IDX 0
+#define LP5812_MSG_1_IDX 1
+#define LP5812_CFG_ERR_STATUS_MASK 0x01
+#define LP5812_CFG_TSD_STATUS_SHIFT 1
+#define LP5812_CFG_TSD_STATUS_MASK 0x01
+
+#define LP5812_FAULT_CLEAR_LOD 0
+#define LP5812_FAULT_CLEAR_LSD 1
+#define LP5812_FAULT_CLEAR_TSD 2
+#define LP5812_FAULT_CLEAR_ALL 3
+#define LP5812_NUMBER_LED_IN_REG 8
+
+#define LP5812_WAIT_DEVICE_STABLE_MIN 1000
+#define LP5812_WAIT_DEVICE_STABLE_MAX 1100
+
+#define LP5812_LSD_LOD_START_UP 0x0B
+#define LP5812_MODE_NAME_MAX_LEN 20
+#define LP5812_MODE_DIRECT_NAME "direct_mode"
+#define LP5812_MODE_DIRECT_VALUE 0
+#define LP5812_MODE_MIX_SELECT_LED_0 0
+#define LP5812_MODE_MIX_SELECT_LED_1 1
+#define LP5812_MODE_MIX_SELECT_LED_2 2
+#define LP5812_MODE_MIX_SELECT_LED_3 3
+
+enum control_mode {
+ LP5812_MODE_MANUAL = 0,
+ LP5812_MODE_AUTONOMOUS
+};
+
+enum dimming_type {
+ LP5812_DIMMING_ANALOG,
+ LP5812_DIMMING_PWM
+};
+
+union lp5812_scan_order {
+ struct {
+ u8 order0:2;
+ u8 order1:2;
+ u8 order2:2;
+ u8 order3:2;
+ } bits;
+ u8 val;
+};
+
+union lp5812_drive_mode {
+ struct {
+ u8 mix_sel_led_0:1;
+ u8 mix_sel_led_1:1;
+ u8 mix_sel_led_2:1;
+ u8 mix_sel_led_3:1;
+ u8 led_mode:3;
+ u8 pwm_fre:1;
+ } bits;
+ u8 val;
+};
+
+struct lp5812_reg {
+ u16 addr;
+ union {
+ u8 val;
+ u8 mask;
+ u8 shift;
+ };
+};
+
+struct lp5812_mode_mapping {
+ char mode_name[LP5812_MODE_NAME_MAX_LEN];
+ u8 mode;
+ u8 scan_order_0;
+ u8 scan_order_1;
+ u8 scan_order_2;
+ u8 scan_order_3;
+ u8 selection_led;
+};
+
+struct lp5812_led_config {
+ bool is_sc_led;
+ const char *name;
+ u8 color_id[LED_COLOR_ID_MAX];
+ u32 max_current[LED_COLOR_ID_MAX];
+ int chan_nr;
+ int num_colors;
+ int led_id[LED_COLOR_ID_MAX];
+};
+
+struct lp5812_chip {
+ u8 num_channels;
+ struct i2c_client *client;
+ struct mutex lock; /* Protects register access */
+ struct lp5812_led_config *led_config;
+ const char *label;
+ const char *scan_mode;
+ union lp5812_scan_order scan_order;
+ union lp5812_drive_mode drive_mode;
+};
+
+struct lp5812_led {
+ u8 brightness;
+ int chan_nr;
+ struct led_classdev cdev;
+ struct led_classdev_mc mc_cdev;
+ struct lp5812_chip *chip;
+};
+
+#endif /*_LP5812_H_*/
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index 72da0bf469ad..f54851dfb42f 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -369,7 +369,7 @@ static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern,
{
unsigned int idx;
u16 val;
- int i;
+ int i, ret;
idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size,
0, len, 0);
@@ -379,8 +379,10 @@ static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern,
for (i = 0; i < len; i++) {
val = pattern[i].brightness;
- regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i),
- &val, sizeof(val));
+ ret = regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i),
+ &val, sizeof(val));
+ if (ret)
+ return ret;
}
bitmap_set(lpg->lut_bitmap, idx, len);