LEDS for v6.20

- 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.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEdrbJNaO+IJqU8IdIUa+KL4f8d2EFAmmN1yEACgkQUa+KL4f8
 d2Fk8w/+NbgkKGY/B8j7f78Tlriy9QvZLXFVwj7SvlKBgMWlH2WwaEEfj7qTQntr
 ZheZsY9eU1rAT78zCOuPLIRujLSCurVSaX77YWKvgl3/j3KVC9jHkEA0ShftNGeL
 RXR0/dnzrPhfyrRLpIXvBY4QPHG4/CBpxhdvYsaxQ0+0Jl6jN262xdPS4MZjNJB/
 MPM01Bo3bXMUE3i8feiGUNqMe0yitHRqw8yfNEUo4nBnOkmT0WbGnatW/ifiWos8
 3q+XQDgj36eU9uCnTiXLbT/YMGPC4ZvZxy96MaCLWnGabaVE4XWwtHCzKoHdXpCG
 6g8FcAapg2XK+iNqmThbFjIhzk048bIQkDNuM/FGTUNsi7gUDODfAWbT133pB/gx
 v8moBUNo9csFCctM4BI4Is/xkmzrEzQQK3OQ7KZw1XymoC66vi0cc6mCzetwevAM
 08TTQmtlDdQGFHFrBNWvx0AXwe8uHDv154lE7taEx5d2by+eBi1Eh5nCum4346N0
 0jssEjwT78eFuwAV0pZW5rHvH5kchKnoWfTS+zpuPSUKTW5jTkUBoVcywu/YTm0e
 X1EkxJxZ/fQJvcL79QSqKnex1jXFC/a+D/81+piJZrQajkGFeutBQ28Wh0ka/n0j
 AjyxNAob5nGt65az7bpG26LIcXpXfdTwlmcKA3q5rOU4u0dmvsI=
 =IyMQ
 -----END PGP SIGNATURE-----

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
master
Linus Torvalds 2026-02-16 11:15:19 -08:00
commit 2228d9cf7a
25 changed files with 1892 additions and 166 deletions

View File

@ -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>;
};
};
};

View File

@ -21,7 +21,7 @@ description: |
properties:
$nodename:
pattern: "^multi-led(@[0-9a-f])?$"
pattern: "^multi-led(@[0-9a-f]|-[0-9]+)?$"
color:
description: |

View File

@ -10,6 +10,7 @@ Required properties:
issi,is31fl3235
issi,is31fl3218
issi,is31fl3216
issi,is31fl3293
si-en,sn3218
si-en,sn3216
- reg: I2C slave address

View File

@ -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

View File

@ -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>;
};
};
};
};

View File

@ -43,6 +43,7 @@ properties:
- items:
- enum:
- qcom,pm8550-pwm
- qcom,pmh0101-pwm
- const: qcom,pm8350c-pwm
- items:
- enum:

View File

@ -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

View File

@ -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";
};
};
};

View File

@ -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>;
};
};
};
};
...

View File

@ -25,6 +25,7 @@ LEDs
leds-lp5523
leds-lp5562
leds-lp55xx
leds-lp5812
leds-mlxcpld
leds-mt6370-rgb
leds-sc27xx

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

202
drivers/leds/leds-as3668.c Normal file
View File

@ -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");

View File

@ -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");

View File

@ -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;
@ -218,6 +172,62 @@ static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,
return is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, value);
}
/*
* 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()
@ -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" },

View File

@ -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;

View File

@ -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++;
}

View File

@ -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

View File

@ -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

View File

@ -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_0LED_3, LED_A0A2, LED_B0
* Reg4 for LED_B1B2, LED_C0C2, LED_D0D2
*/
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");

View File

@ -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_*/

View File

@ -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);

View File

@ -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 */