summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-16 11:15:19 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-16 11:15:19 -0800
commit2228d9cf7a562d1b0ca86bd529f6acb94f4bb80f (patch)
tree9a11ea72e8f20a422f463faa31a10a8d3d398cbe
parent4bfa4a54b02029b3996b9f9021f5f745c71e9064 (diff)
parentb2c87f5e98cd88095dbc6802197526703d5e4e48 (diff)
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
-rw-r--r--Documentation/devicetree/bindings/leds/ams,as3668.yaml74
-rw-r--r--Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml2
-rw-r--r--Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt1
-rw-r--r--Documentation/devicetree/bindings/leds/leds-lm3697.txt73
-rw-r--r--Documentation/devicetree/bindings/leds/leds-lp5860.yaml111
-rw-r--r--Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml1
-rw-r--r--Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml1
-rw-r--r--Documentation/devicetree/bindings/leds/ti,lm3697.yaml125
-rw-r--r--Documentation/devicetree/bindings/leds/ti,lp5812.yaml246
-rw-r--r--Documentation/leds/index.rst1
-rw-r--r--Documentation/leds/leds-lp5812.rst50
-rw-r--r--MAINTAINERS18
-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
-rw-r--r--include/linux/leds-expresswire.h3
25 files changed, 1892 insertions, 166 deletions
diff --git a/Documentation/devicetree/bindings/leds/ams,as3668.yaml b/Documentation/devicetree/bindings/leds/ams,as3668.yaml
new file mode 100644
index 000000000000..d1d73782da55
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/ams,as3668.yaml
@@ -0,0 +1,74 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/ams,as3668.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Osram 4-channel i2c LED driver
+
+maintainers:
+ - Lukas Timmermann <linux@timmermann.space>
+
+description:
+ This IC can drive up to four separate LEDs.
+ Having four channels suggests it could be used with a single RGBW LED.
+
+properties:
+ compatible:
+ const: ams,as3668
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^led@[0-3]$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+required:
+ - compatible
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@42 {
+ compatible = "ams,as3668";
+ reg = <0x42>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0x0>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@1 {
+ reg = <0x1>;
+ function = LED_FUNCTION_STATUS;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+ };
+ };
+
diff --git a/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml b/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml
index bb40bb9e036e..7bfc3d807aca 100644
--- a/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml
+++ b/Documentation/devicetree/bindings/leds/leds-class-multicolor.yaml
@@ -21,7 +21,7 @@ description: |
properties:
$nodename:
- pattern: "^multi-led(@[0-9a-f])?$"
+ pattern: "^multi-led(@[0-9a-f]|-[0-9]+)?$"
color:
description: |
diff --git a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
index 926c2117942c..7082ed186dd9 100644
--- a/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
+++ b/Documentation/devicetree/bindings/leds/leds-is31fl32xx.txt
@@ -10,6 +10,7 @@ Required properties:
issi,is31fl3235
issi,is31fl3218
issi,is31fl3216
+ issi,is31fl3293
si-en,sn3218
si-en,sn3216
- reg: I2C slave address
diff --git a/Documentation/devicetree/bindings/leds/leds-lm3697.txt b/Documentation/devicetree/bindings/leds/leds-lm3697.txt
deleted file mode 100644
index 221b37b6049b..000000000000
--- a/Documentation/devicetree/bindings/leds/leds-lm3697.txt
+++ /dev/null
@@ -1,73 +0,0 @@
-* Texas Instruments - LM3697 Highly Efficient White LED Driver
-
-The LM3697 11-bit LED driver provides high-
-performance backlight dimming for 1, 2, or 3 series
-LED strings while delivering up to 90% efficiency.
-
-This device is suitable for display and keypad lighting
-
-Required properties:
- - compatible:
- "ti,lm3697"
- - reg : I2C slave address
- - #address-cells : 1
- - #size-cells : 0
-
-Optional properties:
- - enable-gpios : GPIO pin to enable/disable the device
- - vled-supply : LED supply
-
-Required child properties:
- - reg : 0 - LED is Controlled by bank A
- 1 - LED is Controlled by bank B
- - led-sources : Indicates which HVLED string is associated to which
- control bank. This is a zero based property so
- HVLED1 = 0, HVLED2 = 1, HVLED3 = 2.
- Additional information is contained
- in Documentation/devicetree/bindings/leds/common.txt
-
-Optional child properties:
- - ti,brightness-resolution - see Documentation/devicetree/bindings/mfd/ti-lmu.txt
- - ramp-up-us: see Documentation/devicetree/bindings/mfd/ti-lmu.txt
- - ramp-down-us: see Documentation/devicetree/bindings/mfd/ti-lmu.txt
- - label : see Documentation/devicetree/bindings/leds/common.txt
- - linux,default-trigger :
- see Documentation/devicetree/bindings/leds/common.txt
-
-Example:
-
-HVLED string 1 and 3 are controlled by control bank A and HVLED 2 string is
-controlled by control bank B.
-
-led-controller@36 {
- compatible = "ti,lm3697";
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <0x36>;
-
- enable-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>;
- vled-supply = <&vbatt>;
-
- led@0 {
- reg = <0>;
- led-sources = <0 2>;
- ti,brightness-resolution = <2047>;
- ramp-up-us = <5000>;
- ramp-down-us = <1000>;
- label = "white:first_backlight_cluster";
- linux,default-trigger = "backlight";
- };
-
- led@1 {
- reg = <1>;
- led-sources = <1>;
- ti,brightness-resolution = <255>;
- ramp-up-us = <500>;
- ramp-down-us = <1000>;
- label = "white:second_backlight_cluster";
- linux,default-trigger = "backlight";
- };
-}
-
-For more product information please see the link below:
-https://www.ti.com/lit/ds/symlink/lm3697.pdf
diff --git a/Documentation/devicetree/bindings/leds/leds-lp5860.yaml b/Documentation/devicetree/bindings/leds/leds-lp5860.yaml
new file mode 100644
index 000000000000..1ccba4854159
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-lp5860.yaml
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/leds-lp5860.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: LED driver for LP5860 RGB LED from Texas Instruments.
+
+maintainers:
+ - Steffen Trumtrar <kernel@pengutronix.de>
+
+description: |
+ The LP5860 is multi-channel, I2C and SPI RGB LED Driver that can group RGB LEDs
+ into a LED group or control them individually.
+
+ For more product information please see the link below:
+ https://www.ti.com/lit/ds/symlink/lp5860.pdf
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - ti,lp5860
+
+ reg:
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ '^multi-led@[0-9a-f]+$':
+ type: object
+ $ref: leds-class-multicolor.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 198
+ description:
+ This property denotes the LED module number that is used
+ for the child node.
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ patternProperties:
+ "^led@[0-9a-f]+$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ required:
+ - reg
+
+required:
+ - compatible
+ - reg
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@0 {
+ compatible = "ti,lp5860";
+ reg = <0x0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ multi-led@0 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x0>;
+ color = <LED_COLOR_ID_RGB>;
+
+ led@0 {
+ reg = <0x0>;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@1 {
+ reg = <0x1>;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+
+ led@2 {
+ reg = <0x2>;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml b/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml
index c4b7e57b2518..3da0fe532e74 100644
--- a/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml
+++ b/Documentation/devicetree/bindings/leds/leds-qcom-lpg.yaml
@@ -43,6 +43,7 @@ properties:
- items:
- enum:
- qcom,pm8550-pwm
+ - qcom,pmh0101-pwm
- const: qcom,pm8350c-pwm
- items:
- enum:
diff --git a/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml b/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml
index 05250aefd385..3bfa24ff58cd 100644
--- a/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml
+++ b/Documentation/devicetree/bindings/leds/qcom,spmi-flash-led.yaml
@@ -29,6 +29,7 @@ properties:
- qcom,pm8150l-flash-led
- qcom,pm8350c-flash-led
- qcom,pm8550-flash-led
+ - qcom,pmh0101-flash-led
- qcom,pmi8998-flash-led
- const: qcom,spmi-flash-led
diff --git a/Documentation/devicetree/bindings/leds/ti,lm3697.yaml b/Documentation/devicetree/bindings/leds/ti,lm3697.yaml
new file mode 100644
index 000000000000..a9f839470a84
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/ti,lm3697.yaml
@@ -0,0 +1,125 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/ti,lm3697.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TI LM3697 Highly Efficient White LED Driver
+
+maintainers:
+ - Dan Murphy <dmurphy@ti.com>
+
+description: >
+ The LM3697 11-bit LED driver provides high-performance backlight dimming for
+ 1, 2, or 3 series LED strings while delivering up to 90% efficiency.
+
+ This device is suitable for display and keypad lighting.
+
+properties:
+ compatible:
+ const: ti,lm3697
+
+ reg:
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ enable-gpios:
+ description: GPIO pin to enable or disable the device.
+ maxItems: 1
+
+ vled-supply:
+ description: LED supply for the device.
+
+patternProperties:
+ '^led@[01]$':
+ description: LED control bank nodes.
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ description: Control bank selection (0 = bank A, 1 = bank B).
+ maximum: 1
+
+ led-sources:
+ description: >
+ HVLED strings associated with this control bank:
+
+ 0 - HVLED1
+ 1 - HVLED2
+ 2 - HVLED3
+ minItems: 1
+ maxItems: 3
+ items:
+ maximum: 2
+
+ ti,brightness-resolution:
+ description: Brightness resolution for the LED string.
+ $ref: /schemas/types.yaml#/definitions/uint32
+ maximum: 2047
+
+ ramp-up-us:
+ description: Ramp-up time in microseconds.
+ minimum: 117
+ maximum: 2048
+
+ ramp-down-us:
+ description: Ramp-down time in microseconds.
+ minimum: 117
+ maximum: 2048
+
+ required:
+ - reg
+ - led-sources
+
+required:
+ - compatible
+ - reg
+ - '#address-cells'
+ - '#size-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@36 {
+ compatible = "ti,lm3697";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x36>;
+
+ enable-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>;
+ vled-supply = <&vbatt>;
+
+ led@0 {
+ reg = <0>;
+ led-sources = <0 2>;
+ ti,brightness-resolution = <2047>;
+ ramp-up-us = <500>;
+ ramp-down-us = <1000>;
+ label = "white:first_backlight_cluster";
+ linux,default-trigger = "backlight";
+ };
+
+ led@1 {
+ reg = <1>;
+ led-sources = <1>;
+ ti,brightness-resolution = <255>;
+ ramp-up-us = <500>;
+ ramp-down-us = <1000>;
+ label = "white:second_backlight_cluster";
+ linux,default-trigger = "backlight";
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/leds/ti,lp5812.yaml b/Documentation/devicetree/bindings/leds/ti,lp5812.yaml
new file mode 100644
index 000000000000..de34bff441c7
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/ti,lp5812.yaml
@@ -0,0 +1,246 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/ti,lp5812.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TI LP5812 4x3 Matrix RGB LED Driver with Autonomous Control
+
+maintainers:
+ - Nam Tran <trannamatk@gmail.com>
+
+description: |
+ The LP5812 is a 4x3 matrix RGB LED driver with I2C interface
+ and autonomous animation engine control.
+ For more product information please see the link below:
+ https://www.ti.com/product/LP5812#tech-docs
+
+properties:
+ compatible:
+ const: ti,lp5812
+
+ reg:
+ maxItems: 1
+
+ ti,scan-mode:
+ description: |
+ Selects the LED scan mode of the LP5812. The device supports
+ three modes:
+ - Direct-drive mode (by default if 'ti,scan-mode' is omitted)
+ drives up to 4 LEDs directly by internal current sinks (LED0-LED3).
+ - TCM-drive mode ("tcm:<n>:<order...>") drives up to 12 LEDs
+ (4 RGB) using 1-4 scan multiplexing. The <n> specifies the number
+ of scans (1-4), and <order...> defines the scan order of the outputs.
+ - Mix-drive mode ("mix:<n>:<direct>:<order...>") combines
+ direct-drive and TCM-drive outputs. The <n> specifies the number
+ of scans, <direct> selects the direct-drive outputs, and <order...>
+ defines the scan order.
+ $ref: /schemas/types.yaml#/definitions/string
+ pattern: '^(tcm|mix):[1-4](:[0-3]){1,4}$'
+
+ vcc-supply:
+ description: Regulator providing power to the 'VCC' pin.
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^led@[0-3]$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 3
+
+ required:
+ - reg
+
+ "^multi-led@[4-7]$":
+ type: object
+ $ref: leds-class-multicolor.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 4
+ maximum: 7
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ patternProperties:
+ "^led@[4-9a-f]$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 4
+ maximum: 15
+
+ required:
+ - reg
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@1b {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "ti,lp5812";
+ reg = <0x1b>;
+ ti,scan-mode = "tcm:4:0:1:2:3";
+ vcc-supply = <&vdd_3v3_reg>;
+
+ led@0 {
+ reg = <0x0>;
+ label = "LED0";
+ led-max-microamp = <25500>;
+ };
+
+ led@1 {
+ reg = <0x1>;
+ label = "LED1";
+ led-max-microamp = <25500>;
+ };
+
+ led@2 {
+ reg = <0x2>;
+ label = "LED2";
+ led-max-microamp = <25500>;
+ };
+
+ led@3 {
+ reg = <0x3>;
+ label = "LED3";
+ led-max-microamp = <25500>;
+ };
+
+ multi-led@4 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x4>;
+ color = <LED_COLOR_ID_RGB>;
+ label = "LED_A";
+
+ led@4 {
+ reg = <0x4>;
+ color = <LED_COLOR_ID_GREEN>;
+ led-max-microamp = <25500>;
+ };
+
+ led@5 {
+ reg = <0x5>;
+ color = <LED_COLOR_ID_RED>;
+ led-max-microamp = <25500>;
+ };
+
+ led@6 {
+ reg = <0x6>;
+ color = <LED_COLOR_ID_BLUE>;
+ led-max-microamp = <25500>;
+ };
+ };
+
+ multi-led@5 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x5>;
+ color = <LED_COLOR_ID_RGB>;
+ label = "LED_B";
+
+ led@7 {
+ reg = <0x7>;
+ color = <LED_COLOR_ID_GREEN>;
+ led-max-microamp = <25500>;
+ };
+
+ led@8 {
+ reg = <0x8>;
+ color = <LED_COLOR_ID_RED>;
+ led-max-microamp = <25500>;
+ };
+
+ led@9 {
+ reg = <0x9>;
+ color = <LED_COLOR_ID_BLUE>;
+ led-max-microamp = <25500>;
+ };
+ };
+
+ multi-led@6 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x6>;
+ color = <LED_COLOR_ID_RGB>;
+ label = "LED_C";
+
+ led@a {
+ reg = <0xa>;
+ color = <LED_COLOR_ID_GREEN>;
+ led-max-microamp = <25500>;
+ };
+
+ led@b {
+ reg = <0xb>;
+ color = <LED_COLOR_ID_RED>;
+ led-max-microamp = <25500>;
+ };
+
+ led@c {
+ reg = <0xc>;
+ color = <LED_COLOR_ID_BLUE>;
+ led-max-microamp = <25500>;
+ };
+ };
+
+ multi-led@7 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0x7>;
+ color = <LED_COLOR_ID_RGB>;
+ label = "LED_D";
+
+ led@d {
+ reg = <0xd>;
+ color = <LED_COLOR_ID_GREEN>;
+ led-max-microamp = <25500>;
+ };
+
+ led@e {
+ reg = <0xe>;
+ color = <LED_COLOR_ID_RED>;
+ led-max-microamp = <25500>;
+ };
+
+ led@f {
+ reg = <0xf>;
+ color = <LED_COLOR_ID_BLUE>;
+ led-max-microamp = <25500>;
+ };
+ };
+ };
+ };
+
+...
diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst
index 76fae171039c..bebf44004278 100644
--- a/Documentation/leds/index.rst
+++ b/Documentation/leds/index.rst
@@ -25,6 +25,7 @@ LEDs
leds-lp5523
leds-lp5562
leds-lp55xx
+ leds-lp5812
leds-mlxcpld
leds-mt6370-rgb
leds-sc27xx
diff --git a/Documentation/leds/leds-lp5812.rst b/Documentation/leds/leds-lp5812.rst
new file mode 100644
index 000000000000..c2a6368d5149
--- /dev/null
+++ b/Documentation/leds/leds-lp5812.rst
@@ -0,0 +1,50 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+========================
+Kernel driver for lp5812
+========================
+
+* TI/National Semiconductor LP5812 LED Driver
+* Datasheet: https://www.ti.com/product/LP5812#tech-docs
+
+Authors: Jared Zhou <jared-zhou@ti.com>
+
+Description
+===========
+
+The LP5812 is a 4x3 matrix LED driver with support for both manual and
+autonomous animation control. This driver provides sysfs interfaces to
+control and configure the LP5812 device and its LED channels.
+
+Sysfs Interface
+===============
+
+This driver uses the standard multicolor LED class interfaces defined
+in Documentation/ABI/testing/sysfs-class-led-multicolor.rst.
+
+Each LP5812 LED output appears under ``/sys/class/leds/`` with its
+assigned label (for example ``LED_A``).
+
+The following attributes are exposed:
+ - multi_intensity: Per-channel RGB intensity control
+ - brightness: Standard brightness control (0-255)
+
+Autonomous Control Modes
+========================
+
+The driver also supports autonomous control through pattern configuration
+(e.g., direct, tcmscan, or mixscan modes) defined in the device tree.
+When configured, the LP5812 can generate transitions and color effects
+without CPU intervention.
+
+Refer to the device tree binding document for valid mode strings and
+configuration examples.
+
+Example Usage
+=============
+
+To control LED_A::
+ # Set RGB intensity (R=50, G=50, B=50)
+ echo 50 50 50 > /sys/class/leds/LED_A/multi_intensity
+ # Set overall brightness to maximum
+ echo 255 > /sys/class/leds/LED_A/brightness
diff --git a/MAINTAINERS b/MAINTAINERS
index 8a307490a478..f0529faca06e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3806,6 +3806,13 @@ L: linux-leds@vger.kernel.org
S: Maintained
F: drivers/leds/flash/leds-as3645a.c
+AS3668 LED DRIVER
+M: Lukas Timmermann <linux@timmermann.space>
+L: linux-leds@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/leds/ams,as3668.yaml
+F: drivers/leds/leds-as3668.c
+
ASAHI KASEI AK7375 LENS VOICE COIL DRIVER
M: Tianshu Qiu <tian.shu.qiu@intel.com>
L: linux-media@vger.kernel.org
@@ -26094,6 +26101,17 @@ S: Supported
F: Documentation/devicetree/bindings/iio/dac/ti,dac7612.yaml
F: drivers/iio/dac/ti-dac7612.c
+TEXAS INSTRUMENTS' LP5812 RGB LED DRIVER
+M: Nam Tran <trannamatk@gmail.com>
+L: linux-leds@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/leds/ti,lp5812.yaml
+F: Documentation/leds/leds-lp5812.rst
+F: drivers/leds/rgb/Kconfig
+F: drivers/leds/rgb/Makefile
+F: drivers/leds/rgb/leds-lp5812.c
+F: drivers/leds/rgb/leds-lp5812.h
+
TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER
M: Alexander Sverdlin <alexander.sverdlin@siemens.com>
L: linux-leds@vger.kernel.org
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);
diff --git a/include/linux/leds-expresswire.h b/include/linux/leds-expresswire.h
index a422921f4159..7f8c4795f69f 100644
--- a/include/linux/leds-expresswire.h
+++ b/include/linux/leds-expresswire.h
@@ -30,9 +30,6 @@ struct expresswire_common_props {
void expresswire_power_off(struct expresswire_common_props *props);
void expresswire_enable(struct expresswire_common_props *props);
-void expresswire_start(struct expresswire_common_props *props);
-void expresswire_end(struct expresswire_common_props *props);
-void expresswire_set_bit(struct expresswire_common_props *props, bool bit);
void expresswire_write_u8(struct expresswire_common_props *props, u8 val);
#endif /* _LEDS_EXPRESSWIRE_H */