* [PATCH v5 0/5] leds: add new LED driver for TI LP5812
@ 2025-04-14 14:57 Nam Tran
2025-04-14 14:57 ` [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver Nam Tran
` (5 more replies)
0 siblings, 6 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-14 14:57 UTC (permalink / raw)
To: pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel, Nam Tran
This patch series adds support for the TI/National Semiconductor LP5812
4x3 matrix RGB LED driver. The driver supports features such as autonomous
animation and time-cross-multiplexing (TCM) for dynamic LED effects.
Signed-off-by: Nam Tran <trannamatk@gmail.com>
---
Changes in v5:
- Rebase on v6.15-rc2
- Removed unused functions (lp5812_dump_regs, lp5812_update_bit).
- Address Krzysztof's review comments
- Link to v4: https://lore.kernel.org/linux-leds/20250405183246.198568-1-trannamatk@gmail.com/
---
Nam Tran (5):
dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
leds: add TI/National Semiconductor LP5812 LED Driver
docs: ABI: Document LP5812 LED sysfs interfaces
docs: leds: Document TI LP5812 LED driver
arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B
.../ABI/testing/sysfs-bus-i2c-devices-lp5812 | 144 +
.../devicetree/bindings/leds/ti,lp5812.yaml | 119 +
Documentation/leds/leds-lp5812.rst | 79 +
MAINTAINERS | 12 +
.../arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 60 +
drivers/leds/Kconfig | 16 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-lp5812.c | 2797 +++++++++++++++++
drivers/leds/leds-lp5812.h | 350 +++
9 files changed, 3578 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812
create mode 100644 Documentation/devicetree/bindings/leds/ti,lp5812.yaml
create mode 100644 Documentation/leds/leds-lp5812.rst
create mode 100644 drivers/leds/leds-lp5812.c
create mode 100644 drivers/leds/leds-lp5812.h
base-commit: 8ffd015db85fea3e15a77027fda6c02ced4d2444
--
2.25.1
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
@ 2025-04-14 14:57 ` Nam Tran
2025-04-14 15:10 ` Krzysztof Kozlowski
2025-04-14 14:57 ` [PATCH v5 2/5] " Nam Tran
` (4 subsequent siblings)
5 siblings, 1 reply; 16+ messages in thread
From: Nam Tran @ 2025-04-14 14:57 UTC (permalink / raw)
To: pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel, Nam Tran
The LP5812 is a 4×3 RGB LED driver with an autonomous animation engine
and time-cross-multiplexing (TCM) support for up to 12 LEDs.
It supports both analog (256 levels) and PWM (8-bit) dimming,
including exponential PWM for smooth brightness control.
Signed-off-by: Nam Tran <trannamatk@gmail.com>
---
.../devicetree/bindings/leds/ti,lp5812.yaml | 119 ++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 125 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/ti,lp5812.yaml
diff --git a/Documentation/devicetree/bindings/leds/ti,lp5812.yaml b/Documentation/devicetree/bindings/leds/ti,lp5812.yaml
new file mode 100644
index 000000000000..448d6f87b586
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/ti,lp5812.yaml
@@ -0,0 +1,119 @@
+# 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/National Semiconductor LP5812 LED Driver
+
+maintainers:
+ - Nam Tran <trannamatk@gmail.com>
+
+description: |
+ The LP5812 is an I2C LED Driver that can support LED matrix 4x3.
+ 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
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^led@[0-9a-b]$":
+ type: object
+ $ref: common.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ minimum: 0
+ maximum: 0xb
+
+ chan-name:
+ $ref: /schemas/types.yaml#/definitions/string
+ description: LED channel name
+
+ required:
+ - reg
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@1b {
+ compatible = "ti,lp5812";
+ reg = <0x1b>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led@0 {
+ reg = <0x0>;
+ chan-name = "a0";
+ };
+ led@1 {
+ reg = <0x1>;
+ chan-name = "a1";
+ };
+ led@2 {
+ reg = <0x2>;
+ chan-name = "a2";
+ };
+ led@3 {
+ reg = <0x3>;
+ chan-name = "b0";
+ };
+ led@4 {
+ reg = <0x4>;
+ chan-name = "b1";
+ };
+ led@5 {
+ reg = <0x5>;
+ chan-name = "b2";
+ };
+ led@6 {
+ reg = <0x6>;
+ chan-name = "c0";
+ };
+ led@7 {
+ reg = <0x7>;
+ chan-name = "c1";
+ };
+ led@8 {
+ reg = <0x8>;
+ chan-name = "c2";
+ };
+ led@9 {
+ reg = <0x9>;
+ chan-name = "d0";
+ };
+ led@a {
+ reg = <0xa>;
+ chan-name = "d1";
+ };
+ led@b {
+ reg = <0xb>;
+ chan-name = "d2";
+ };
+ };
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index c59316109e3f..3901c04b9734 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23934,6 +23934,12 @@ S: Supported
F: Documentation/devicetree/bindings/iio/dac/ti,dac7612.yaml
F: drivers/iio/dac/ti-dac7612.c
+TEXAS INSTRUMENTS' LP5812 LED DRIVER
+M: Nam Tran <trannamatk@gmail.com>
+L: linux-leds@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/leds/ti,lp5812.yaml
+
TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER
M: Alexander Sverdlin <alexander.sverdlin@siemens.com>
L: linux-leds@vger.kernel.org
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 2/5] leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
2025-04-14 14:57 ` [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver Nam Tran
@ 2025-04-14 14:57 ` Nam Tran
2025-04-14 14:57 ` [PATCH v5 3/5] docs: ABI: Document LP5812 LED sysfs interfaces Nam Tran
` (3 subsequent siblings)
5 siblings, 0 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-14 14:57 UTC (permalink / raw)
To: pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel, Nam Tran
The LP5812 is a 4×3 matrix RGB LED driver
with an autonomous animation engine
and time-cross-multiplexing (TCM) support for up to 12 LEDs.
Each LED can be configured through the related registers
to realize vivid and fancy lighting effects.
Signed-off-by: Nam Tran <trannamatk@gmail.com>
---
MAINTAINERS | 4 +
drivers/leds/Kconfig | 16 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-lp5812.c | 2797 ++++++++++++++++++++++++++++++++++++
drivers/leds/leds-lp5812.h | 350 +++++
5 files changed, 3168 insertions(+)
create mode 100644 drivers/leds/leds-lp5812.c
create mode 100644 drivers/leds/leds-lp5812.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 3901c04b9734..c8a7058668dc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23939,6 +23939,10 @@ M: Nam Tran <trannamatk@gmail.com>
L: linux-leds@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/leds/ti,lp5812.yaml
+F: drivers/leds/Kconfig
+F: drivers/leds/Makefile
+F: drivers/leds/leds-lp5812.c
+F: drivers/leds/leds-lp5812.h
TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER
M: Alexander Sverdlin <alexander.sverdlin@siemens.com>
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a104cbb0a001..386aa7d68296 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -483,6 +483,22 @@ config LEDS_LP5569
Driver provides direct control via LED class and interface for
programming the engines.
+config LEDS_LP5812
+ tristate "Enable LP5812 support LED matrix 4x3"
+ depends on LEDS_CLASS && I2C
+ depends on LEDS_CLASS_MULTICOLOR
+ help
+ If you say Y here you get support for TI LP5812 LED driver.
+
+ The LP5812 is an I2C-based RGB LED driver that supports a 4x3 LED matrix.
+ It provides independent brightness control and color mixing for each LED.
+ This driver integrates with the Linux LED framework and LED multicolor class.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-lp5812.
+
+ If unsure, say N.
+
config LEDS_LP8501
tristate "LED Support for TI LP8501 LED driver chip"
depends on LEDS_CLASS && I2C
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 2f170d69dcbf..77f9cec9f82b 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o
obj-$(CONFIG_LEDS_LP5569) += leds-lp5569.o
obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
+obj-$(CONFIG_LEDS_LP5812) += leds-lp5812.o
obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
diff --git a/drivers/leds/leds-lp5812.c b/drivers/leds/leds-lp5812.c
new file mode 100644
index 000000000000..c323f8a2f9f2
--- /dev/null
+++ b/drivers/leds/leds-lp5812.c
@@ -0,0 +1,2797 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LP5812 LED driver
+ *
+ * Copyright (C) 2025 Texas Instruments
+ *
+ * Author: Jared Zhou <jared-zhou@ti.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/mod_devicetable.h>
+#include <linux/string.h>
+
+#include "leds-lp5812.h"
+
+#define to_lp5812_led(x) container_of(x, struct lp5812_led, kobj)
+#define to_anim_engine_unit(x) container_of(x, struct anim_engine_unit, kobj)
+
+static int lp5812_init_dev_config(struct lp5812_chip *chip,
+ const char *drive_mode, int rm_led_sysfs);
+
+static struct drive_mode_led_map chip_leds_map[] = {
+ {
+ "direct_mode",
+ (const char *[]){LED0, LED1, LED2, LED3, NULL}
+ },
+ {
+ "tcmscan:1:0", /* tcm 1 scan; scan order 0 out0 */
+ (const char *[]){LED_A0, LED_A1, LED_A2, NULL}
+ },
+ {
+ "tcmscan:1:1", /* tcm 1 scan; scan order 0 out1 */
+ (const char *[]){LED_B0, LED_B1, LED_B2, NULL}
+ },
+ {
+ "tcmscan:1:2", /* tcm 1 scan; scan order 0 out2 */
+ (const char *[]){LED_C0, LED_C1, LED_C2, NULL}
+ },
+ {
+ "tcmscan:1:3", /* tcm 1 scan; scan order 0 out3 */
+ (const char *[]){LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* tcm 2 scan, scan order 0 out0; scan order 1 out1 */
+ "tcmscan:2:0:1",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2, NULL}
+ },
+ { /* tcm 2 scan, scan order 0 out0; scan order 1 out2 */
+ "tcmscan:2:0:2",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_C0, LED_C1, LED_C2, NULL}
+ },
+ { /* tcm 2 scan, scan order 0 out0; scan order 1 out3 */
+ "tcmscan:2:0:3",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* tcm 2 scan, scan order 0 out1; scan order 1 out2 */
+ "tcmscan:2:1:2",
+ (const char *[]){LED_B0, LED_B1, LED_B2, LED_C0, LED_C1, LED_C2, NULL}
+ },
+ { /* tcm 2 scan, scan order 0 out1; scan order 1 out3 */
+ "tcmscan:2:1:3",
+ (const char *[]){LED_B0, LED_B1, LED_B2, LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* tcm 2 scan, scan order 0 out2; scan order 1 out3 */
+ "tcmscan:2:2:3",
+ (const char *[]){LED_C0, LED_C1, LED_C2, LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* tcm 3 scan, scan order 0 out0; scan order 1 out1;
+ * scan order 2 out2
+ */
+ "tcmscan:3:0:1:2",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2,
+ LED_C0, LED_C1, LED_C2, NULL}
+ },
+ { /* tcm 3 scan, scan order 0 out0; scan order 1 out1;
+ * scan order 2 out3
+ */
+ "tcmscan:3:0:1:3",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2,
+ LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* tcm 3 scan, scan order 0 out0; scan order 1 out2;
+ * scan order 2 out3
+ */
+ "tcmscan:3:0:2:3",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_C0, LED_C1, LED_C2,
+ LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* tcm 4 scan, scan order 0 out0; scan order 1 out1;
+ * scan order 2 out2; scan order 3 out3
+ */
+ "tcmscan:4:0:1:2:3",
+ (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2,
+ LED_C0, LED_C1, LED_C2, LED_D0, LED_D1, LED_D2, NULL}
+ },
+ { /* mix 1 scan, direct connect out0; scan order 0 out1 */
+ "mixscan:1:0:1",
+ (const char *[]){LED0, LED_B0, LED_B1, NULL}
+ },
+ { /* mix 1 scan, direct connect out0; scan order 0 out2 */
+ "mixscan:1:0:2",
+ (const char *[]){LED0, LED_C0, LED_C2, NULL}
+ },
+ { /* mix 1 scan, direct connect out0; scan order 0 out3 */
+ "mixscan:1:0:3",
+ (const char *[]){LED0, LED_D1, LED_D2, NULL}
+ },
+ { /* mix 1 scan, direct connect out1; scan order 0 out0 */
+ "mixscan:1:1:0",
+ (const char *[]){LED1, LED_A1, LED_A2, NULL}
+ },
+ { /* mix 1 scan, direct connect out1; scan order 0 out2 */
+ "mixscan:1:1:2",
+ (const char *[]){LED1, LED_C0, LED_C1, NULL}
+ },
+ { /* mix 1 scan, direct connect out1; scan order 0 out3 */
+ "mixscan:1:1:3",
+ (const char *[]){LED1, LED_D0, LED_D2, NULL}
+ },
+ { /* mix 1 scan, direct connect out2; scan order 0 out0 */
+ "mixscan:1:2:0",
+ (const char *[]){LED2, LED_A0, LED_A2, NULL}
+ },
+ { /* mix 1 scan, direct connect out2; scan order 0 out1 */
+ "mixscan:1:2:1",
+ (const char *[]){LED2, LED_B1, LED_B2, NULL}
+ },
+ { /* mix 1 scan, direct connect out2; scan order 0 out3 */
+ "mixscan:1:2:3",
+ (const char *[]){LED2, LED_D0, LED_D1, NULL}
+ },
+ { /* mix 1 scan, direct connect out3; scan order 0 out0 */
+ "mixscan:1:3:0",
+ (const char *[]){LED3, LED_A0, LED_A1, NULL}
+ },
+ { /* mix 1 scan, direct connect out3; scan order 0 out1 */
+ "mixscan:1:3:1",
+ (const char *[]){LED3, LED_B0, LED_B2, NULL}
+ },
+ { /* mix 1 scan, direct connect out3; scan order 0 out2 */
+ "mixscan:1:3:2",
+ (const char *[]){LED3, LED_C1, LED_C2, NULL}
+ },
+ { /* mix 2 scan, direct connect out0; scan order 0 out1;
+ * scan order 1 out2
+ */
+ "mixscan:2:0:1:2",
+ (const char *[]){LED0, LED_B0, LED_B1, LED_C0, LED_C2, NULL}
+ },
+ { /* mix 2 scan, direct connect out0; scan order 0 out1;
+ * scan order 1 out3
+ */
+ "mixscan:2:0:1:3",
+ (const char *[]){LED0, LED_B0, LED_B1, LED_D1, LED_D2, NULL}
+ },
+ { /* mix 2 scan, direct connect out0; scan order 0 out2;
+ * scan order 1 out3
+ */
+ "mixscan:2:0:2:3",
+ (const char *[]){LED0, LED_C0, LED_C2, LED_D1, LED_D2, NULL}
+ },
+ { /* mix 2 scan, direct connect out1; scan order 0 out0;
+ * scan order 1 out2
+ */
+ "mixscan:2:1:0:2",
+ (const char *[]){LED1, LED_A1, LED_A2, LED_C0, LED_C1, NULL}
+ },
+ { /* mix 2 scan, direct connect out1; scan order 0 out0;
+ * scan order 1 out3
+ */
+ "mixscan:2:1:0:3",
+ (const char *[]){LED1, LED_A1, LED_A2, LED_D0, LED_D2, NULL}
+ },
+ { /* mix 2 scan, direct connect out1; scan order 0 out2;
+ * scan order 1 out3
+ */
+ "mixscan:2:1:2:3",
+ (const char *[]){LED1, LED_C0, LED_C1, LED_D0, LED_D2, NULL}
+ },
+ { /* mix 2 scan, direct connect out2; scan order 0 out0;
+ * scan order 1 out1
+ */
+ "mixscan:2:2:0:1",
+ (const char *[]){LED2, LED_A0, LED_A2, LED_B1, LED_B2, NULL}
+ },
+ { /* mix 2 scan, direct connect out2; scan order 0 out0;
+ * scan order 1 out3
+ */
+ "mixscan:2:2:0:3",
+ (const char *[]){LED2, LED_A0, LED_A2, LED_D0, LED_D1, NULL}
+ },
+ { /* mix 2 scan, direct connect out2; scan order 0 out1;
+ * scan order 1 out3
+ */
+ "mixscan:2:2:1:3",
+ (const char *[]){LED2, LED_B1, LED_B2, LED_D0, LED_D1, NULL}
+ },
+ { /* mix 2 scan, direct connect out3; scan order 0 out0;
+ * scan order 1 out1
+ */
+ "mixscan:2:3:0:1",
+ (const char *[]){LED3, LED_A0, LED_A1, LED_B0, LED_B2, NULL}
+ },
+ { /* mix 2 scan, direct connect out3; scan order 0 out0;
+ * scan order 1 out2
+ */
+ "mixscan:2:3:0:2",
+ (const char *[]){LED3, LED_A0, LED_A1, LED_C1, LED_C2, NULL}
+ },
+ { /* mix 2 scan, direct connect out3; scan order 0 out1;
+ * scan order 1 out2
+ */
+ "mixscan:2:3:1:2",
+ (const char *[]){LED3, LED_B0, LED_B2, LED_C1, LED_C2, NULL}
+ },
+ { /* mix 3 scan, direct connect out0; scan order 0 out1;
+ * scan order 1 out2; scan order 2 out3
+ */
+ "mixscan:3:0:1:2:3",
+ (const char *[]){LED0, LED_B0, LED_B1, LED_C0, LED_C2, LED_D1,
+ LED_D2, NULL}
+ },
+ { /* mix 3 scan, direct connect out1; scan order 0 out0;
+ * scan order 1 out2; scan order 2 out3
+ */
+ "mixscan:3:1:0:2:3",
+ (const char *[]){LED1, LED_A1, LED_A2, LED_C0, LED_C1, LED_D0,
+ LED_D2, NULL}
+ },
+ { /* mix 3 scan, direct connect out2; scan order 0 out0;
+ * scan order 1 out1; scan order 2 out3
+ */
+ "mixscan:3:2:0:1:3",
+ (const char *[]){LED2, LED_A0, LED_A2, LED_B1, LED_B2, LED_D0,
+ LED_D1, NULL}
+ },
+ { /* mix 3 scan, direct connect out3; scan order 0 out0;
+ * scan order 1 out1; scan order 2 out2
+ */
+ "mixscan:3:3:0:1:2",
+ (const char *[]){LED3, LED_A0, LED_A1, LED_B0, LED_B2, LED_C1,
+ LED_C2, NULL}
+ }
+};
+
+static const char *led_name_array[MAX_LEDS] = {
+ LED0, LED1, LED2, LED3, LED_A0, LED_A1, LED_A2, LED_B0, LED_B1,
+ LED_B2, LED_C0, LED_C1, LED_C2, LED_D0, LED_D1, LED_D2
+};
+
+static u16 anim_base_addr_array[MAX_LEDS] = {
+ LED0_AUTO_BASE_ADRR, LED1_AUTO_BASE_ADRR, LED2_AUTO_BASE_ADRR,
+ LED3_AUTO_BASE_ADRR, LED_A0_AUTO_BASE_ADRR, LED_A1_AUTO_BASE_ADRR,
+ LED_A2_AUTO_BASE_ADRR, LED_B0_AUTO_BASE_ADRR, LED_B1_AUTO_BASE_ADRR,
+ LED_B2_AUTO_BASE_ADRR, LED_C0_AUTO_BASE_ADRR, LED_C1_AUTO_BASE_ADRR,
+ LED_C2_AUTO_BASE_ADRR, LED_D0_AUTO_BASE_ADRR, LED_D1_AUTO_BASE_ADRR,
+ LED_D2_AUTO_BASE_ADRR
+};
+
+static const char *time_name_array[MAX_TIME] = {
+ TIME0, TIME1, TIME2, TIME3, TIME4, TIME5, TIME6, TIME7, TIME8,
+ TIME9, TIME10, TIME11, TIME12, TIME13, TIME14, TIME15
+};
+
+static const char *led_playback_time_arr[MAX_TIME] = {
+ "0 time", "1 time", "2 times", "3 times", "4 times", "5 times",
+ "6 times", "7 times", "8 times", "9 times", "10 times", "11 times",
+ "12 times", "13 times", "14 times", "infinite times"
+};
+
+static const char *aeu_name_array[MAX_AEU] = {AEU1, AEU2, AEU3};
+
+static const struct lp5812_specific_regs regs = {
+ .enable_reg = CHIP_EN_REG,
+ .reset_reg = RESET_REG,
+ .update_cmd_reg = CMD_UPDATE_REG,
+ .start_cmd_reg = CMD_START_REG,
+ .stop_cmd_reg = CMD_STOP_REG,
+ .pause_cmd_reg = CMD_PAUSE_REG,
+ .continue_cmd_reg = CMD_CONTINUE_REG,
+ .fault_clear_reg = FAULT_CLEAR_REG,
+ .tsd_config_status_reg = TSD_CONFIG_STAT_REG,
+};
+
+static void led_kobj_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static void aeu_kobj_release(struct kobject *kobj)
+{
+ kfree(kobj);
+}
+
+static const struct kobj_type led_ktype = {
+ .release = led_kobj_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+};
+
+static const struct kobj_type aeu_ktype = {
+ .release = aeu_kobj_release,
+ .sysfs_ops = &kobj_sysfs_ops,
+};
+
+static int lp5812_write(struct lp5812_chip *chip, u16 reg, u8 val)
+{
+ int ret;
+ u8 extracted_bits; /* save 9th and 8th bit of reg address */
+ struct i2c_msg msg;
+ u8 buf[2] = {(u8)(reg & 0xFF), val};
+
+ extracted_bits = (reg >> 8) & 0x03;
+ msg.addr = (chip->i2c_cl->addr << 2) | extracted_bits;
+ msg.flags = 0;
+ msg.len = sizeof(buf);
+ msg.buf = buf;
+
+ ret = i2c_transfer(chip->i2c_cl->adapter, &msg, 1);
+ if (ret != 1) {
+ dev_err(chip->dev, "i2c write error, register 0x%02X, ret=%d\n", reg, ret);
+ ret = ret < 0 ? ret : -EIO;
+ } else {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int lp5812_read(struct lp5812_chip *chip, u16 reg, u8 *val)
+{
+ int ret;
+ u8 ret_val; /* lp5812_chip return value */
+ u8 extracted_bits; /* save 9th and 8th bit of reg address */
+ u8 converted_reg; /* extracted 8bit from reg */
+ struct i2c_msg msgs[2];
+
+ extracted_bits = (reg >> 8) & 0x03;
+ converted_reg = (u8)(reg & 0xFF);
+
+ msgs[0].addr = (chip->i2c_cl->addr << 2) | extracted_bits;
+ msgs[0].flags = 0;
+ msgs[0].len = 1;
+ msgs[0].buf = &converted_reg;
+
+ msgs[1].addr = (chip->i2c_cl->addr << 2) | extracted_bits;
+ msgs[1].flags = I2C_M_RD;
+ msgs[1].len = 1;
+ msgs[1].buf = &ret_val;
+
+ ret = i2c_transfer(chip->i2c_cl->adapter, msgs, 2);
+ if (ret != 2) {
+ dev_err(chip->dev, "Read register 0x%02X error, ret=%d\n", reg, ret);
+ *val = 0;
+ ret = ret < 0 ? ret : -EIO;
+ } else {
+ *val = ret_val;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int lp5812_read_tsd_config_status(struct lp5812_chip *chip, u8 *reg_val)
+{
+ int ret = 0;
+
+ if (!reg_val)
+ return -1;
+
+ ret = lp5812_read(chip, chip->regs->tsd_config_status_reg, reg_val);
+
+ return ret;
+}
+
+static int lp5812_update_regs_config(struct lp5812_chip *chip)
+{
+ int ret;
+ u8 reg_val; /* save register value */
+
+ /* Send update command to update config setting */
+ ret = lp5812_write(chip, chip->regs->update_cmd_reg, UPDATE_CMD_VAL);
+ if (ret)
+ return ret;
+ /* check if the configuration is proper */
+ ret = lp5812_read_tsd_config_status(chip, ®_val);
+ if (ret == 0)
+ return (int)(reg_val & 0x01);
+
+ return ret;
+}
+
+static int lp5812_read_lod_status(struct lp5812_chip *chip, int led_number, u8 *val)
+{
+ int ret = 0;
+ u16 reg = 0;
+ u8 reg_val = 0;
+
+ if (!val)
+ return -1;
+
+ if (led_number < 0x8)
+ reg = LOD_STAT_1_REG;
+ else
+ reg = LOD_STAT_2_REG;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *val = (reg_val & (1 << (led_number % 8))) ? 1 : 0;
+
+ return ret;
+}
+
+static int lp5812_read_lsd_status(struct lp5812_chip *chip, int led_number, u8 *val)
+{
+ int ret = 0;
+ u16 reg = 0;
+ u8 reg_val = 0;
+
+ if (!val)
+ return -1;
+
+ if (led_number < 0x8)
+ reg = LSD_STAT_1_REG;
+ else
+ reg = LSD_STAT_2_REG;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *val = (reg_val & (1 << (led_number % 8))) ? 1 : 0;
+
+ return ret;
+}
+
+static int lp5812_read_auto_pwm_value(struct lp5812_chip *chip, int led_number,
+ u8 *val)
+{
+ int ret = 0;
+ u16 reg = 0;
+ u8 reg_val = 0;
+
+ reg = AUTO_PWM_BASE_ADDR + led_number;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *val = reg_val;
+
+ return ret;
+}
+
+static int lp5812_read_aep_status(struct lp5812_chip *chip, int led_number, u8 *val)
+{
+ int ret = 0;
+ u16 reg;
+ u8 reg_val;
+
+ switch (led_number / 2) {
+ case 0:
+ reg = AEP_STATUS_0_REG; // LED_0 and LED_1
+ break;
+ case 1:
+ reg = AEP_STATUS_1_REG; // LED_2 and LED_3
+ break;
+ case 2:
+ reg = AEP_STATUS_2_REG; // LED_A0 and LED_A1
+ break;
+ case 3:
+ reg = AEP_STATUS_3_REG; // LED_A2 and LED_B0
+ break;
+ case 4:
+ reg = AEP_STATUS_4_REG; // LED_B1 and LED_B2
+ break;
+ case 5:
+ reg = AEP_STATUS_5_REG; // LED_C0 and LED_C1
+ break;
+ case 6:
+ reg = AEP_STATUS_6_REG; // LED_C2 and LED_D0
+ break;
+ case 7:
+ reg = AEP_STATUS_7_REG; // LED_D1 and LED_D2
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *val = (led_number % 2) ? ((reg_val >> 3) & 0x07) : (reg_val & 0x07);
+
+ return ret;
+}
+
+static int lp5812_enable_disable(struct lp5812_chip *chip, int enable)
+{
+ return lp5812_write(chip, chip->regs->enable_reg, (u8)enable);
+}
+
+static int lp5812_reset(struct lp5812_chip *chip)
+{
+ return lp5812_write(chip, chip->regs->reset_reg, RESET_REG_VAL);
+}
+
+static int lp5812_fault_clear(struct lp5812_chip *chip, u8 value)
+{
+ u8 reg_val;
+
+ if (value == 0)
+ reg_val = LOD_CLEAR_VAL;
+ else if (value == 1)
+ reg_val = LSD_CLEAR_VAL;
+ else if (value == 2)
+ reg_val = TSD_CLEAR_VAL;
+ else if (value == 3)
+ reg_val = FAULT_CLEAR_ALL;
+ else
+ return -EINVAL;
+
+ return lp5812_write(chip, chip->regs->fault_clear_reg, reg_val);
+}
+
+static int lp5812_device_command(struct lp5812_chip *chip, enum device_command command)
+{
+ switch (command) {
+ case UPDATE:
+ return lp5812_write(chip, chip->regs->update_cmd_reg,
+ UPDATE_CMD_VAL);
+ case START:
+ return lp5812_write(chip, chip->regs->start_cmd_reg,
+ START_CMD_VAL);
+ case STOP:
+ return lp5812_write(chip, chip->regs->stop_cmd_reg,
+ STOP_CMD_VAL);
+ case PAUSE:
+ return lp5812_write(chip, chip->regs->pause_cmd_reg,
+ PAUSE_CMD_VAL);
+ case CONTINUE:
+ return lp5812_write(chip, chip->regs->continue_cmd_reg,
+ CONTINUE_CMD_VAL);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int lp5812_set_pwm_dimming_scale(struct lp5812_chip *chip, int led_number,
+ enum pwm_dimming_scale scale)
+{
+ int ret = 0;
+ u16 reg;
+ u8 reg_val;
+
+ if (led_number <= 7)
+ reg = (u16)DEV_CONFIG5;
+ else
+ reg = (u16)DEV_CONFIG6;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret != 0)
+ return ret;
+ if (scale == LINEAR) {
+ reg_val &= ~(1 << (led_number % 8));
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret != 0)
+ return ret;
+ } else {
+ reg_val |= (1 << (led_number % 8));
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret != 0)
+ return ret;
+ }
+
+ ret = lp5812_update_regs_config(chip);
+
+ return ret;
+}
+
+static int lp5812_get_pwm_dimming_scale(struct lp5812_chip *chip,
+ int led_number, enum pwm_dimming_scale *scale)
+{
+ int ret = 0;
+ u16 reg;
+ u8 reg_val;
+
+ if (led_number < 0x8)
+ reg = DEV_CONFIG5;
+ else
+ reg = DEV_CONFIG6;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *scale = (reg_val & (1 << (led_number % 8))) ? EXPONENTIAL : LINEAR;
+
+ return 0;
+}
+
+static int lp5812_set_phase_align(struct lp5812_chip *chip, int led_number,
+ int phase_align_val)
+{
+ int ret;
+ int bit_pos;
+ u16 reg;
+ u8 reg_val;
+
+ reg = DEV_CONFIG7 + (led_number / 4);
+ bit_pos = (led_number % 4) * 2;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+ reg_val |= (phase_align_val << bit_pos);
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret != 0)
+ return ret;
+ ret = lp5812_update_regs_config(chip);
+
+ return ret;
+}
+
+static int lp5812_get_phase_align(struct lp5812_chip *chip, int led_number,
+ int *phase_align_val)
+{
+ int ret;
+ int bit_pos;
+ u16 reg;
+ u8 reg_val;
+
+ reg = DEV_CONFIG7 + (led_number / 4);
+ bit_pos = (led_number % 4) * 2;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *phase_align_val = (reg_val >> bit_pos) & 0x03;
+
+ return ret;
+}
+
+static int lp5812_get_led_mode(struct lp5812_chip *chip,
+ int led_number, enum control_mode *mode)
+{
+ int ret = 0;
+ u16 reg;
+ u8 reg_val;
+
+ if (led_number < 0x8)
+ reg = DEV_CONFIG3;
+ else
+ reg = DEV_CONFIG4;
+
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret)
+ return ret;
+
+ *mode = (reg_val & (1 << (led_number % 8))) ? AUTONOMOUS : MANUAL;
+
+ return 0;
+}
+
+static int lp5812_set_led_mode(struct lp5812_chip *chip, int led_number,
+ enum control_mode mode)
+{
+ int ret = 0;
+ u16 reg;
+ u8 reg_val;
+
+ if (led_number <= 7)
+ reg = (u16)DEV_CONFIG3;
+ else
+ reg = (u16)DEV_CONFIG4;
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret != 0)
+ return ret;
+ if (mode == MANUAL) {
+ reg_val &= ~(1 << (led_number % 8));
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret != 0)
+ return ret;
+ } else {
+ reg_val |= (1 << (led_number % 8));
+ ret = lp5812_write(chip, reg, reg_val);
+ if (ret != 0)
+ 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)
+{
+ int ret;
+ u16 led_base_reg;
+
+ if (dimming_type == ANALOG)
+ led_base_reg = (u16)MANUAL_DC_LED_0_REG;
+ else
+ led_base_reg = (u16)MANUAL_PWM_LED_0_REG;
+ ret = lp5812_write(chip, led_base_reg + led_number, val);
+
+ return ret;
+}
+
+static int lp5812_manual_dc_pwm_read(struct lp5812_chip *chip,
+ int led_number, u8 *val, enum dimming_type dimming_type)
+{
+ int ret;
+ u16 led_base_reg;
+
+ if (dimming_type == ANALOG)
+ led_base_reg = (u16)MANUAL_DC_LED_0_REG;
+ else
+ led_base_reg = (u16)MANUAL_PWM_LED_0_REG;
+ ret = lp5812_read(chip, led_base_reg + led_number, val);
+
+ return ret;
+}
+
+static int lp5812_autonomous_dc_pwm_control(struct lp5812_chip *chip,
+ int led_number, u8 val, enum dimming_type dimming_type)
+{
+ int ret;
+ u16 led_base_reg;
+
+ led_base_reg = (u16)AUTO_DC_LED_0_REG;
+ ret = lp5812_write(chip, led_base_reg + led_number, val);
+
+ return ret;
+}
+
+static int lp5812_autonomous_dc_pwm_read(struct lp5812_chip *chip,
+ int led_number, u8 *val, enum dimming_type dimming_type)
+{
+ int ret;
+ u16 led_base_reg;
+
+ led_base_reg = (u16)AUTO_DC_LED_0_REG;
+ ret = lp5812_read(chip, led_base_reg + led_number, val);
+
+ return ret;
+}
+
+static int lp5812_disable_all_leds(struct lp5812_chip *chip)
+{
+ int ret = 0;
+
+ ret = lp5812_write(chip, (u16)LED_ENABLE_1_REG, 0x00);
+ if (ret != 0)
+ return ret;
+ ret = lp5812_write(chip, (u16)LED_ENABLE_2_REG, 0x00);
+ if (ret != 0)
+ return ret;
+
+ return ret;
+}
+
+static int lp5812_get_drive_mode_scan_order(struct lp5812_chip *chip)
+{
+ u8 val;
+ int ret = 0;
+
+ /* get led mode */
+ ret = lp5812_read(chip, (u16)DEV_CONFIG1, &val);
+ if (ret != 0)
+ return ret;
+ chip->u_drive_mode.drive_mode_val = val;
+
+ /* get scan order */
+ ret = lp5812_read(chip, (u16)DEV_CONFIG2, &val);
+ if (ret != 0)
+ return ret;
+ chip->u_scan_order.scan_order_val = val;
+
+ return ret;
+}
+
+static int lp5812_set_drive_mode_scan_order(struct lp5812_chip *chip)
+{
+ u8 val;
+ int ret = 0;
+
+ /* Set led mode */
+ val = chip->u_drive_mode.drive_mode_val;
+ ret = lp5812_write(chip, (u16)DEV_CONFIG1, val);
+ if (ret != 0)
+ return ret;
+
+ /* Setup scan order */
+ val = chip->u_scan_order.scan_order_val;
+ ret = lp5812_write(chip, (u16)DEV_CONFIG2, val);
+ if (ret != 0)
+ return ret;
+
+ return ret;
+}
+
+static int lp5812_initialize(struct lp5812_chip *chip)
+{
+ int ret = 0;
+
+ /* wait for 1 ms */
+ usleep_range(1000, 1100);
+
+ /* enable the lp5812 */
+ ret = lp5812_enable_disable(chip, 1);
+ if (ret != 0) {
+ dev_err(chip->dev, "lp5812_enable_disable failed\n");
+ return ret;
+ }
+ ret = lp5812_set_drive_mode_scan_order(chip);
+ if (ret != 0) {
+ dev_err(chip->dev, "lp5812_set_drive_mode_scan_order failed\n");
+ return ret;
+ }
+
+ /* Set lsd_threshold = 3h to avoid incorrect LSD detection */
+ ret = lp5812_write(chip, (u16)DEV_CONFIG12, 0x0B);
+ if (ret != 0) {
+ dev_err(chip->dev, "write 0x0B to DEV_CONFIG12 failed\n");
+ return ret;
+ }
+
+ /* Send update command to complete configuration settings */
+ ret = lp5812_update_regs_config(chip);
+ if (ret != 0) {
+ dev_err(chip->dev, "lp5812_update_regs_config failed\n");
+ return ret;
+ }
+
+ /* Enable LED_A0 for testing */
+ ret = lp5812_write(chip, (u16)LED_ENABLE_1_REG, 0x20);
+ if (ret != 0) {
+ dev_err(chip->dev, "write 0x10 to LED_ENABLE_1_REG failed\n");
+ return ret;
+ }
+ /* set max DC current for LED_A0 */
+ ret = lp5812_write(chip, (u16)0x35, 0x80);
+ if (ret != 0)
+ dev_err(chip->dev, "set max DC current for LED_A0 failed\n");
+
+ /* set 100% pwm cycle for LED_A0 */
+ ret = lp5812_write(chip, (u16)0x45, 0x80);
+ if (ret != 0)
+ dev_err(chip->dev, "set 100 percent pwm cycle for LED_A0 failed\n");
+
+ return ret;
+}
+
+static int led_set_autonomous_animation_config(struct lp5812_led *led)
+{
+ int ret;
+ u16 reg;
+ struct lp5812_chip *chip = led->priv;
+
+ /* Set start/end pause */
+ reg = led->anim_base_addr + AUTO_PAUSE;
+ ret = lp5812_write(chip, reg, led->start_stop_pause_time.time_val);
+ if (ret)
+ return ret;
+
+ /* Set led playback and AEU selection */
+ reg = led->anim_base_addr + AUTO_PLAYBACK;
+ ret = lp5812_write(chip, reg, led->led_playback.led_playback_val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int led_get_autonomous_animation_config(struct lp5812_led *led)
+{
+ int ret;
+ u16 reg;
+ struct lp5812_chip *chip = led->priv;
+
+ /* Get start/end pause value */
+ reg = led->anim_base_addr + AUTO_PAUSE;
+ ret = lp5812_read(chip, reg, &led->start_stop_pause_time.time_val);
+ if (ret)
+ return ret;
+
+ /* Get led playback and AEU selection values */
+ reg = led->anim_base_addr + AUTO_PLAYBACK;
+ ret = lp5812_read(chip, reg, &led->led_playback.led_playback_val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static u16 get_aeu_pwm_register(struct anim_engine_unit *aeu,
+ enum pwm_slope_time_num pwm_num)
+{
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+
+ switch (pwm_num) {
+ case PWM1:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_PWM_1;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_PWM_1;
+ else
+ reg = led->anim_base_addr + AEU3_PWM_1;
+ break;
+ case PWM2:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_PWM_2;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_PWM_2;
+ else
+ reg = led->anim_base_addr + AEU3_PWM_2;
+ break;
+ case PWM3:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_PWM_3;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_PWM_3;
+ else
+ reg = led->anim_base_addr + AEU3_PWM_3;
+ break;
+ case PWM4:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_PWM_4;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_PWM_4;
+ else
+ reg = led->anim_base_addr + AEU3_PWM_4;
+ break;
+ case PWM5:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_PWM_5;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_PWM_5;
+ else
+ reg = led->anim_base_addr + AEU3_PWM_5;
+ break;
+ default:
+ reg = led->anim_base_addr;
+ break;
+ }
+
+ return reg;
+}
+
+static u16 get_aeu_slope_time_register(struct anim_engine_unit *aeu,
+ enum pwm_slope_time_num slope_time_num)
+{
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+
+ switch (slope_time_num) {
+ case SLOPE_T1:
+ case SLOPE_T2:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_T12;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_T12;
+ else
+ reg = led->anim_base_addr + AEU3_T12;
+ break;
+ case SLOPE_T3:
+ case SLOPE_T4:
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_T34;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_T34;
+ else
+ reg = led->anim_base_addr + AEU3_T34;
+ break;
+ default:
+ reg = led->anim_base_addr;
+ break;
+ }
+
+ return reg;
+}
+
+static u16 get_aeu_playback_time_register(struct anim_engine_unit *aeu)
+{
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+
+ if (aeu->aeu_number == 1)
+ reg = led->anim_base_addr + AEU1_PLAYBACK;
+ else if (aeu->aeu_number == 2)
+ reg = led->anim_base_addr + AEU2_PLAYBACK;
+ else
+ reg = led->anim_base_addr + AEU3_PLAYBACK;
+
+ return reg;
+}
+
+static int led_aeu_pwm_set_val(struct anim_engine_unit *aeu, u8 val,
+ enum pwm_slope_time_num pwm_num)
+{
+ int ret;
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+ struct lp5812_chip *chip = led->priv;
+
+ reg = get_aeu_pwm_register(aeu, pwm_num);
+ ret = lp5812_write(chip, reg, val);
+
+ return ret;
+}
+
+static int led_aeu_pwm_get_val(struct anim_engine_unit *aeu, u8 *val,
+ enum pwm_slope_time_num pwm_num)
+{
+ int ret;
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+ struct lp5812_chip *chip = led->priv;
+
+ reg = get_aeu_pwm_register(aeu, pwm_num);
+ ret = lp5812_read(chip, reg, val);
+
+ return ret;
+}
+
+static int led_aeu_slope_time_set_val(struct anim_engine_unit *aeu, u8 val,
+ enum pwm_slope_time_num slope_time_num)
+{
+ int ret;
+ u16 reg;
+ union time slope_time_val;
+ struct lp5812_led *led = aeu->led;
+ struct lp5812_chip *chip = led->priv;
+
+ reg = get_aeu_slope_time_register(aeu, slope_time_num);
+
+ /* get original value of slope time */
+ ret = lp5812_read(chip, reg, &slope_time_val.time_val);
+ if (ret != 0)
+ return ret;
+
+ /* Update new value for slope time*/
+ if (slope_time_num == SLOPE_T1 || slope_time_num == SLOPE_T3)
+ slope_time_val.s_time.first = val;
+ if (slope_time_num == SLOPE_T2 || slope_time_num == SLOPE_T4)
+ slope_time_val.s_time.second = val;
+
+ /* Save updated value to hardware */
+ ret = lp5812_write(chip, reg, slope_time_val.time_val);
+
+ return ret;
+}
+
+static int led_aeu_slope_time_get_val(struct anim_engine_unit *aeu, u8 *val,
+ enum pwm_slope_time_num slope_time_num)
+{
+ int ret = 0;
+ u16 reg;
+ union time slope_time_val;
+ struct lp5812_led *led = aeu->led;
+ struct lp5812_chip *chip = led->priv;
+
+ reg = get_aeu_slope_time_register(aeu, slope_time_num);
+ /* get slope time value */
+ ret = lp5812_read(chip, reg, &slope_time_val.time_val);
+ if (ret != 0)
+ return ret;
+
+ if (slope_time_num == SLOPE_T1 || slope_time_num == SLOPE_T3)
+ *val = slope_time_val.s_time.first;
+
+ if (slope_time_num == SLOPE_T2 || slope_time_num == SLOPE_T4)
+ *val = slope_time_val.s_time.second;
+
+ return ret;
+}
+
+static int led_aeu_playback_time_set_val(struct anim_engine_unit *aeu, u8 val)
+{
+ int ret;
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+ struct lp5812_chip *chip = led->priv;
+
+ reg = get_aeu_playback_time_register(aeu);
+ ret = lp5812_write(chip, reg, val);
+
+ return ret;
+}
+
+static int led_aeu_playback_time_get_val(struct anim_engine_unit *aeu, u8 *val)
+{
+ int ret;
+ u16 reg;
+ struct lp5812_led *led = aeu->led;
+ struct lp5812_chip *chip = led->priv;
+
+ reg = get_aeu_playback_time_register(aeu);
+ ret = lp5812_read(chip, reg, val);
+
+ return ret;
+}
+
+static int aeu_create_sysfs_group(struct anim_engine_unit *aeu)
+{
+ int ret = 0;
+
+ ret = kobject_add(&aeu->kobj, &aeu->led->kobj, "%s",
+ aeu->aeu_name);
+ if (ret)
+ return ret;
+ ret = sysfs_create_group(&aeu->kobj, &aeu->attr_group);
+ aeu->enabled = 1;
+
+ return ret;
+}
+
+static void aeu_remove_sysfs_group(struct anim_engine_unit *aeu)
+{
+ aeu->enabled = 0;
+ sysfs_remove_group(&aeu->kobj, &aeu->attr_group);
+ kobject_del(&aeu->kobj);
+}
+
+/* Function to remove multiple sysfs group of AEU in one led */
+static void aeu_remove_multi_sysfs_groups(struct lp5812_led *led)
+{
+ int i;
+
+ for (i = 0; i < led->total_aeu; i++) {
+ if (led->aeu[i].enabled)
+ aeu_remove_sysfs_group(&led->aeu[i]);
+ }
+}
+
+/*
+ * Function to create multi sysfs group for specific led. it will
+ * check the mode of led and AEU number selection to create
+ * corresponding AEU interfaces
+ */
+static int aeu_create_multi_sysfs_groups(struct lp5812_led *led)
+{
+ int i;
+ int aeu_select;
+ enum control_mode mode;
+ struct lp5812_chip *chip = led->priv;
+
+ if (lp5812_get_led_mode(chip, led->led_number, &mode))
+ return -EIO;
+ if (mode == AUTONOMOUS) {
+ if (led_get_autonomous_animation_config(led))
+ return -EIO;
+ aeu_select = led->led_playback.s_led_playback.aeu_selection;
+ if (aeu_select == 3)
+ aeu_select = 2;
+ for (i = 0; i < aeu_select + 1; i++)
+ aeu_create_sysfs_group(&led->aeu[i]);
+ }
+
+ return 0;
+}
+
+static int lp5812_create_sysfs_group(struct lp5812_led *led)
+{
+ int ret = 0;
+
+ ret = kobject_add(&led->kobj, &led->priv->dev->kobj, "%s",
+ led->led_name);
+ if (ret)
+ return ret;
+ ret = sysfs_create_group(&led->kobj, &led->attr_group);
+ if (ret)
+ return ret;
+ led->is_sysfs_created = 1;
+
+ return ret;
+}
+
+static void lp5812_remove_sysfs_group(struct lp5812_led *led)
+{
+ int i;
+
+ /* remove sysfs group of AEU */
+ for (i = 0; i < led->total_aeu; i++) {
+ if (led->aeu[i].enabled)
+ aeu_remove_sysfs_group(&led->aeu[i]);
+ }
+ led->is_sysfs_created = 0;
+ sysfs_remove_group(&led->kobj, &led->attr_group);
+ kobject_del(&led->kobj);
+}
+
+static ssize_t device_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int enable;
+ int ret;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ ret = kstrtoint(buf, 0, &enable);
+ if (ret)
+ return ret;
+
+ if (enable != 0 && enable != 1)
+ return -EINVAL;
+
+ /* set to hardware */
+ mutex_lock(&chip->lock);
+ if (lp5812_enable_disable(chip, enable)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return len;
+}
+
+static ssize_t device_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ u8 enable;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&chip->lock);
+ if (lp5812_read(chip, chip->regs->enable_reg, &enable)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return sysfs_emit(buf, "%d\n", enable);
+}
+
+static ssize_t device_command_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&chip->lock);
+ if (sysfs_streq(buf, "update")) {
+ lp5812_device_command(chip, UPDATE);
+ } else if (sysfs_streq(buf, "start")) {
+ lp5812_device_command(chip, START);
+ } else if (sysfs_streq(buf, "stop")) {
+ lp5812_device_command(chip, STOP);
+ } else if (sysfs_streq(buf, "pause")) {
+ lp5812_device_command(chip, PAUSE);
+ } else if (sysfs_streq(buf, "continue")) {
+ lp5812_device_command(chip, CONTINUE);
+ } else {
+ mutex_unlock(&chip->lock);
+ return -EINVAL;
+ }
+ mutex_unlock(&chip->lock);
+
+ return len;
+}
+
+static ssize_t sw_reset_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int reset;
+ int ret;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ ret = kstrtoint(buf, 0, &reset);
+ if (ret)
+ return ret;
+
+ if (reset != 1)
+ return -EINVAL;
+
+ /* reset hardware */
+ mutex_lock(&chip->lock);
+ lp5812_reset(chip);
+ msleep(1000);
+ /* set back manual mode as default for all LEDs */
+ lp5812_write(chip, (u16)DEV_CONFIG3, 0x00);
+ lp5812_write(chip, (u16)DEV_CONFIG4, 0x00);
+ lp5812_update_regs_config(chip);
+ mutex_unlock(&chip->lock);
+
+ /* Update sysfs based on default mode when hardware reseted*/
+ ret = lp5812_init_dev_config(chip, "direct_mode", 1);
+ if (ret) {
+ dev_err(dev, "%s: lp5812_init_dev_config failed\n",
+ __func__);
+ return ret;
+ }
+
+ return len;
+}
+
+static ssize_t fault_clear_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int fault_clear;
+ int ret;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ ret = kstrtoint(buf, 0, &fault_clear);
+ if (ret)
+ return ret;
+
+ if (fault_clear != 0 && fault_clear != 1 && fault_clear != 2 &&
+ fault_clear != 3) {
+ return -EINVAL;
+ }
+ mutex_lock(&chip->lock);
+ if (lp5812_fault_clear(chip, fault_clear)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return len;
+}
+
+static void lp5812_create_sysfs_via_led_name(struct lp5812_chip *chip,
+ const char *led_name)
+{
+ int i;
+
+ for (i = 0; i < chip->total_leds; i++) {
+ if (sysfs_streq(led_name, chip->leds[i].led_name)) {
+ lp5812_create_sysfs_group(&chip->leds[i]);
+
+ /*
+ * create AEU interface if current led mode is
+ * autonomous
+ */
+ aeu_create_multi_sysfs_groups(&chip->leds[i]);
+ }
+ }
+}
+
+static void set_mix_sel_led(struct lp5812_chip *chip, int mix_sel_led)
+{
+ if (mix_sel_led == 0) {
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_0 = 1;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_1 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_2 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_3 = 0;
+ }
+ if (mix_sel_led == 1) {
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_0 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_1 = 1;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_2 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_3 = 0;
+ }
+ if (mix_sel_led == 2) {
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_0 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_1 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_2 = 1;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_3 = 0;
+ }
+ if (mix_sel_led == 3) {
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_0 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_1 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_2 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_3 = 1;
+ }
+}
+
+static void parse_drive_mode(struct lp5812_chip *chip, char *str)
+{
+ int ret;
+ char *sub_str;
+ int tcm_scan_num, mix_scan_num, mix_sel_led;
+ int scan_oder0, scan_oder1, scan_oder2, scan_oder3;
+
+ sub_str = strsep(&str, ":");
+ if (sysfs_streq(sub_str, "direct_mode")) {
+ chip->u_drive_mode.s_drive_mode.led_mode = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_0 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_1 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_2 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_3 = 0;
+ }
+ if (sysfs_streq(sub_str, "tcmscan")) {
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_0 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_1 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_2 = 0;
+ chip->u_drive_mode.s_drive_mode.mix_sel_led_3 = 0;
+
+ /* Get tcm scan number */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &tcm_scan_num);
+ if (ret)
+ return;
+ chip->u_drive_mode.s_drive_mode.led_mode = tcm_scan_num;
+ switch (tcm_scan_num) {
+ case TCM_1_SCAN:
+ /* Get scan_oder0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ break;
+ case TCM_2_SCAN:
+ /* Get scan_order0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ /* Get scan_order1 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder1);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ chip->u_scan_order.s_scan_order.scan_order_1 =
+ scan_oder1;
+ break;
+ case TCM_3_SCAN:
+ /* Get scan_order0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ /* Get scan_order1 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder1);
+ if (ret)
+ return;
+ /* Get scan_order2 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder2);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ chip->u_scan_order.s_scan_order.scan_order_1 =
+ scan_oder1;
+ chip->u_scan_order.s_scan_order.scan_order_2 =
+ scan_oder2;
+ break;
+ case TCM_4_SCAN:
+ /* Get scan_order0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ /* Get scan_order1 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder1);
+ if (ret)
+ return;
+ /* Get scan_order2 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder2);
+ if (ret)
+ return;
+ /* Get scan_order3 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder3);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ chip->u_scan_order.s_scan_order.scan_order_1 =
+ scan_oder1;
+ chip->u_scan_order.s_scan_order.scan_order_2 =
+ scan_oder2;
+ chip->u_scan_order.s_scan_order.scan_order_3 =
+ scan_oder3;
+ break;
+ default:
+ break;
+ }
+ }
+ if (sysfs_streq(sub_str, "mixscan")) {
+ /* Get mix scan number */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &mix_scan_num);
+ if (ret)
+ return;
+ chip->u_drive_mode.s_drive_mode.led_mode = mix_scan_num + 4;
+ /* Get mix_sel_led */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &mix_sel_led);
+ if (ret)
+ return;
+ set_mix_sel_led(chip, mix_sel_led);
+ if (mix_scan_num == 1) {
+ /* Get scan_order0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ }
+ if (mix_scan_num == 2) {
+ /* Get scan_order0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ /* Get scan_order1 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder1);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ chip->u_scan_order.s_scan_order.scan_order_1 =
+ scan_oder1;
+ }
+ if (mix_scan_num == 3) {
+ /* Get scan_order0 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder0);
+ if (ret)
+ return;
+ /* Get scan_order1 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder1);
+ if (ret)
+ return;
+ /* Get scan_order2 */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &scan_oder2);
+ if (ret)
+ return;
+ chip->u_scan_order.s_scan_order.scan_order_0 =
+ scan_oder0;
+ chip->u_scan_order.s_scan_order.scan_order_1 =
+ scan_oder1;
+ chip->u_scan_order.s_scan_order.scan_order_2 =
+ scan_oder2;
+ }
+ }
+}
+
+static void leds_remove_existed_sysfs(struct lp5812_chip *chip)
+{
+ int i;
+
+ for (i = 0; i < chip->total_leds; i++) {
+ if (chip->leds[i].is_sysfs_created)
+ lp5812_remove_sysfs_group(&chip->leds[i]);
+ }
+}
+
+static int lp5812_init_dev_config(struct lp5812_chip *chip,
+ const char *drive_mode, int rm_led_sysfs)
+{
+ int i;
+ int num_drive_mode;
+ int is_valid_arg = 0;
+ char *str;
+ const char **led_ptr;
+
+ str = kstrdup(drive_mode, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ num_drive_mode = ARRAY_SIZE(chip_leds_map);
+ for (i = 0; i < num_drive_mode; ++i) {
+ if (sysfs_streq(str, chip_leds_map[i].drive_mode)) {
+ if (rm_led_sysfs)
+ leds_remove_existed_sysfs(chip);
+ parse_drive_mode(chip, str);
+ led_ptr = chip_leds_map[i].led_arr;
+ while (*led_ptr) {
+ lp5812_create_sysfs_via_led_name(chip,
+ *led_ptr);
+ ++led_ptr;
+ }
+ is_valid_arg = 1;
+ break;
+ }
+ }
+ kfree(str);
+ if (!is_valid_arg)
+ return -EINVAL;
+
+ return 0;
+}
+
+static ssize_t dev_config_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ int ret;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_init_dev_config(chip, buf, 1);
+ if (ret) {
+ dev_err(chip->dev, "%s: lp5812_init_dev_config failed\n",
+ __func__);
+ goto out;
+ }
+
+ /* set drive mode and scan order to hardware */
+ ret = lp5812_set_drive_mode_scan_order(chip);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+ ret = lp5812_update_regs_config(chip);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+
+ /* disable all LEDs that were previously enabled */
+ ret = lp5812_disable_all_leds(chip);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+ mutex_unlock(&chip->lock);
+
+ return len;
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static char *parse_dev_config_info(struct lp5812_chip *chip)
+{
+ char *tmp;
+ int order_0, order_1, order_2, order_3;
+ char *scan_order_0 = " scan_order_0: ";
+ char *scan_order_1 = " scan_order_1: ";
+ char *scan_order_2 = " scan_order_2: ";
+ char *scan_order_3 = " scan_order_3: ";
+
+ tmp = kmalloc(128, GFP_KERNEL);
+ if (!tmp)
+ return NULL;
+
+ order_0 = chip->u_scan_order.s_scan_order.scan_order_0;
+ order_1 = chip->u_scan_order.s_scan_order.scan_order_1;
+ order_2 = chip->u_scan_order.s_scan_order.scan_order_2;
+ order_3 = chip->u_scan_order.s_scan_order.scan_order_3;
+
+ switch (chip->u_drive_mode.s_drive_mode.led_mode) {
+ case DIRECT_MODE:
+ sprintf(tmp, "%s", "Mode: direct mode;");
+ break;
+ case TCM_1_SCAN:
+ sprintf(tmp, "%s%s%d%s", "Mode: tcm 1 scan;", scan_order_0,
+ order_0, ";");
+ break;
+ case TCM_2_SCAN:
+ sprintf(tmp, "%s%s%d%s%s%d%s", "Mode: tcm 2 scan;",
+ scan_order_0, order_0, ";", scan_order_1, order_1, ";");
+ break;
+ case TCM_3_SCAN:
+ sprintf(tmp, "%s%s%d%s%s%d%s%s%d%s", "Mode: tcm 3 scan;",
+ scan_order_0, order_0, ";", scan_order_1, order_1,
+ ";", scan_order_2, order_2, ";");
+ break;
+ case TCM_4_SCAN:
+ sprintf(tmp, "%s%s%d%s%s%d%s%s%d%s%s%d%s", "Mode: tcm 4 scan;",
+ scan_order_0, order_0, ";", scan_order_1, order_1, ";",
+ scan_order_2, order_2, ";", scan_order_3, order_3, ";");
+ break;
+ case MIX_1_SCAN:
+ sprintf(tmp, "%s%s%d%s", "Mode: mix 1 scan;", scan_order_0,
+ order_0, ";");
+ break;
+ case MIX_2_SCAN:
+ sprintf(tmp, "%s%s%d%s%s%d%s", "Mode: mix 2 scan;",
+ scan_order_0, order_0, ";", scan_order_1, order_1, ";");
+ break;
+ case MIX_3_SCAN:
+ sprintf(tmp, "%s%s%d%s%s%d%s%s%d%s", "Mode: mix 3 scan;",
+ scan_order_0, order_0, ";", scan_order_1, order_1,
+ ";", scan_order_2, order_2, ";");
+ break;
+ }
+
+ if (chip->u_drive_mode.s_drive_mode.mix_sel_led_0)
+ strcat(tmp, " Direct output: OUT0;");
+ else if (chip->u_drive_mode.s_drive_mode.mix_sel_led_1)
+ strcat(tmp, " Direct output: OUT1;");
+ else if (chip->u_drive_mode.s_drive_mode.mix_sel_led_2)
+ strcat(tmp, " Direct output: OUT2;");
+ else if (chip->u_drive_mode.s_drive_mode.mix_sel_led_3)
+ strcat(tmp, " Direct output: OUT3;");
+ else
+ strcat(tmp, " Direct output: None;");
+ return tmp;
+}
+
+static ssize_t dev_config_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int i;
+ int num_drive_mode;
+ char *mode_info;
+ char *total_str;
+ size_t total_length;
+ char *const_str = "\nPlease select below valid drive mode:\n";
+ char *const_ex_str = "For Ex: echo tcmscan:1:0 > dev_config\n";
+ int ret = 0;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ /* get drive mode and scan order */
+ mutex_lock(&chip->lock);
+ ret = lp5812_get_drive_mode_scan_order(chip);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ mode_info = parse_dev_config_info(chip);
+ if (!mode_info)
+ return -ENOMEM;
+
+ num_drive_mode = ARRAY_SIZE(chip_leds_map);
+ total_length = strlen(mode_info) + strlen(const_str) +
+ strlen(const_ex_str) + 1;
+ for (i = 0; i < num_drive_mode; ++i) {
+ total_length += strlen(chip_leds_map[i].drive_mode) +
+ strlen("\n");
+ }
+
+ total_str = kmalloc(total_length, GFP_KERNEL);
+ if (!total_str)
+ return -ENOMEM;
+
+ sprintf(total_str, "%s%s%s", mode_info, const_str, const_ex_str);
+ for (i = 0; i < num_drive_mode; ++i) {
+ strcat(total_str, chip_leds_map[i].drive_mode);
+ strcat(total_str, "\n");
+ }
+
+ ret = sysfs_emit(buf, "%s", total_str);
+ kfree(mode_info);
+ kfree(total_str);
+
+ return ret;
+}
+
+static ssize_t tsd_config_status_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_val;
+ int tsd_stat;
+ int config_stat;
+ struct lp5812_chip *chip = i2c_get_clientdata(to_i2c_client(dev));
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_read(chip, (u16)TSD_CONFIG_STAT_REG, ®_val);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+ tsd_stat = (reg_val >> 1) & 0x01;
+ config_stat = reg_val & 0x01;
+
+ return sysfs_emit(buf, "%d %d\n", tsd_stat, config_stat);
+}
+
+static LP5812_DEV_ATTR_RW(device_enable);
+static LP5812_DEV_ATTR_RW(dev_config);
+static LP5812_DEV_ATTR_WO(device_command);
+static LP5812_DEV_ATTR_WO(sw_reset);
+static LP5812_DEV_ATTR_WO(fault_clear);
+static LP5812_DEV_ATTR_RO(tsd_config_status);
+
+static struct attribute *lp5812_chip_attributes[] = {
+ &dev_attr_device_enable.attr,
+ &dev_attr_device_command.attr,
+ &dev_attr_sw_reset.attr,
+ &dev_attr_fault_clear.attr,
+ &dev_attr_dev_config.attr,
+ &dev_attr_tsd_config_status.attr,
+ NULL
+};
+
+static ssize_t led_activate_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u8 val = 0;
+ u16 reg = 0;
+ u8 reg_val = 0;
+ int ret = 0;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ if (led->led_number < 0x8)
+ reg = LED_ENABLE_1_REG;
+ else
+ reg = LED_ENABLE_2_REG;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_read(chip, reg, ®_val);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ val = (reg_val & (1 << (led->led_number % 8))) ? 1 : 0;
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t led_activate_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int val = 0;
+ u16 reg = 0;
+ u8 reg_val = 0;
+ int ret = 0;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+
+ if (val != 0 && val != 1)
+ return -EINVAL;
+
+ if (led->led_number < 0x8)
+ reg = LED_ENABLE_1_REG;
+ else
+ reg = LED_ENABLE_2_REG;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_read(chip, reg, ®_val);
+ if (ret == 0) {
+ if (val == 0) {
+ ret = lp5812_write(chip, reg,
+ reg_val & (~(1 << (led->led_number % 8))));
+ } else {
+ ret = lp5812_write(chip, reg,
+ reg_val | (1 << (led->led_number % 8)));
+ }
+ }
+ mutex_unlock(&chip->lock);
+
+ if (ret)
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t led_mode_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = 0;
+ enum control_mode mode;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_get_led_mode(chip, led->led_number, &mode);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n", AUTONOMOUS == mode ? "autonomous" : "manual");
+}
+
+static ssize_t led_mode_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ enum control_mode val;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ if (sysfs_streq(buf, "manual")) {
+ /* Remove AEU sysfs interface for current led in manual mode */
+ aeu_remove_multi_sysfs_groups(led);
+ val = MANUAL;
+ } else if (sysfs_streq(buf, "autonomous")) {
+ val = AUTONOMOUS;
+ } else {
+ return -EINVAL;
+ }
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_set_led_mode(chip, led->led_number, val);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+
+ ret = aeu_create_multi_sysfs_groups(led);
+ if (ret) {
+ dev_err(chip->dev, "aeu_create_multi_sysfs_groups() failed\n");
+ goto out;
+ }
+
+ mutex_unlock(&chip->lock);
+ return count;
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static ssize_t led_manual_dc_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int manual_dc;
+ int ret;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ ret = kstrtoint(buf, 0, &manual_dc);
+ if (ret)
+ return ret;
+
+ if (manual_dc < 0 || manual_dc > 255)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ if (lp5812_manual_dc_pwm_control(chip, led->led_number,
+ manual_dc, ANALOG)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+static ssize_t led_manual_dc_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u8 val;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ if (lp5812_manual_dc_pwm_read(chip, led->led_number, &val, ANALOG)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t led_manual_pwm_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int manual_pwm;
+ int ret;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ ret = kstrtoint(buf, 0, &manual_pwm);
+ if (ret)
+ return ret;
+
+ if (manual_pwm < 0 || manual_pwm > 255)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ if (lp5812_manual_dc_pwm_control(chip, led->led_number,
+ manual_pwm, PWM)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+static ssize_t led_manual_pwm_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u8 val;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ if (lp5812_manual_dc_pwm_read(chip, led->led_number, &val, PWM)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t led_auto_dc_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int auto_dc;
+ int ret;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ ret = kstrtoint(buf, 0, &auto_dc);
+ if (ret)
+ return ret;
+
+ if (auto_dc < 0 || auto_dc > 255)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ if (lp5812_autonomous_dc_pwm_control(chip, led->led_number,
+ auto_dc, ANALOG)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+static ssize_t led_auto_dc_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u8 val;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ if (lp5812_autonomous_dc_pwm_read(chip, led->led_number,
+ &val, ANALOG)) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static int parse_autonomous_animation_config(struct lp5812_led *led,
+ const char *user_buf)
+{
+ int ret;
+ int i;
+ char *str;
+ char *sub_str;
+ int aeu_select, start_pause_time, stop_pause_time, led_playback_time;
+
+ str = kstrdup(user_buf, GFP_KERNEL);
+ if (!str)
+ return -ENOMEM;
+
+ /* parse aeu_select */
+ sub_str = strsep(&str, ":");
+ ret = kstrtoint(sub_str, 0, &aeu_select);
+ if (ret)
+ goto free_str;
+ if (aeu_select < 1 || aeu_select > 3) {
+ ret = -EINVAL;
+ goto free_str;
+ }
+
+ aeu_remove_multi_sysfs_groups(led);
+
+ for (i = 0; i < aeu_select; i++)
+ aeu_create_sysfs_group(&led->aeu[i]);
+ led->led_playback.s_led_playback.aeu_selection = aeu_select - 1;
+
+ /* parse start_pause_time */
+ sub_str = strsep(&str, ":");
+ if (sub_str) {
+ ret = kstrtoint(sub_str, 0, &start_pause_time);
+ if (ret)
+ goto free_str;
+ if (start_pause_time < 0 || start_pause_time > 15) {
+ ret = -EINVAL;
+ goto free_str;
+ }
+ led->start_stop_pause_time.s_time.second = start_pause_time;
+ } else {
+ led->start_stop_pause_time.s_time.second = 15;
+ }
+
+ /* parse stop_pause_time */
+ sub_str = strsep(&str, ":");
+ if (sub_str) {
+ ret = kstrtoint(sub_str, 0, &stop_pause_time);
+ if (ret)
+ goto free_str;
+ if (stop_pause_time < 0 || stop_pause_time > 15) {
+ ret = -EINVAL;
+ goto free_str;
+ }
+ led->start_stop_pause_time.s_time.first = stop_pause_time;
+ } else {
+ led->start_stop_pause_time.s_time.first = 15;
+ }
+
+ /* parse led_playback_time */
+ sub_str = strsep(&str, ":");
+ if (sub_str) {
+ ret = kstrtoint(sub_str, 0, &led_playback_time);
+ if (ret)
+ goto free_str;
+ if (led_playback_time < 0 || led_playback_time > 15) {
+ ret = -EINVAL;
+ goto free_str;
+ }
+ led->led_playback.s_led_playback.led_playback_time =
+ led_playback_time;
+ } else {
+ led->led_playback.s_led_playback.led_playback_time = 15;
+ }
+
+ kfree(str);
+ return 0;
+
+free_str:
+ kfree(str);
+ return ret;
+}
+
+static ssize_t led_auto_animation_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ enum control_mode led_mode;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ /* First check the mode of led is manual or autonomous */
+ ret = lp5812_get_led_mode(chip, led->led_number, &led_mode);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+
+ if (led_mode == AUTONOMOUS) {
+ ret = parse_autonomous_animation_config(led, buf);
+ if (ret) {
+ dev_err(chip->dev, "parse_autonomous_animation_config failed\n");
+ goto out;
+ }
+ /* Write data to hardware */
+ ret = led_set_autonomous_animation_config(led);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+ }
+ mutex_unlock(&chip->lock);
+ return count;
+
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static ssize_t led_auto_animation_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret;
+ char tmp_str[256] = {};
+ char usage[128] = {};
+ char *aeu_select = "AEU Select: ";
+ char *start_pause_time = "Start pause time: ";
+ char *stop_pause_time = "; Stop pause time: ";
+ char *led_playback_time = "; LED Playback time: ";
+ int aeu_selection, playback_time, start_pause, stop_pause;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ sprintf(usage, "%s%s",
+ "Command usage: echo (aeu number):(start pause time):",
+ "(stop pause time):(playback time) > autonomous_animation");
+
+ mutex_lock(&chip->lock);
+ ret = led_get_autonomous_animation_config(led);
+ if (ret) {
+ ret = -EIO;
+ goto out;
+ }
+
+ /* parse config and feedback to userspace */
+ aeu_selection = led->led_playback.s_led_playback.aeu_selection;
+ playback_time = led->led_playback.s_led_playback.led_playback_time;
+ start_pause = led->start_stop_pause_time.s_time.second;
+ stop_pause = led->start_stop_pause_time.s_time.first;
+ if (aeu_selection == ONLY_AEU1) {
+ sprintf(tmp_str, "%s%s%s%s%s%s%s%s\n", aeu_select,
+ "Only use AEU1; ", start_pause_time,
+ time_name_array[start_pause], stop_pause_time,
+ time_name_array[stop_pause], led_playback_time,
+ led_playback_time_arr[playback_time]);
+ } else if (aeu_selection == AEU1_AEU2) {
+ sprintf(tmp_str, "%s%s%s%s%s%s%s%s\n", aeu_select,
+ "Use AEU1 and AEU2; ", start_pause_time,
+ time_name_array[start_pause], stop_pause_time,
+ time_name_array[stop_pause], led_playback_time,
+ led_playback_time_arr[playback_time]);
+ } else {
+ sprintf(tmp_str, "%s%s%s%s%s%s%s%s\n", aeu_select,
+ "Use AEU1,AEU2 and AEU3; ", start_pause_time,
+ time_name_array[start_pause], stop_pause_time,
+ time_name_array[stop_pause], led_playback_time,
+ led_playback_time_arr[playback_time]);
+ }
+ strcat(tmp_str, usage);
+ mutex_unlock(&chip->lock);
+ return sysfs_emit(buf, "%s\n", tmp_str);
+
+out:
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
+static ssize_t led_lod_lsd_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = 0;
+ u8 lsd_status, lod_status;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_read_lsd_status(chip, led->led_number, &lsd_status);
+ if (ret) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+
+ ret = lp5812_read_lod_status(chip, led->led_number, &lod_status);
+ if (ret) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return sysfs_emit(buf, "%d %d\n", lod_status, lsd_status);
+}
+
+static ssize_t led_auto_pwm_val_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = 0;
+ u8 auto_pwm_val;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_read_auto_pwm_value(chip, led->led_number, &auto_pwm_val);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return sysfs_emit(buf, "%d\n", auto_pwm_val);
+}
+
+static ssize_t led_aep_status_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = 0;
+ u8 aep_status;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_read_aep_status(chip, led->led_number, &aep_status);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return sysfs_emit(buf, "%d\n", aep_status);
+}
+
+static ssize_t led_pwm_phase_align_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int val, ret;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ if (sysfs_streq(buf, "forward"))
+ val = 0;
+ else if (sysfs_streq(buf, "middle"))
+ val = 2;
+ else if (sysfs_streq(buf, "backward"))
+ val = 3;
+ else
+ return -EINVAL;
+ mutex_lock(&chip->lock);
+ ret = lp5812_set_phase_align(chip, led->led_number, val);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t led_pwm_phase_align_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int val, ret;
+ char *str;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_get_phase_align(chip, led->led_number, &val);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+ if (val == 0 || val == 1)
+ str = "forward";
+ else if (val == 2)
+ str = "middle";
+ else
+ str = "backward";
+
+ return sysfs_emit(buf, "%s\n", str);
+}
+
+static ssize_t led_pwm_dimming_scale_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int ret;
+ enum pwm_dimming_scale val;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ if (sysfs_streq(buf, "linear"))
+ val = LINEAR;
+ else if (sysfs_streq(buf, "exponential"))
+ val = EXPONENTIAL;
+ else
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_set_pwm_dimming_scale(chip, led->led_number, val);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return count;
+}
+
+static ssize_t led_pwm_dimming_scale_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = 0;
+ enum pwm_dimming_scale scale;
+ struct lp5812_led *led = to_lp5812_led(kobj);
+ struct lp5812_chip *chip = led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = lp5812_get_pwm_dimming_scale(chip, led->led_number, &scale);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return sysfs_emit(buf, "%s\n",
+ scale == EXPONENTIAL ? "exponential" : "linear");
+}
+
+static LP5812_KOBJ_ATTR_RW(activate, led_activate_show, led_activate_store);
+static LP5812_KOBJ_ATTR_RW(mode, led_mode_show, led_mode_store);
+static LP5812_KOBJ_ATTR_RW(manual_dc, led_manual_dc_show,
+ led_manual_dc_store);
+static LP5812_KOBJ_ATTR_RW(manual_pwm, led_manual_pwm_show,
+ led_manual_pwm_store);
+static LP5812_KOBJ_ATTR_RW(autonomous_dc, led_auto_dc_show,
+ led_auto_dc_store);
+static LP5812_KOBJ_ATTR_RW(autonomous_animation, led_auto_animation_show,
+ led_auto_animation_store);
+static LP5812_KOBJ_ATTR_RW(pwm_phase_align, led_pwm_phase_align_show,
+ led_pwm_phase_align_store);
+static LP5812_KOBJ_ATTR_RW(pwm_dimming_scale, led_pwm_dimming_scale_show,
+ led_pwm_dimming_scale_store);
+static LP5812_KOBJ_ATTR_RO(lod_lsd, led_lod_lsd_show);
+static LP5812_KOBJ_ATTR_RO(auto_pwm_val, led_auto_pwm_val_show);
+static LP5812_KOBJ_ATTR_RO(aep_status, led_aep_status_show);
+
+static struct attribute *led_kobj_attributes[] = {
+ &kobj_attr_activate.attr,
+ &kobj_attr_mode.attr,
+ &kobj_attr_manual_dc.attr,
+ &kobj_attr_manual_pwm.attr,
+ &kobj_attr_autonomous_dc.attr,
+ &kobj_attr_autonomous_animation.attr,
+ &kobj_attr_lod_lsd.attr,
+ &kobj_attr_auto_pwm_val.attr,
+ &kobj_attr_aep_status.attr,
+ &kobj_attr_pwm_phase_align.attr,
+ &kobj_attr_pwm_dimming_scale.attr,
+ NULL
+};
+
+static ssize_t aeu_pwm_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count, enum pwm_slope_time_num pwm_channel)
+{
+ int val = 0;
+ int ret = 0;
+ struct anim_engine_unit *aeu = to_anim_engine_unit(kobj);
+ struct lp5812_chip *chip = aeu->led->priv;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+ if (val < 0 || val > 255)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ ret = led_aeu_pwm_set_val(aeu, val, pwm_channel);
+ if (ret != 0) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+static ssize_t aeu_pwm_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf, enum pwm_slope_time_num pwm_channel)
+{
+ int ret = 0;
+ u8 val = 0;
+ struct anim_engine_unit *aeu = to_anim_engine_unit(kobj);
+ struct lp5812_chip *chip = aeu->led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = led_aeu_pwm_get_val(aeu, &val, pwm_channel);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t aeu_pwm1_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_pwm_store(kobj, attr, buf, count, PWM1);
+}
+
+static ssize_t aeu_pwm1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_pwm_show(kobj, attr, buf, PWM1);
+}
+
+static ssize_t aeu_pwm2_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_pwm_store(kobj, attr, buf, count, PWM2);
+}
+
+static ssize_t aeu_pwm2_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_pwm_show(kobj, attr, buf, PWM2);
+}
+
+static ssize_t aeu_pwm3_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_pwm_store(kobj, attr, buf, count, PWM3);
+}
+
+static ssize_t aeu_pwm3_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_pwm_show(kobj, attr, buf, PWM3);
+}
+
+static ssize_t aeu_pwm4_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_pwm_store(kobj, attr, buf, count, PWM4);
+}
+
+static ssize_t aeu_pwm4_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_pwm_show(kobj, attr, buf, PWM4);
+}
+
+static ssize_t aeu_pwm5_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_pwm_store(kobj, attr, buf, count, PWM5);
+}
+
+static ssize_t aeu_pwm5_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_pwm_show(kobj, attr, buf, PWM5);
+}
+
+static ssize_t aeu_slope_time_store(struct kobject *kobj, struct kobj_attribute *attr,
+ const char *buf, size_t count, enum pwm_slope_time_num slope_time_num)
+{
+ int val = 0;
+ int ret = 0;
+ struct anim_engine_unit *aeu = to_anim_engine_unit(kobj);
+ struct lp5812_chip *chip = aeu->led->priv;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+
+ if (val < 0x00 || val > 0x0F)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ ret = led_aeu_slope_time_set_val(aeu, val, slope_time_num);
+ if (ret != 0) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+static ssize_t aeu_slope_time_show(struct kobject *kobj, struct kobj_attribute *attr,
+ char *buf, enum pwm_slope_time_num slope_time_num)
+{
+ int ret = 0;
+ u8 val = 0;
+ struct anim_engine_unit *aeu = to_anim_engine_unit(kobj);
+ struct lp5812_chip *chip = aeu->led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = led_aeu_slope_time_get_val(aeu, &val, slope_time_num);
+ mutex_unlock(&chip->lock);
+ if (ret)
+ return -EIO;
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t aeu_slope_time_t1_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T1);
+}
+
+static ssize_t aeu_slope_time_t1_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_slope_time_show(kobj, attr, buf, SLOPE_T1);
+}
+
+static ssize_t aeu_slope_time_t2_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T2);
+}
+
+static ssize_t aeu_slope_time_t2_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_slope_time_show(kobj, attr, buf, SLOPE_T2);
+}
+
+static ssize_t aeu_slope_time_t3_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T3);
+}
+
+static ssize_t aeu_slope_time_t3_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_slope_time_show(kobj, attr, buf, SLOPE_T3);
+}
+
+static ssize_t aeu_slope_time_t4_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T4);
+}
+
+static ssize_t aeu_slope_time_t4_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ return aeu_slope_time_show(kobj, attr, buf, SLOPE_T4);
+}
+
+static ssize_t aeu_playback_time_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int val = 0;
+ int ret = 0;
+ struct anim_engine_unit *aeu = to_anim_engine_unit(kobj);
+ struct lp5812_chip *chip = aeu->led->priv;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return -EINVAL;
+
+ if (val < 0 || val > 3)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ ret = led_aeu_playback_time_set_val(aeu, val);
+ if (ret != 0) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+
+static ssize_t aeu_playback_time_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int ret = 0;
+ u8 val = 0;
+ struct anim_engine_unit *aeu = to_anim_engine_unit(kobj);
+ struct lp5812_chip *chip = aeu->led->priv;
+
+ mutex_lock(&chip->lock);
+ ret = led_aeu_playback_time_get_val(aeu, &val);
+ if (ret != 0) {
+ mutex_unlock(&chip->lock);
+ return -EIO;
+ }
+ mutex_unlock(&chip->lock);
+
+ return sysfs_emit(buf, "%d\n", val);
+}
+
+static LP5812_KOBJ_ATTR_RW(pwm1, aeu_pwm1_show, aeu_pwm1_store);
+static LP5812_KOBJ_ATTR_RW(pwm2, aeu_pwm2_show, aeu_pwm2_store);
+static LP5812_KOBJ_ATTR_RW(pwm3, aeu_pwm3_show, aeu_pwm3_store);
+static LP5812_KOBJ_ATTR_RW(pwm4, aeu_pwm4_show, aeu_pwm4_store);
+static LP5812_KOBJ_ATTR_RW(pwm5, aeu_pwm5_show, aeu_pwm5_store);
+static LP5812_KOBJ_ATTR_RW(slope_time_t1, aeu_slope_time_t1_show,
+ aeu_slope_time_t1_store);
+static LP5812_KOBJ_ATTR_RW(slope_time_t2, aeu_slope_time_t2_show,
+ aeu_slope_time_t2_store);
+static LP5812_KOBJ_ATTR_RW(slope_time_t3, aeu_slope_time_t3_show,
+ aeu_slope_time_t3_store);
+static LP5812_KOBJ_ATTR_RW(slope_time_t4, aeu_slope_time_t4_show,
+ aeu_slope_time_t4_store);
+static LP5812_KOBJ_ATTR_RW(playback_time, aeu_playback_time_show,
+ aeu_playback_time_store);
+
+static struct attribute *aeu_kobj_attributes[] = {
+ &kobj_attr_pwm1.attr,
+ &kobj_attr_pwm2.attr,
+ &kobj_attr_pwm3.attr,
+ &kobj_attr_pwm4.attr,
+ &kobj_attr_pwm5.attr,
+ &kobj_attr_slope_time_t1.attr,
+ &kobj_attr_slope_time_t2.attr,
+ &kobj_attr_slope_time_t3.attr,
+ &kobj_attr_slope_time_t4.attr,
+ &kobj_attr_playback_time.attr,
+ NULL
+};
+
+static void aeu_init_properties(struct lp5812_led *led)
+{
+ int i;
+
+ for (i = 0; i < MAX_AEU; i++) {
+ led->aeu[i].aeu_name = aeu_name_array[i];
+ led->aeu[i].aeu_number = i + 1;
+ led->aeu[i].led = led;
+ led->aeu[i].enabled = 0;
+ led->aeu[i].attr_group.attrs = aeu_kobj_attributes;
+ kobject_init(&led->aeu[i].kobj, &aeu_ktype);
+ }
+}
+
+static int lp5812_probe(struct i2c_client *client)
+{
+ struct lp5812_chip *chip;
+ int i;
+ int ret;
+ u8 val;
+
+ chip = devm_kzalloc(&client->dev, sizeof(struct lp5812_chip),
+ GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+ mutex_init(&chip->lock);
+ chip->i2c_cl = client;
+ chip->dev = &client->dev;
+ chip->regs = ®s;
+ chip->command = NONE;
+ chip->total_leds = MAX_LEDS;
+ chip->attr_group.name = "lp5812_chip_setup";
+ chip->attr_group.attrs = lp5812_chip_attributes;
+ chip->chip_leds_map = chip_leds_map;
+ chip->u_drive_mode.drive_mode_val = 0x10;
+ chip->u_scan_order.scan_order_val = 0x00;
+
+ /* initialize property for each led */
+ for (i = 0; i < MAX_LEDS; i++) {
+ chip->leds[i].led_name = led_name_array[i];
+ chip->leds[i].led_number = i;
+ chip->leds[i].anim_base_addr = anim_base_addr_array[i];
+ chip->leds[i].enable = 0; /* LED disable as default */
+ chip->leds[i].mode = MANUAL; /* manual mode as default */
+ chip->leds[i].priv = chip;
+ chip->leds[i].total_aeu = MAX_AEU;
+ chip->leds[i].led_playback.led_playback_val = 0;
+ chip->leds[i].start_stop_pause_time.time_val = 0;
+ /* sysfs for this led not be created */
+ chip->leds[i].is_sysfs_created = 0;
+ chip->leds[i].attr_group.attrs = led_kobj_attributes;
+ kobject_init(&chip->leds[i].kobj, &led_ktype);
+
+ /* init animation engine unit properties */
+ aeu_init_properties(&chip->leds[i]);
+
+ /* set autonomous animation config as default for all LEDs */
+ led_set_autonomous_animation_config(&chip->leds[i]);
+ }
+
+ i2c_set_clientdata(client, chip);
+
+ ret = sysfs_create_group(&chip->dev->kobj, &chip->attr_group);
+ if (ret)
+ return dev_err_probe(chip->dev, ret, "sysfs_create_group failed\n");
+
+ ret = lp5812_init_dev_config(chip, "tcmscan:4:0:1:2:3", 0);
+ if (ret)
+ return dev_err_probe(chip->dev, ret, "%s: lp5812_init_dev_config failed\n",
+ __func__);
+
+ ret = lp5812_initialize(chip);
+ if (ret)
+ return dev_err_probe(chip->dev, ret, "lp5812 initialize failed\n");
+
+ /* code to verify i2c read/write ok or not */
+ lp5812_read(chip, (u16)DEV_CONFIG2, &val);
+
+ lp5812_write(chip, (u16)LED_A1_AUTO_BASE_ADRR, 0x14);
+ lp5812_read(chip, (u16)LED_A1_AUTO_BASE_ADRR, &val);
+ /* End code to verify i2c read/write*/
+
+ return 0;
+}
+
+static void lp5812_remove(struct i2c_client *client)
+{
+ struct lp5812_chip *chip = i2c_get_clientdata(client);
+
+ mutex_destroy(&chip->lock);
+ leds_remove_existed_sysfs(chip);
+ sysfs_remove_group(&chip->dev->kobj, &chip->attr_group);
+
+ lp5812_disable_all_leds(chip);
+
+ lp5812_enable_disable(chip, 0);
+}
+
+static const struct i2c_device_id lp5812_id[] = {
+ { "lp5812" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, lp5812_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_lp5812_match[] = {
+ { .compatible = "ti,lp5812", },
+ {/* NULL */}
+};
+
+MODULE_DEVICE_TABLE(of, of_lp5812_match);
+#endif
+
+static struct i2c_driver lp5812_driver = {
+ .driver = {
+ .name = "lp5812",
+ .of_match_table = of_match_ptr(of_lp5812_match),
+ },
+ .probe = lp5812_probe,
+ .remove = lp5812_remove,
+ .id_table = lp5812_id
+};
+
+module_i2c_driver(lp5812_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP5812 LED Driver");
+MODULE_AUTHOR("Jared Zhou");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp5812.h b/drivers/leds/leds-lp5812.h
new file mode 100644
index 000000000000..2c26d7cbcc2f
--- /dev/null
+++ b/drivers/leds/leds-lp5812.h
@@ -0,0 +1,350 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * LP5812 Driver Header
+ *
+ * Copyright (C) 2025 Texas Instruments
+ *
+ * Author: Jared Zhou <jared-zhou@ti.com>
+ */
+
+#ifndef _LEDS_LP5812_H_
+#define _LEDS_LP5812_H_
+
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/sysfs.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/leds.h>
+#include <linux/delay.h>
+
+#define CHIP_EN_REG 0x00
+
+#define DEV_CONFIG0 0x01
+#define DEV_CONFIG1 0x02
+#define DEV_CONFIG2 0x03
+#define DEV_CONFIG3 0x04
+#define DEV_CONFIG4 0x05
+#define DEV_CONFIG5 0x06
+#define DEV_CONFIG6 0x07
+#define DEV_CONFIG7 0x08
+#define DEV_CONFIG8 0x09
+#define DEV_CONFIG9 0x0A
+#define DEV_CONFIG10 0x0B
+#define DEV_CONFIG11 0x0c
+#define DEV_CONFIG12 0x0D
+
+#define CMD_UPDATE_REG 0x10
+#define CMD_START_REG 0x11
+#define CMD_STOP_REG 0x12
+#define CMD_PAUSE_REG 0x13
+#define CMD_CONTINUE_REG 0x14
+
+#define LED_ENABLE_1_REG 0x20
+#define LED_ENABLE_2_REG 0x21
+
+#define FAULT_CLEAR_REG 0x22
+#define RESET_REG 0x23
+
+#define MANUAL_DC_LED_0_REG 0x30
+#define MANUAL_PWM_LED_0_REG 0x40
+#define AUTO_DC_LED_0_REG 0x50
+
+/* value for register */
+#define UPDATE_CMD_VAL 0x55
+#define START_CMD_VAL 0xFF
+#define STOP_CMD_VAL 0xAA
+#define PAUSE_CMD_VAL 0x33
+#define CONTINUE_CMD_VAL 0xCC
+
+#define CHIP_ENABLE 0x01
+#define CHIP_DISABLE 0x00
+
+#define FAULT_CLEAR_ALL 0x07
+#define TSD_CLEAR_VAL 0x04
+#define LSD_CLEAR_VAL 0x02
+#define LOD_CLEAR_VAL 0x01
+#define RESET_REG_VAL 0x66
+
+#define LED0_AUTO_BASE_ADRR 0x80
+#define LED1_AUTO_BASE_ADRR 0x9A
+#define LED2_AUTO_BASE_ADRR 0xB4
+#define LED3_AUTO_BASE_ADRR 0xCE
+#define LED_A0_AUTO_BASE_ADRR 0xE8
+#define LED_A1_AUTO_BASE_ADRR 0x102
+#define LED_A2_AUTO_BASE_ADRR 0x11C
+#define LED_B0_AUTO_BASE_ADRR 0x136
+#define LED_B1_AUTO_BASE_ADRR 0x150
+#define LED_B2_AUTO_BASE_ADRR 0x16A
+#define LED_C0_AUTO_BASE_ADRR 0x184
+#define LED_C1_AUTO_BASE_ADRR 0x19E
+#define LED_C2_AUTO_BASE_ADRR 0x1B8
+#define LED_D0_AUTO_BASE_ADRR 0x1D2
+#define LED_D1_AUTO_BASE_ADRR 0x1EC
+#define LED_D2_AUTO_BASE_ADRR 0x206
+
+/* Flag Registers */
+#define TSD_CONFIG_STAT_REG 0x300
+#define LOD_STAT_1_REG 0x301
+#define LOD_STAT_2_REG 0x302
+#define LSD_STAT_1_REG 0x303
+#define LSD_STAT_2_REG 0x304
+
+#define AUTO_PWM_BASE_ADDR 0x305
+
+#define AEP_STATUS_0_REG 0x315
+#define AEP_STATUS_1_REG 0x316
+#define AEP_STATUS_2_REG 0x317
+#define AEP_STATUS_3_REG 0x318
+#define AEP_STATUS_4_REG 0x319
+#define AEP_STATUS_5_REG 0x31A
+#define AEP_STATUS_6_REG 0x31B
+#define AEP_STATUS_7_REG 0x31C
+
+#define LED0 "led_0"
+#define LED1 "led_1"
+#define LED2 "led_2"
+#define LED3 "led_3"
+#define LED_A0 "led_A0"
+#define LED_A1 "led_A1"
+#define LED_A2 "led_A2"
+#define LED_B0 "led_B0"
+#define LED_B1 "led_B1"
+#define LED_B2 "led_B2"
+#define LED_C0 "led_C0"
+#define LED_C1 "led_C1"
+#define LED_C2 "led_C2"
+#define LED_D0 "led_D0"
+#define LED_D1 "led_D1"
+#define LED_D2 "led_D2"
+
+/* Below define time for (start/stop/slope time) */
+#define TIME0 "no time"
+#define TIME1 "0.09s"
+#define TIME2 "0.18s"
+#define TIME3 "0.36s"
+#define TIME4 "0.54s"
+#define TIME5 "0.80s"
+#define TIME6 "1.07s"
+#define TIME7 "1.52s"
+#define TIME8 "2.06s"
+#define TIME9 "2.50s"
+#define TIME10 "3.04s"
+#define TIME11 "4.02s"
+#define TIME12 "5.01s"
+#define TIME13 "5.99s"
+#define TIME14 "7.06s"
+#define TIME15 "8.05s"
+/* End define time for (start/stop/slope time) */
+
+#define AEU1 "AEU1"
+#define AEU2 "AEU2"
+#define AEU3 "AEU3"
+
+#define MAX_LEDS 16
+#define MAX_TIME 16
+#define MAX_AEU 3
+
+#define LP5812_DEV_ATTR_RW(name) \
+ DEVICE_ATTR_RW(name)
+#define LP5812_DEV_ATTR_RO(name) \
+ DEVICE_ATTR_RO(name)
+#define LP5812_DEV_ATTR_WO(name) \
+ DEVICE_ATTR_WO(name)
+
+#define LP5812_KOBJ_ATTR(_name, _mode, _show, _store) \
+ struct kobj_attribute kobj_attr_##_name = __ATTR(_name, _mode, _show, _store)
+#define LP5812_KOBJ_ATTR_RW(name, show, store) \
+ LP5812_KOBJ_ATTR(name, 0644, show, store)
+#define LP5812_KOBJ_ATTR_RO(name, show) \
+ LP5812_KOBJ_ATTR(name, 0444, show, NULL)
+#define LP5812_KOBJ_ATTR_WO(name, store) \
+ LP5812_KOBJ_ATTR(name, 0200, NULL, store)
+
+enum pwm_slope_time_num {
+ PWM1 = 1,
+ PWM2,
+ PWM3,
+ PWM4,
+ PWM5,
+ SLOPE_T1,
+ SLOPE_T2,
+ SLOPE_T3,
+ SLOPE_T4
+};
+
+enum dimming_type {
+ ANALOG,
+ PWM
+};
+
+enum pwm_dimming_scale {
+ LINEAR = 0,
+ EXPONENTIAL
+};
+
+enum control_mode {
+ MANUAL = 0,
+ AUTONOMOUS
+};
+
+enum device_command {
+ NONE,
+ UPDATE,
+ START,
+ STOP,
+ PAUSE,
+ CONTINUE
+};
+
+enum animation_addr {
+ AUTO_PAUSE = 0,
+ AUTO_PLAYBACK,
+ AEU1_PWM_1,
+ AEU1_PWM_2,
+ AEU1_PWM_3,
+ AEU1_PWM_4,
+ AEU1_PWM_5,
+ AEU1_T12,
+ AEU1_T34,
+ AEU1_PLAYBACK,
+ AEU2_PWM_1,
+ AEU2_PWM_2,
+ AEU2_PWM_3,
+ AEU2_PWM_4,
+ AEU2_PWM_5,
+ AEU2_T12,
+ AEU2_T34,
+ AEU2_PLAYBACK,
+ AEU3_PWM_1,
+ AEU3_PWM_2,
+ AEU3_PWM_3,
+ AEU3_PWM_4,
+ AEU3_PWM_5,
+ AEU3_T12,
+ AEU3_T34,
+ AEU3_PLAYBACK
+};
+
+enum drive_mode {
+ DIRECT_MODE = 0,
+ TCM_1_SCAN,
+ TCM_2_SCAN,
+ TCM_3_SCAN,
+ TCM_4_SCAN,
+ MIX_1_SCAN,
+ MIX_2_SCAN,
+ MIX_3_SCAN
+};
+
+enum aeu_select {
+ ONLY_AEU1,
+ AEU1_AEU2,
+ AEU1_AEU2_AEU3
+};
+
+union time {
+ struct {
+ u8 first:4;
+ u8 second:4;
+ } __packed s_time;
+ u8 time_val;
+}; /* type for start/stop pause time and slope time */
+
+union led_playback {
+ struct {
+ u8 led_playback_time:4;
+ u8 aeu_selection:2;
+ u8 reserved:2;
+ } __packed s_led_playback;
+ u8 led_playback_val;
+};
+
+union scan_order {
+ struct {
+ u8 scan_order_0:2;
+ u8 scan_order_1:2;
+ u8 scan_order_2:2;
+ u8 scan_order_3:2;
+ } __packed s_scan_order;
+ u8 scan_order_val;
+};
+
+union drive_mode_info {
+ 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;
+ } __packed s_drive_mode;
+ u8 drive_mode_val;
+};
+
+struct drive_mode_led_map {
+ const char *drive_mode;
+ const char **led_arr;
+};
+
+struct lp5812_specific_regs {
+ u16 enable_reg;
+ u16 reset_reg;
+ u16 update_cmd_reg;
+ u16 start_cmd_reg;
+ u16 stop_cmd_reg;
+ u16 pause_cmd_reg;
+ u16 continue_cmd_reg;
+ u16 fault_clear_reg;
+ u16 tsd_config_status_reg;
+};
+
+struct anim_engine_unit {
+ struct kobject kobj;
+ struct lp5812_led *led;
+ struct attribute_group attr_group;
+ const char *aeu_name;
+ int aeu_number; /* start from 1 */
+
+ /* To know led using this AEU or not*/
+ int enabled;
+};
+
+struct lp5812_led {
+ struct kobject kobj;
+ struct lp5812_chip *priv;
+ struct attribute_group attr_group;
+ int enable;
+ enum control_mode mode;
+ enum dimming_type dimming_type;
+ u8 lod_lsd;
+ u8 auto_pwm;
+ u8 aep_status;
+ u16 anim_base_addr;
+ int led_number; /* start from 0 */
+ int is_sysfs_created;
+ const char *led_name;
+
+ union led_playback led_playback;
+ union time start_stop_pause_time;
+
+ int total_aeu;
+ struct anim_engine_unit aeu[MAX_AEU];
+};
+
+struct lp5812_chip {
+ struct i2c_client *i2c_cl;
+ struct mutex lock; /* Protects access to device registers */
+ struct device *dev;
+ struct attribute_group attr_group;
+ const struct lp5812_specific_regs *regs;
+ const struct drive_mode_led_map *chip_leds_map;
+ enum device_command command;
+ int total_leds;
+ union scan_order u_scan_order;
+ union drive_mode_info u_drive_mode;
+
+ struct lp5812_led leds[MAX_LEDS]; /* MAX 16 LEDs */
+};
+
+#endif /*_LEDS_LP5812_H_*/
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 3/5] docs: ABI: Document LP5812 LED sysfs interfaces
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
2025-04-14 14:57 ` [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver Nam Tran
2025-04-14 14:57 ` [PATCH v5 2/5] " Nam Tran
@ 2025-04-14 14:57 ` Nam Tran
2025-04-14 14:57 ` [PATCH v5 4/5] docs: leds: Document TI LP5812 LED driver Nam Tran
` (2 subsequent siblings)
5 siblings, 0 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-14 14:57 UTC (permalink / raw)
To: pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel, Nam Tran
The LP5812 is a 4 × 3 matrix RGB LED driver
with autonomous animation engine control.
The driver provides interfaces to configure
LED modes manual/autonomous, set PWM/DC values,
and manage autonomous animation engines.
Signed-off-by: Nam Tran <trannamatk@gmail.com>
---
.../ABI/testing/sysfs-bus-i2c-devices-lp5812 | 144 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 145 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812
diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812 b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812
new file mode 100644
index 000000000000..04689663a643
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812
@@ -0,0 +1,144 @@
+What: /sys/bus/i2c/devices/.../lp5812_chip_setup/device_enable
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Enables or disables the LP5812 device. (RW)
+ 0 - Disable
+ 1 - Enable
+
+What: /sys/bus/i2c/devices/.../lp5812_chip_setup/dev_config
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Configures drive mode and scan order. (RW)
+ Some valid values: tcmscan:4:0:1:2:3 (default), tcmscan:3:0:1:2, mixscan:2:2:0:3, mixscan:3:0:1:2:3
+
+What: /sys/bus/i2c/devices/.../lp5812_chip_setup/device_command
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Issues device-level commands. (WO)
+ Valid values: "update", "start", "stop", "pause", "continue"
+
+What: /sys/bus/i2c/devices/.../lp5812_chip_setup/sw_reset
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Triggers a software reset of the device. (WO)
+ 1 - resets device
+ 0 - does not reset device
+
+What: /sys/bus/i2c/devices/.../lp5812_chip_setup/fault_clear
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Clears fault status. (WO)
+ 1 - clears fault status
+ 0 - does not clear fault status
+
+What: /sys/bus/i2c/devices/.../lp5812_chip_setup/tsd_config_status
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Report the current thermal shutdown config status. (RO)
+
+What: /sys/bus/i2c/devices/.../led_<id>/activate
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Activate or deactivate the specified LED channel. (RW)
+ 1 - Activate
+ 0 - Deactivate
+
+What: /sys/bus/i2c/devices/.../led_<id>/mode
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Selects LED operation mode. (RW)
+ Valid values: "manual", "autonomous"
+
+What: /sys/bus/i2c/devices/.../led_<id>/manual_dc
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ DC current level in manual mode. (RW)
+ Valid values: 0 - 255
+
+What: /sys/bus/i2c/devices/.../led_<id>/manual_pwm
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ PWM duty cycle in manual mode. (RW)
+ Valid values: 0 - 255
+
+What: /sys/bus/i2c/devices/.../led_<id>/autonomous_dc
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ DC current level used in autonomous mode. (RW)
+ Valid values: 0 - 255
+
+What: /sys/bus/i2c/devices/.../led_<id>/autonomous_dc
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ DC current level used in autonomous mode. (RW)
+ Valid values: 0 - 255
+
+What: /sys/bus/i2c/devices/.../led_<id>/pwm_dimming_scale
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ PWM dimming scale type. (RW)
+ Valid values: "linear", "exponential"
+
+What: /sys/bus/i2c/devices/.../led_<id>/pwm_phase_align
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Configures PWM phase alignment. (RW)
+ Valid values: "forward", "middle", "backward"
+
+What: /sys/bus/i2c/devices/.../led_<id>/autonomous_animation
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Controls AEU configuration and playback. (RW)
+ Format: (aeu number):(start pause time):(stop pause time):(playback time)
+ with aeu number 1, 2, 3; playback time 0 - 15
+
+What: /sys/bus/i2c/devices/.../led_<id>/aep_status
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Shows current animation pattern status, value from 0 to 7. (RO)
+
+What: /sys/bus/i2c/devices/.../led_<id>/auto_pwm_val
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ Shows the pwm value in autonomous mode when pause the animation, value from 0 to 255. (RO)
+
+What: /sys/bus/i2c/devices/.../led_<id>/lod_lsd
+Date: May 2025
+KernelVersion: 6.16
+Contact: Nam Tran <trannamatk@gmail.com>
+Description:
+ 0 0 mean no lod and lsd fault detected, 1 1 mean lod and lsd fault detected (RO)
diff --git a/MAINTAINERS b/MAINTAINERS
index c8a7058668dc..63ecb019e3bc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23938,6 +23938,7 @@ TEXAS INSTRUMENTS' LP5812 LED DRIVER
M: Nam Tran <trannamatk@gmail.com>
L: linux-leds@vger.kernel.org
S: Maintained
+F: Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812
F: Documentation/devicetree/bindings/leds/ti,lp5812.yaml
F: drivers/leds/Kconfig
F: drivers/leds/Makefile
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 4/5] docs: leds: Document TI LP5812 LED driver
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
` (2 preceding siblings ...)
2025-04-14 14:57 ` [PATCH v5 3/5] docs: ABI: Document LP5812 LED sysfs interfaces Nam Tran
@ 2025-04-14 14:57 ` Nam Tran
2025-04-14 14:57 ` [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B Nam Tran
2025-04-14 15:05 ` [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Krzysztof Kozlowski
5 siblings, 0 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-14 14:57 UTC (permalink / raw)
To: pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel, Nam Tran
The driver provides sysfs interfaces to control
and configure the LP5812 device and its LED channels.
The documetation describes the chip's capabilities,
sysfs interface, and usage examples.
Signed-off-by: Nam Tran <trannamatk@gmail.com>
---
Documentation/leds/leds-lp5812.rst | 79 ++++++++++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 80 insertions(+)
create mode 100644 Documentation/leds/leds-lp5812.rst
diff --git a/Documentation/leds/leds-lp5812.rst b/Documentation/leds/leds-lp5812.rst
new file mode 100644
index 000000000000..ef182f859f86
--- /dev/null
+++ b/Documentation/leds/leds-lp5812.rst
@@ -0,0 +1,79 @@
+========================
+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 maxtrix LED driver with support for both manual and
+autonomous animation control. It provides features such as:
+
+- PWM dimming and DC current control
+- Slope time configuration
+- Autonomous Engine Unit (AEU) for LED animation playback
+- Flexible scan and drive mode configuration
+
+This driver provides sysfs interfaces to control and configure the LP5812
+device and its LED channels.
+
+Sysfs Interface
+===============
+
+LP5812 device exposes a chip-level sysfs group:
+ /sys/bus/i2c/devices/<i2c-dev-addr>/lp5812_chip_setup/
+
+The following attributes are available at chip level:
+ - device_enable: Enable/disable the device (RW)
+ - dev_config: Configure drive mode and scan order (RW)
+ - device_command: Issue device-wide commands (WO)
+ - sw_reset: Reset the hardware (WO)
+ - fault_clear: Clear any device faults (WO)
+ - tsd_config_status: Read thermal shutdown config status (RO)
+
+Each LED channel is exposed as:
+ /sys/bus/i2c/devices/<i2c-dev-addr>/led_<id>/
+
+Each LED exposes the following attributes:
+ - activate: Activate or deactivate the LED (RW)
+ - mode: manual or autonomous mode (RW)
+ - manual_dc: DC current value (0–255) (RW)
+ - manual_pwm: PWM duty cycle (0–255) (RW)
+ - autonomous_dc: DC current in autonomous mode (RW)
+ - pwm_dimming_scale: linear or exponential (RW)
+ - pwm_phase_align: PWM alignment mode (RW)
+ - autonomous_animation: Config autonomous animation mode with aeu number, start pause time, stop pause time, playback time (RW)
+ - aep_status: autonomous engine pattern status (RO)
+ - auto_pwm_val: pwm value in autonomous mode when pause the animation (RO)
+ - lod_lsd: lod and lsd fault detected status (RO)
+
+Example Usage
+=============
+
+To control led_A0 in manual mode::
+ echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/lp5812_chip_setup/device_enable
+ echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/activate
+ echo manual > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/mode
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/manual_dc
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/manual_pwm
+
+To control led_A0 in autonomous mode::
+ echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/lp5812_chip_setup/device_enable
+ echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/activate
+ echo autonomous > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/mode
+ echo 1:10:10:15 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/autonomous_animation
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm1
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm2
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm3
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm4
+ echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm5
+ echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t1
+ echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t2
+ echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t3
+ echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t4
+ echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/playback_time
+ echo start > /sys/bus/i2c/drivers/lp5812/xxxx/lp5812_chip_setup/device_command
diff --git a/MAINTAINERS b/MAINTAINERS
index 63ecb019e3bc..4433cfe7491c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23940,6 +23940,7 @@ L: linux-leds@vger.kernel.org
S: Maintained
F: Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812
F: Documentation/devicetree/bindings/leds/ti,lp5812.yaml
+F: Documentation/leds/leds-lp5812.rst
F: drivers/leds/Kconfig
F: drivers/leds/Makefile
F: drivers/leds/leds-lp5812.c
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
` (3 preceding siblings ...)
2025-04-14 14:57 ` [PATCH v5 4/5] docs: leds: Document TI LP5812 LED driver Nam Tran
@ 2025-04-14 14:57 ` Nam Tran
2025-04-14 15:10 ` Krzysztof Kozlowski
2025-04-14 15:05 ` [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Krzysztof Kozlowski
5 siblings, 1 reply; 16+ messages in thread
From: Nam Tran @ 2025-04-14 14:57 UTC (permalink / raw)
To: pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel, Nam Tran
Add the LP5812 LED driver node to the Device Tree for Raspberry Pi 4 B.
This enables the LED connected to the LP5812 to be controlled via I2C.
Signed-off-by: Nam Tran <trannamatk@gmail.com>
---
.../arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 60 +++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
index 353bb50ce542..0dec6ce44c6c 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
@@ -152,6 +152,66 @@ &hdmi1 {
status = "okay";
};
+&i2c1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@1b {
+ compatible = "ti,lp5812";
+ reg = <0x1b>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ led@0 {
+ reg = <0x0>;
+ chan-name = "a0";
+ };
+ led@1 {
+ reg = <0x1>;
+ chan-name = "a1";
+ };
+ led@2 {
+ reg = <0x2>;
+ chan-name = "a2";
+ };
+ led@3 {
+ reg = <0x3>;
+ chan-name = "b0";
+ };
+ led@4 {
+ reg = <0x4>;
+ chan-name = "b1";
+ };
+ led@5 {
+ reg = <0x5>;
+ chan-name = "b2";
+ };
+ led@6 {
+ reg = <0x6>;
+ chan-name = "c0";
+ };
+ led@7 {
+ reg = <0x7>;
+ chan-name = "c1";
+ };
+ led@8 {
+ reg = <0x8>;
+ chan-name = "c2";
+ };
+ led@9 {
+ reg = <0x9>;
+ chan-name = "d0";
+ };
+ led@a {
+ reg = <0xa>;
+ chan-name = "d1";
+ };
+ led@b {
+ reg = <0xb>;
+ chan-name = "d2";
+ };
+ };
+};
+
&led_act {
gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
};
--
2.25.1
^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH v5 0/5] leds: add new LED driver for TI LP5812
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
` (4 preceding siblings ...)
2025-04-14 14:57 ` [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B Nam Tran
@ 2025-04-14 15:05 ` Krzysztof Kozlowski
2025-04-18 4:43 ` Nam Tran
5 siblings, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-14 15:05 UTC (permalink / raw)
To: Nam Tran, pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel
On 14/04/2025 16:57, Nam Tran wrote:
> This patch series adds support for the TI/National Semiconductor LP5812
> 4x3 matrix RGB LED driver. The driver supports features such as autonomous
> animation and time-cross-multiplexing (TCM) for dynamic LED effects.
>
> Signed-off-by: Nam Tran <trannamatk@gmail.com>
> ---
> Changes in v5:
> - Rebase on v6.15-rc2
> - Removed unused functions (lp5812_dump_regs, lp5812_update_bit).
> - Address Krzysztof's review comments
Which ones? What changed?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-14 14:57 ` [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver Nam Tran
@ 2025-04-14 15:10 ` Krzysztof Kozlowski
2025-04-15 9:53 ` Nam Tran
0 siblings, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-14 15:10 UTC (permalink / raw)
To: Nam Tran, pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel
On 14/04/2025 16:57, Nam Tran wrote:
> +
> +description: |
> + The LP5812 is an I2C LED Driver that can support LED matrix 4x3.
> + 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
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
No need for supply?
> +
> +patternProperties:
> + "^led@[0-9a-b]$":
> + type: object
> + $ref: common.yaml#
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + minimum: 0
> + maximum: 0xb
> +
> + chan-name:
> + $ref: /schemas/types.yaml#/definitions/string
> + description: LED channel name
My comment stay valid. I don't think LEDs have channels, datasheet also
has nothing about channels, so again - use existing properties. Or
better drop it - I don't see any point in the name. The reg already
defines it.
However after dropping this, your example has nodes with only reg -
what's the point of them? Why no properties from common.yaml are
applicable? If they are not applicable, then the entire subnode should
be dropped - you don't need them to describe the hardware.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B
2025-04-14 14:57 ` [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B Nam Tran
@ 2025-04-14 15:10 ` Krzysztof Kozlowski
2025-04-15 9:56 ` Nam Tran
0 siblings, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-14 15:10 UTC (permalink / raw)
To: Nam Tran, pavel, lee, krzk+dt, robh, conor+dt, corbet
Cc: devicetree, linux-leds, linux-kernel
On 14/04/2025 16:57, Nam Tran wrote:
> Add the LP5812 LED driver node to the Device Tree for Raspberry Pi 4 B.
> This enables the LED connected to the LP5812 to be controlled via I2C.
>
> Signed-off-by: Nam Tran <trannamatk@gmail.com>
> ---
> .../arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 60 +++++++++++++++++++
> 1 file changed, 60 insertions(+)
>
> diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
> index 353bb50ce542..0dec6ce44c6c 100644
> --- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
> +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
> @@ -152,6 +152,66 @@ &hdmi1 {
> status = "okay";
> };
>
> +&i2c1 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + led-controller@1b {
> + compatible = "ti,lp5812";
> + reg = <0x1b>;
Messed indentation... You already got such comment about your DTS in the
binding. :/
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-14 15:10 ` Krzysztof Kozlowski
@ 2025-04-15 9:53 ` Nam Tran
2025-04-15 9:59 ` Krzysztof Kozlowski
0 siblings, 1 reply; 16+ messages in thread
From: Nam Tran @ 2025-04-15 9:53 UTC (permalink / raw)
To: krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On Mon, 14 Apr 2025, Krzysztof Kozlowski wrote:
> On 14/04/2025 16:57, Nam Tran wrote:
> > +
> > +description: |
> > + The LP5812 is an I2C LED Driver that can support LED matrix 4x3.
> > + 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
> > +
> > + "#address-cells":
> > + const: 1
> > +
> > + "#size-cells":
> > + const: 0
>
> No need for supply?
Since the hardware uses an external power supply,
we decide not to include the supply property in the binding.
> > +
> > +patternProperties:
> > + "^led@[0-9a-b]$":
> > + type: object
> > + $ref: common.yaml#
> > + unevaluatedProperties: false
> > +
> > + properties:
> > + reg:
> > + minimum: 0
> > + maximum: 0xb
> > +
> > + chan-name:
> > + $ref: /schemas/types.yaml#/definitions/string
> > + description: LED channel name
>
> My comment stay valid. I don't think LEDs have channels, datasheet also
> has nothing about channels, so again - use existing properties. Or
> better drop it - I don't see any point in the name. The reg already
> defines it.
The channel was named for the output channel to each LED, not the LED channels.
They are not required properties because we can control entirely the LEDs of LP5812 through the indexes (regs property),
but the person who wants to develop LP5812's matrix-related features can use the "channels" for easy mapping.
>
> However after dropping this, your example has nodes with only reg -
> what's the point of them? Why no properties from common.yaml are
> applicable? If they are not applicable, then the entire subnode should
> be dropped - you don't need them to describe the hardware.
Actually, the "color" property can be applied, but the LP5812 is a matrix LED,
so specifying a particular LED color is not necessary when developing LP5812 features.
Best regards,
Nam Tran
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B
2025-04-14 15:10 ` Krzysztof Kozlowski
@ 2025-04-15 9:56 ` Nam Tran
0 siblings, 0 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-15 9:56 UTC (permalink / raw)
To: krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On Mon, 14 Apr 2025, Krzysztof Kozlowski wrote:
> On 14/04/2025 16:57, Nam Tran wrote:
> > Add the LP5812 LED driver node to the Device Tree for Raspberry Pi 4 B.
> > This enables the LED connected to the LP5812 to be controlled via I2C.
> >
> > Signed-off-by: Nam Tran <trannamatk@gmail.com>
> > ---
> > .../arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 60 +++++++++++++++++++
> > 1 file changed, 60 insertions(+)
> >
> > diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
> > index 353bb50ce542..0dec6ce44c6c 100644
> > --- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
> > +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
> > @@ -152,6 +152,66 @@ &hdmi1 {
> > status = "okay";
> > };
> >
> > +&i2c1 {
> > + #address-cells = <1>;
> > + #size-cells = <0>;
> > +
> > + led-controller@1b {
> > + compatible = "ti,lp5812";
> > + reg = <0x1b>;
>
> Messed indentation... You already got such comment about your DTS in the
> binding. :/
Thank you for pointing it out. I fixed the indentation in the binding file but overlooked it here.
I'll make sure it's corrected in the next version.
Best regards,
Nam Tran
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-15 9:53 ` Nam Tran
@ 2025-04-15 9:59 ` Krzysztof Kozlowski
2025-04-17 2:06 ` Nam Tran
0 siblings, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-15 9:59 UTC (permalink / raw)
To: Nam Tran, krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On 15/04/2025 11:53, Nam Tran wrote:
> On Mon, 14 Apr 2025, Krzysztof Kozlowski wrote:
>
>> On 14/04/2025 16:57, Nam Tran wrote:
>>> +
>>> +description: |
>>> + The LP5812 is an I2C LED Driver that can support LED matrix 4x3.
>>> + 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
>>> +
>>> + "#address-cells":
>>> + const: 1
>>> +
>>> + "#size-cells":
>>> + const: 0
>>
>> No need for supply?
>
> Since the hardware uses an external power supply,
> we decide not to include the supply property in the binding.
So there is power supply? If so, must be in the binding. Bindings
describe given hardware (LP5812), not your particular board/setup.
>
>>> +
>>> +patternProperties:
>>> + "^led@[0-9a-b]$":
>>> + type: object
>>> + $ref: common.yaml#
>>> + unevaluatedProperties: false
>>> +
>>> + properties:
>>> + reg:
>>> + minimum: 0
>>> + maximum: 0xb
>>> +
>>> + chan-name:
>>> + $ref: /schemas/types.yaml#/definitions/string
>>> + description: LED channel name
>>
>> My comment stay valid. I don't think LEDs have channels, datasheet also
>> has nothing about channels, so again - use existing properties. Or
>> better drop it - I don't see any point in the name. The reg already
>> defines it.
>
> The channel was named for the output channel to each LED, not the LED channels.
I don't understand what you want to say. Please explain why existing
label property is not correct here.
> They are not required properties because we can control entirely the LEDs of LP5812 through the indexes (regs property),
I did not ask about this.
> but the person who wants to develop LP5812's matrix-related features can use the "channels" for easy mapping.
easy mapping of what? Please show me the usage.
>
>>
>> However after dropping this, your example has nodes with only reg -
>> what's the point of them? Why no properties from common.yaml are
>> applicable? If they are not applicable, then the entire subnode should
>> be dropped - you don't need them to describe the hardware.
>
> Actually, the "color" property can be applied, but the LP5812 is a matrix LED,
> so specifying a particular LED color is not necessary when developing LP5812 features.
This does not help me much and based on this I see no points in
describing individual LEDs, because the only missing information is
number of them but even that is fixed for given device, isn't it?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-15 9:59 ` Krzysztof Kozlowski
@ 2025-04-17 2:06 ` Nam Tran
2025-04-17 5:43 ` Krzysztof Kozlowski
0 siblings, 1 reply; 16+ messages in thread
From: Nam Tran @ 2025-04-17 2:06 UTC (permalink / raw)
To: krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On Tue, 15 Apr 2025, Krzysztof Kozlowski wrote:
>On 15/04/2025 11:53, Nam Tran wrote:
>> On Mon, 14 Apr 2025, Krzysztof Kozlowski wrote:
>>
>>> On 14/04/2025 16:57, Nam Tran wrote:
>>>> +
>>>> +description: |
>>>> + The LP5812 is an I2C LED Driver that can support LED matrix 4x3.
>>>> + 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
>>>> +
>>>> + "#address-cells":
>>>> + const: 1
>>>> +
>>>> + "#size-cells":
>>>> + const: 0
>>>
>>> No need for supply?
>>
>> Since the hardware uses an external power supply,
>> we decide not to include the supply property in the binding.
>
>So there is power supply? If so, must be in the binding. Bindings
>describe given hardware (LP5812), not your particular board/setup.
Thank you for the clarification.
The LP5812 is externally powered and has a dedicated VCC pin.
I'll update the binding to include a `vcc-supply` property.
>>
>>>> +
>>>> +patternProperties:
>>>> + "^led@[0-9a-b]$":
>>>> + type: object
>>>> + $ref: common.yaml#
>>>> + unevaluatedProperties: false
>>>> +
>>>> + properties:
>>>> + reg:
>>>> + minimum: 0
>>>> + maximum: 0xb
>>>> +
>>>> + chan-name:
>>>> + $ref: /schemas/types.yaml#/definitions/string
>>>> + description: LED channel name
>>>
>>> My comment stay valid. I don't think LEDs have channels, datasheet also
>>> has nothing about channels, so again - use existing properties. Or
>>> better drop it - I don't see any point in the name. The reg already
>>> defines it.
>>
>> The channel was named for the output channel to each LED, not the LED channels.
>
>I don't understand what you want to say. Please explain why existing
>label property is not correct here.
I understand that the label property is deprecated and that the preferred approach now is to use function and color instead.
However, in the case of the LP5812, which is a matrix LED driver, these properties are not a good fit.
The LP5812 does not associate each output with a specific function (like "status", "activity"),
and the LEDs driven by LP5812 are not fixed to a particular color.
>> but the person who wants to develop LP5812's matrix-related features can use the "channels" for easy mapping.
>
>easy mapping of what? Please show me the usage.
You're right — I cannot provide a meaningful usage example for chan-name.
The chan-name property was intended to give a more descriptive name for each LED channel, mainly for convenience in user space.
But since this isn’t standard and you advised against introducing such a property, we’ve decided to drop it.
>>
>>>
>>> However after dropping this, your example has nodes with only reg -
>>> what's the point of them? Why no properties from common.yaml are
>>> applicable? If they are not applicable, then the entire subnode should
>>> be dropped - you don't need them to describe the hardware.
>>
>> Actually, the "color" property can be applied, but the LP5812 is a matrix LED,
>> so specifying a particular LED color is not necessary when developing LP5812 features.
>
>This does not help me much and based on this I see no points in
>describing individual LEDs, because the only missing information is
>number of them but even that is fixed for given device, isn't it?
Actually, the number of LED outputs on the LP5812 is not strictly fixed — it depends on the selected operating mode.
This mode is configurable by the end user at runtime through sysfs interfaces provided by the driver.
I understand your point — if no additional properties from common.yaml are applicable, these subnodes may not be necessary.
Therefore, we’ve decided to drop them.
Best regards,
Nam Tran
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-17 2:06 ` Nam Tran
@ 2025-04-17 5:43 ` Krzysztof Kozlowski
2025-04-17 9:37 ` Nam Tran
0 siblings, 1 reply; 16+ messages in thread
From: Krzysztof Kozlowski @ 2025-04-17 5:43 UTC (permalink / raw)
To: Nam Tran, krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On 17/04/2025 04:06, Nam Tran wrote:
>
>>>
>>>>> +
>>>>> +patternProperties:
>>>>> + "^led@[0-9a-b]$":
>>>>> + type: object
>>>>> + $ref: common.yaml#
>>>>> + unevaluatedProperties: false
>>>>> +
>>>>> + properties:
>>>>> + reg:
>>>>> + minimum: 0
>>>>> + maximum: 0xb
>>>>> +
>>>>> + chan-name:
>>>>> + $ref: /schemas/types.yaml#/definitions/string
>>>>> + description: LED channel name
>>>>
>>>> My comment stay valid. I don't think LEDs have channels, datasheet also
>>>> has nothing about channels, so again - use existing properties. Or
>>>> better drop it - I don't see any point in the name. The reg already
>>>> defines it.
>>>
>>> The channel was named for the output channel to each LED, not the LED channels.
>>
>> I don't understand what you want to say. Please explain why existing
>> label property is not correct here.
>
> I understand that the label property is deprecated and that the preferred approach now is to use function and color instead.
> However, in the case of the LP5812, which is a matrix LED driver, these properties are not a good fit.
> The LP5812 does not associate each output with a specific function (like "status", "activity"),
> and the LEDs driven by LP5812 are not fixed to a particular color.
Then use label instead of creating another property. If label is
deprecated, how creating another property which duplicates the label
solves anything?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver
2025-04-17 5:43 ` Krzysztof Kozlowski
@ 2025-04-17 9:37 ` Nam Tran
0 siblings, 0 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-17 9:37 UTC (permalink / raw)
To: krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On Thu, 17 Apr 2025, Krzysztof Kozlowski wrote:
>On 17/04/2025 04:06, Nam Tran wrote:
>>
>>>>
>>>>>> +
>>>>>> +patternProperties:
>>>>>> + "^led@[0-9a-b]$":
>>>>>> + type: object
>>>>>> + $ref: common.yaml#
>>>>>> + unevaluatedProperties: false
>>>>>> +
>>>>>> + properties:
>>>>>> + reg:
>>>>>> + minimum: 0
>>>>>> + maximum: 0xb
>>>>>> +
>>>>>> + chan-name:
>>>>>> + $ref: /schemas/types.yaml#/definitions/string
>>>>>> + description: LED channel name
>>>>>
>>>>> My comment stay valid. I don't think LEDs have channels, datasheet also
>>>>> has nothing about channels, so again - use existing properties. Or
>>>>> better drop it - I don't see any point in the name. The reg already
>>>>> defines it.
>>>>
>>>> The channel was named for the output channel to each LED, not the LED channels.
>>>
>>> I don't understand what you want to say. Please explain why existing
>>> label property is not correct here.
>>
>> I understand that the label property is deprecated and that the preferred approach now is to use function and color instead.
>> However, in the case of the LP5812, which is a matrix LED driver, these properties are not a good fit.
>> The LP5812 does not associate each output with a specific function (like "status", "activity"),
>> and the LEDs driven by LP5812 are not fixed to a particular color.
>
>Then use label instead of creating another property. If label is
>deprecated, how creating another property which duplicates the label
>solves anything?
You're right — creating a new property like chan-name does not help
if it duplicates the purpose of the deprecated label property.
I initially created the chan-name property after referring to the existing leds-lp55xx.yaml binding.
However, based on your recommendation, I’ve decided to drop chan-name and the entire subnode as well.
Additionally, I will add the vcc-supply property as mentioned in the last email.
Would this be sufficient for me to submit a new patch?
Best regards,
Nam Tran
^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH v5 0/5] leds: add new LED driver for TI LP5812
2025-04-14 15:05 ` [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Krzysztof Kozlowski
@ 2025-04-18 4:43 ` Nam Tran
0 siblings, 0 replies; 16+ messages in thread
From: Nam Tran @ 2025-04-18 4:43 UTC (permalink / raw)
To: krzk+dt
Cc: pavel, lee, robh, conor+dt, corbet, devicetree, linux-leds,
linux-kernel
On Mon, 14 Apr 2025, Krzysztof Kozlowski wrote:
>On 14/04/2025 16:57, Nam Tran wrote:
>> This patch series adds support for the TI/National Semiconductor LP5812
>> 4x3 matrix RGB LED driver. The driver supports features such as autonomous
>> animation and time-cross-multiplexing (TCM) for dynamic LED effects.
>>
>> Signed-off-by: Nam Tran <trannamatk@gmail.com>
>> ---
>> Changes in v5:
>> - Rebase on v6.15-rc2
>> - Removed unused functions (lp5812_dump_regs, lp5812_update_bit).
>> - Address Krzysztof's review comments
>Which ones? What changed?
Apologies for the delayed reply.
I’ve updated the ABI document based on your previous comments.
Regarding the binding, I’ve removed redundant descriptions and fixed the indentation as you pointed out.
I’ll make sure to describe the changes more clearly in the next patch submission.
Best regards,
Nam Tran
^ permalink raw reply [flat|nested] 16+ messages in thread
end of thread, other threads:[~2025-04-18 4:44 UTC | newest]
Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-04-14 14:57 [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Nam Tran
2025-04-14 14:57 ` [PATCH v5 1/5] dt-bindings: leds: add TI/National Semiconductor LP5812 LED Driver Nam Tran
2025-04-14 15:10 ` Krzysztof Kozlowski
2025-04-15 9:53 ` Nam Tran
2025-04-15 9:59 ` Krzysztof Kozlowski
2025-04-17 2:06 ` Nam Tran
2025-04-17 5:43 ` Krzysztof Kozlowski
2025-04-17 9:37 ` Nam Tran
2025-04-14 14:57 ` [PATCH v5 2/5] " Nam Tran
2025-04-14 14:57 ` [PATCH v5 3/5] docs: ABI: Document LP5812 LED sysfs interfaces Nam Tran
2025-04-14 14:57 ` [PATCH v5 4/5] docs: leds: Document TI LP5812 LED driver Nam Tran
2025-04-14 14:57 ` [PATCH v5 5/5] arm64: dts: Add LP5812 LED node for Raspberry Pi 4 Model B Nam Tran
2025-04-14 15:10 ` Krzysztof Kozlowski
2025-04-15 9:56 ` Nam Tran
2025-04-14 15:05 ` [PATCH v5 0/5] leds: add new LED driver for TI LP5812 Krzysztof Kozlowski
2025-04-18 4:43 ` Nam Tran
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).