Hello!
Here is the multicolor framework reworked with numerous changes.
Most importantly is the ABI to produce a color.
brightness file controls the cluster based on the color intensity set.
The color intensity can be set either through the colors/color_mix file,
which requires all LED values within the cluster to be written or the LED's
intensity can be controlled individually via the colors/<color>/intensity file.
This code has been tested using the LP5523 EVM and the LP50xx evms. Triggers
work in this model. I have performed tests to confirm that the color requested
stays consistent when ramping from LED_OFF to LED_FULL and back down to LED_OFF.
I anticipate another round of patches but I would like to get comments on all
the code.
Dan
Dan Murphy (9):
leds: multicolor: Add sysfs interface definition
documention: leds: Add multicolor class documentation
dt: bindings: Add multicolor class dt bindings documention
dt-bindings: leds: Add multicolor ID to the color ID list
leds: Add multicolor ID to the color ID list
leds: multicolor: Introduce a multicolor class definition
dt: bindings: lp50xx: Introduce the lp50xx family of RGB drivers
leds: lp50xx: Add the LP50XX family of the RGB LED driver
leds: Update the lp55xx to use the multi color framework
.../ABI/testing/sysfs-class-led-multicolor | 67 ++
.../bindings/leds/leds-class-multicolor.txt | 96 +++
.../devicetree/bindings/leds/leds-lp50xx.txt | 148 ++++
Documentation/leds/leds-class-multicolor.txt | 152 ++++
drivers/leds/Kconfig | 17 +
drivers/leds/Makefile | 2 +
drivers/leds/led-class-multicolor.c | 402 +++++++++
drivers/leds/led-core.c | 1 +
drivers/leds/leds-lp50xx.c | 789 ++++++++++++++++++
drivers/leds/leds-lp5523.c | 13 +
drivers/leds/leds-lp55xx-common.c | 153 +++-
drivers/leds/leds-lp55xx-common.h | 10 +
include/dt-bindings/leds/common.h | 3 +-
include/linux/led-class-multicolor.h | 96 +++
include/linux/platform_data/leds-lp55xx.h | 6 +
modules.builtin.modinfo | Bin 0 -> 43550 bytes
16 files changed, 1931 insertions(+), 24 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-led-multicolor
create mode 100644 Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
create mode 100644 Documentation/devicetree/bindings/leds/leds-lp50xx.txt
create mode 100644 Documentation/leds/leds-class-multicolor.txt
create mode 100644 drivers/leds/led-class-multicolor.c
create mode 100644 drivers/leds/leds-lp50xx.c
create mode 100644 include/linux/led-class-multicolor.h
create mode 100644 modules.builtin.modinfo
--
2.22.0.214.g8dca754b1e
Add a new color ID that is declared as MULTICOLOR as with the
multicolor framework declaring a definitive color is not accurate
as the node can contain multiple colors.
Signed-off-by: Dan Murphy <[email protected]>
---
drivers/leds/led-core.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index f0c1c403f678..52422b1ee1f3 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -34,6 +34,7 @@ const char * const led_colors[LED_COLOR_ID_MAX] = {
[LED_COLOR_ID_VIOLET] = "violet",
[LED_COLOR_ID_YELLOW] = "yellow",
[LED_COLOR_ID_IR] = "ir",
+ [LED_COLOR_ID_MULTI] = "multicolor",
};
EXPORT_SYMBOL_GPL(led_colors);
--
2.22.0.214.g8dca754b1e
Add a documentation of LED Multicolor LED class specific
sysfs attributes.
Signed-off-by: Dan Murphy <[email protected]>
---
.../ABI/testing/sysfs-class-led-multicolor | 67 +++++++++++++++++++
1 file changed, 67 insertions(+)
create mode 100644 Documentation/ABI/testing/sysfs-class-led-multicolor
diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor b/Documentation/ABI/testing/sysfs-class-led-multicolor
new file mode 100644
index 000000000000..59839f0eae76
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-multicolor
@@ -0,0 +1,67 @@
+What: /sys/class/leds/<led>/brightness
+Date: Sept 2019
+KernelVersion: TBD
+Contact: Dan Murphy <[email protected]>
+Description: read/write
+ The multicolor class will redirect the device drivers call back
+ function for brightness control to the multicolor class
+ brightness control function.
+
+ Writing to this file will update all LEDs within the group to a
+ calculated percentage of what each color LED in the group is set
+ to. Please refer to the leds-class-multicolor.txt in the
+ Documentation directory for a complete description.
+
+ The value of the color is from 0 to
+ /sys/class/leds/<led>/max_brightness.
+
+What: /sys/class/leds/<led>/colors/color_mix
+Date: Sept 2019
+KernelVersion: TBD
+Contact: Dan Murphy <[email protected]>
+Description: read/write
+ The color_mix file allows writing all registered multicolor LEDs
+ virtually at the same time. The value(s) written to this file
+ contain the intensity values for each multicolor LED within
+ the colors directory. The color indexes are reported in the
+ color_id file as defined in this document.
+ Please refer to the leds-class-multicolor.txt in the
+ Documentation directory for a complete description.
+
+What: /sys/class/leds/<led>/colors/<led_color>/color_id
+Date: Sept 2019
+KernelVersion: TBD
+Contact: Dan Murphy <[email protected]>
+Description: read only
+ This file when read will return the index of the color in the
+ color_mix.
+ Please refer to the leds-class-multicolor.txt in the
+ Documentation directory for a complete description.
+
+What: /sys/class/leds/<led>/colors/<led_color>/intensity
+Date: Sept 2019
+KernelVersion: TBD
+Contact: Dan Murphy <[email protected]>
+Description: read/write
+ The led_color directory is dynamically created based on the
+ colors defined by the registrar of the class.
+ The led_color can be but not limited to red, green, blue,
+ white, amber, yellow and violet. Drivers can also declare a
+ LED color for presentation. There is one directory per color
+ presented. The brightness file is created under each
+ led_color directory and controls the individual LED color
+ setting.
+
+ The value of the color is from 0 to
+ /sys/class/leds/<led>/colors/<led_color>/max_intensity.
+
+What: /sys/class/leds/<led>/colors/<led_color>/max_intensity
+Date: Sept 2019
+KernelVersion: TBD
+Contact: Dan Murphy <[email protected]>
+Description: read only
+ Maximum intensity level for the LED color, default is
+ 255 (LED_FULL).
+
+ If the LED does not support different intensity levels, this
+ should be 1.
--
2.22.0.214.g8dca754b1e
Add the support documentation on the multicolor LED framework.
This document defines the directores and file generated by the
multicolor framework. It also documents usage.
Signed-off-by: Dan Murphy <[email protected]>
---
Documentation/leds/leds-class-multicolor.txt | 152 +++++++++++++++++++
1 file changed, 152 insertions(+)
create mode 100644 Documentation/leds/leds-class-multicolor.txt
diff --git a/Documentation/leds/leds-class-multicolor.txt b/Documentation/leds/leds-class-multicolor.txt
new file mode 100644
index 000000000000..3b354e523660
--- /dev/null
+++ b/Documentation/leds/leds-class-multicolor.txt
@@ -0,0 +1,152 @@
+
+Multi Color LED handling under Linux
+=================================================
+
+Author: Dan Murphy <[email protected]>
+
+Description
+-----------
+There are varying monochrome LED colors available for application. These
+LEDs can be used as a single use case LED or can be mixed with other color
+LEDs to produce the full spectrum of color. Color LEDs that are grouped
+can be presented under a single LED node with individual color control.
+The multicolor class groups these LEDs and allows dynamically setting the value
+of a single LED or setting the intensity values of the LEDs in the group and
+updating the LEDs virtually simultaneously.
+
+Multicolor Class Control
+-------------------------
+The multicolor class presents the LED groups under a directory called "colors".
+This directory is a child under the LED parent node created by the led_class
+framework. The led_class framework is documented in led-class.txt within this
+documentation directory.
+
+Each colored LED is given its own directory. These directories can be, but not
+limited to red, green, blue, white, amber, yellow and violet. Under these
+directories the intensity and max_intensity files are presented for each LED.
+
+
+Directory Layout Example
+------------------------
+root:/sys/class/leds/rgb:grouped_leds# ls -lR colors/
+colors/:
+drwxr-xr-x 2 root root 0 Jun 28 20:21 blue
+drwxr-xr-x 2 root root 0 Jun 28 20:21 green
+drwxr-xr-x 2 root root 0 Jun 28 20:21 red
+-rw------- 1 root root 4096 Jun 28 20:21 color_mix
+
+colors/blue:
+-rw------- 1 root root 4096 Jun 28 20:21 intensity
+-r-------- 1 root root 4096 Jun 28 20:27 max_intensity
+-r-------- 1 root root 4096 Jun 28 20:21 color_id
+
+colors/green:
+-rw------- 1 root root 4096 Jun 28 20:22 intensity
+-r-------- 1 root root 4096 Jun 28 20:27 max_intensity
+-r-------- 1 root root 4096 Jun 28 20:21 color_id
+
+colors/red:
+-rw------- 1 root root 4096 Jun 28 20:21 intensity
+-r-------- 1 root root 4096 Jun 28 20:27 max_intensity
+-r-------- 1 root root 4096 Jun 28 20:21 color_id
+
+Multicolor Color Mixing
+-----------------------
+Multicolor monochrome LEDs intensity can be modified and mixed to produce a
+varying array of colors. The color_mix file gives the user the ability to write
+all the monochrome LEDs registered in the directory with a single call.
+To create a specific color from monochrome LEDs the color_mix file needs to be
+written with each color's intensity. The order in which the monochrome LEDs
+should be written is based on the colors color_id.
+
+For example:
+cat /sys/class/leds/rgb:grouped_leds/red/color_id
+0
+cat /sys/class/leds/rgb:grouped_leds/green/color_id
+1
+cat /sys/class/leds/rgb:grouped_leds/blue/color_id
+2
+
+red - color_id = 0
+green - color_id = 1
+blue - color_id = 2
+
+These id's are the order in which to write the color_mix file.
+
+echo "<red> <green> <blue>" > color_mix
+
+echo "80 00 80" > color_mix
+
+The order of the monochrome LEDs are determined during multicolor class
+registration and will not change unless unregistered and re-registered.
+
+Other example with amber monochrome LED:
+blue - color_id = 0
+amber - color_id = 1
+
+In this exampe blue is at ID 0 and amber ID is 1 so the user would write
+echo "<blue> <amber>" > color_mix
+
+echo "38 80" > color_mix
+
+If a single monochrome LED needs to be modified then the user would write the
+colors/<color>/intensity file.
+
+
+Multicolor Class Brightness Control
+-----------------------------------
+The multiclor class framework will calculate each monochrome LEDs intensity.
+
+The brightness level for each LED is calculated based on the color LED
+intensity setting divided by the color LED max intensity setting multiplied by
+the requested value.
+
+led_brightness = requested_value * led_color_intensity/led_color_max_intensity
+
+Example:
+Three LEDs are present in the group as defined in "Directory Layout Example"
+within this document.
+
+A user first writes the color LED brightness file with the brightness level that
+is necessary to achieve a blueish violet output from the RGB LED group.
+
+echo 138 > /sys/class/leds/rgb:grouped_leds/red/intensity
+echo 43 > /sys/class/leds/rgb:grouped_leds/green/intensity
+echo 226 > /sys/class/leds/rgb:grouped_leds/blue/intensity
+
+red -
+ intensity = 138
+ max_intensity = 255
+green -
+ intensity = 43
+ max_intensity = 255
+blue -
+ intensity = 226
+ max_intensity = 255
+
+The user can control the brightness of that RGB group by writing the parent
+'brightness' control. Assuming a parent max_brightness of 255 the user may want
+to dim the LED color group to half. The user would write a value of 128 to the
+parent brightness file then the values written to each LED will be adjusted
+base on this value
+
+cat /sys/class/leds/rgb:grouped_leds/max_brightness
+255
+echo 128 > /sys/class/leds/rgb:grouped_leds/brightness
+
+adjusted_red_value = 128 * 138/255 = 69
+adjusted_green_value = 128 * 43/255 = 21
+adjusted_blue_value = 128 * 226/255 = 113
+
+Reading the parent brightness file will return the current brightness value of
+the color LED group.
+
+cat /sys/class/leds/rgb:grouped_leds/max_brightness
+255
+
+echo 128 > /sys/class/leds/rgb:grouped_leds/brightness
+
+cat /sys/class/leds/rgb:grouped_leds/max_brightness
+128
+
+
--
2.22.0.214.g8dca754b1e
Introduce the LP5036/30/24/18 RGB LED driver.
The difference in these parts are the number of
LED outputs where the:
LP5036 can control 36 LEDs
LP5030 can control 30 LEDs
LP5024 can control 24 LEDs
LP5018 can control 18 LEDs
The device has the ability to group LED output into control banks
so that multiple LED banks can be controlled with the same mixing and
brightness. Inversely the LEDs can also be controlled independently.
Signed-off-by: Dan Murphy <[email protected]>
---
drivers/leds/Kconfig | 7 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-lp50xx.c | 789 +++++++++++++++++++++++++++++++++++++
3 files changed, 797 insertions(+)
create mode 100644 drivers/leds/leds-lp50xx.c
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 7f780d5b8490..69c037020f6b 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -350,6 +350,13 @@ config LEDS_LP3952
To compile this driver as a module, choose M here: the
module will be called leds-lp3952.
+config LEDS_LP50XX
+ tristate "LED Support for TI LP5036/30/24/18 LED driver chip"
+ depends on LEDS_CLASS && REGMAP_I2C
+ help
+ If you say yes here you get support for the Texas Instruments
+ LP5036, LP5030, LP5024 and LP5018 LED driver.
+
config LEDS_LP55XX_COMMON
tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 897b810257dd..438a5499f3ee 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
obj-$(CONFIG_LEDS_LP3952) += leds-lp3952.o
+obj-$(CONFIG_LEDS_LP50XX) += leds-lp50xx.o
obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
new file mode 100644
index 000000000000..5a32410e4f34
--- /dev/null
+++ b/drivers/leds/leds-lp50xx.c
@@ -0,0 +1,789 @@
+// SPDX-License-Identifier: GPL-2.0
+// TI LP50XX LED chip family driver
+// Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
+
+#include <linux/led-class-multicolor.h>
+
+#define LP50XX_DEV_CFG0 0x00
+#define LP50XX_DEV_CFG1 0x01
+#define LP50XX_LED_CFG0 0x02
+
+/* LP5009 and LP5012 registers */
+#define LP5012_BNK_BRT 0x03
+#define LP5012_BNKA_CLR 0x04
+#define LP5012_BNKB_CLR 0x05
+#define LP5012_BNKC_CLR 0x06
+#define LP5012_LED0_BRT 0x07
+#define LP5012_LED1_BRT 0x08
+#define LP5012_LED2_BRT 0x09
+#define LP5012_LED3_BRT 0x0a
+#define LP5012_OUT0_CLR 0x0b
+#define LP5012_OUT1_CLR 0x0c
+#define LP5012_OUT2_CLR 0x0d
+#define LP5012_OUT3_CLR 0x0e
+#define LP5012_OUT4_CLR 0x0f
+#define LP5012_OUT5_CLR 0x10
+#define LP5012_OUT6_CLR 0x11
+#define LP5012_OUT7_CLR 0x12
+#define LP5012_OUT8_CLR 0x13
+#define LP5012_OUT9_CLR 0x14
+#define LP5012_OUT10_CLR 0x15
+#define LP5012_OUT11_CLR 0x16
+#define LP5012_RESET 0x17
+
+/* LP5018 and LP5024 registers */
+#define LP5024_BNK_BRT 0x03
+#define LP5024_BNKA_CLR 0x04
+#define LP5024_BNKB_CLR 0x05
+#define LP5024_BNKC_CLR 0x06
+#define LP5024_LED0_BRT 0x07
+#define LP5024_LED1_BRT 0x08
+#define LP5024_LED2_BRT 0x09
+#define LP5024_LED3_BRT 0x0a
+#define LP5024_LED4_BRT 0x0b
+#define LP5024_LED5_BRT 0x0c
+#define LP5024_LED6_BRT 0x0d
+#define LP5024_LED7_BRT 0x0e
+
+#define LP5024_OUT0_CLR 0x0f
+#define LP5024_OUT1_CLR 0x10
+#define LP5024_OUT2_CLR 0x11
+#define LP5024_OUT3_CLR 0x12
+#define LP5024_OUT4_CLR 0x13
+#define LP5024_OUT5_CLR 0x14
+#define LP5024_OUT6_CLR 0x15
+#define LP5024_OUT7_CLR 0x16
+#define LP5024_OUT8_CLR 0x17
+#define LP5024_OUT9_CLR 0x18
+#define LP5024_OUT10_CLR 0x19
+#define LP5024_OUT11_CLR 0x1a
+#define LP5024_OUT12_CLR 0x1b
+#define LP5024_OUT13_CLR 0x1c
+#define LP5024_OUT14_CLR 0x1d
+#define LP5024_OUT15_CLR 0x1e
+#define LP5024_OUT16_CLR 0x1f
+#define LP5024_OUT17_CLR 0x20
+#define LP5024_OUT18_CLR 0x21
+#define LP5024_OUT19_CLR 0x22
+#define LP5024_OUT20_CLR 0x23
+#define LP5024_OUT21_CLR 0x24
+#define LP5024_OUT22_CLR 0x25
+#define LP5024_OUT23_CLR 0x26
+#define LP5024_RESET 0x27
+
+/* LP5030 and LP5036 registers */
+#define LP5036_LED_CFG1 0x03
+#define LP5036_BNK_BRT 0x04
+#define LP5036_BNKA_CLR 0x05
+#define LP5036_BNKB_CLR 0x06
+#define LP5036_BNKC_CLR 0x07
+#define LP5036_LED0_BRT 0x08
+#define LP5036_LED1_BRT 0x09
+#define LP5036_LED2_BRT 0x0a
+#define LP5036_LED3_BRT 0x0b
+#define LP5036_LED4_BRT 0x0c
+#define LP5036_LED5_BRT 0x0d
+#define LP5036_LED6_BRT 0x0e
+#define LP5036_LED7_BRT 0x0f
+#define LP5036_LED8_BRT 0x10
+#define LP5036_LED9_BRT 0x11
+#define LP5036_LED10_BRT 0x12
+#define LP5036_LED11_BRT 0x13
+
+#define LP5036_OUT0_CLR 0x14
+#define LP5036_OUT1_CLR 0x15
+#define LP5036_OUT2_CLR 0x16
+#define LP5036_OUT3_CLR 0x17
+#define LP5036_OUT4_CLR 0x18
+#define LP5036_OUT5_CLR 0x19
+#define LP5036_OUT6_CLR 0x1a
+#define LP5036_OUT7_CLR 0x1b
+#define LP5036_OUT8_CLR 0x1c
+#define LP5036_OUT9_CLR 0x1d
+#define LP5036_OUT10_CLR 0x1e
+#define LP5036_OUT11_CLR 0x1f
+#define LP5036_OUT12_CLR 0x20
+#define LP5036_OUT13_CLR 0x21
+#define LP5036_OUT14_CLR 0x22
+#define LP5036_OUT15_CLR 0x23
+#define LP5036_OUT16_CLR 0x24
+#define LP5036_OUT17_CLR 0x25
+#define LP5036_OUT18_CLR 0x26
+#define LP5036_OUT19_CLR 0x27
+#define LP5036_OUT20_CLR 0x28
+#define LP5036_OUT21_CLR 0x29
+#define LP5036_OUT22_CLR 0x2a
+#define LP5036_OUT23_CLR 0x2b
+#define LP5036_OUT24_CLR 0x2c
+#define LP5036_OUT25_CLR 0x2d
+#define LP5036_OUT26_CLR 0x2e
+#define LP5036_OUT27_CLR 0x2f
+#define LP5036_OUT28_CLR 0x30
+#define LP5036_OUT29_CLR 0x31
+#define LP5036_OUT30_CLR 0x32
+#define LP5036_OUT31_CLR 0x33
+#define LP5036_OUT32_CLR 0x34
+#define LP5036_OUT33_CLR 0x35
+#define LP5036_OUT34_CLR 0x36
+#define LP5036_OUT35_CLR 0x37
+#define LP5036_RESET 0x38
+
+#define LP50XX_SW_RESET 0xff
+#define LP50XX_CHIP_EN BIT(6)
+
+#define LP5009_MAX_LED_MODULES 2
+#define LP5012_MAX_LED_MODULES 4
+#define LP5018_MAX_LED_MODULES 6
+#define LP5024_MAX_LED_MODULES 8
+#define LP5030_MAX_LED_MODULES 10
+#define LP5036_MAX_LED_MODULES 12
+
+enum lp50xx_model {
+ LP5009,
+ LP5012,
+ LP5018,
+ LP5024,
+ LP5030,
+ LP5036,
+};
+
+struct lp50xx_led {
+ struct led_classdev led_dev;
+ struct led_classdev_mc mc_cdev;
+ struct lp50xx *priv;
+ unsigned long bank_modules;
+ char label[LED_MAX_NAME_SIZE];
+ u8 ctrl_bank_enabled;
+ u32 available_leds;
+ int led_number;
+};
+
+/**
+ * struct lp50xx -
+ * @enable_gpio: Hardware enable gpio
+ * @regulator: LED supply regulator pointer
+ * @client: Pointer to the I2C client
+ * @regmap: Devices register map
+ * @dev: Pointer to the devices device struct
+ * @lock: Lock for reading/writing the device
+ * @model_id: id of the device
+ * @max_leds: maximum number of LEDs the device supports
+ * @num_of_banked_leds: holds the number of banked LEDs
+ * @led_brightness0_reg: first brightness register of the device
+ * @mix_out0_reg: first color mix register of the device
+ * @bank_brt_reg: bank brightness register
+ * @bank_mix_reg: color mix register
+ * @reset_reg: devices reset register
+ * @leds: Array of LED strings
+ */
+struct lp50xx {
+ struct gpio_desc *enable_gpio;
+ struct regulator *regulator;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct device *dev;
+ struct mutex lock;
+ enum lp50xx_model model_id;
+ int max_leds;
+ int num_of_banked_leds;
+
+ u8 led_brightness0_reg;
+ u8 mix_out0_reg;
+ u8 bank_brt_reg;
+ u8 bank_mix_reg;
+ u8 reset_reg;
+
+ /* This needs to be at the end of the struct */
+ struct lp50xx_led leds[];
+};
+
+static const struct reg_default lp5012_reg_defs[] = {
+ {LP50XX_DEV_CFG0, 0x0},
+ {LP50XX_DEV_CFG1, 0x3c},
+ {LP50XX_LED_CFG0, 0x0},
+ {LP5012_BNK_BRT, 0xff},
+ {LP5012_BNKA_CLR, 0x0f},
+ {LP5012_BNKB_CLR, 0x0f},
+ {LP5012_BNKC_CLR, 0x0f},
+ {LP5012_LED0_BRT, 0x0f},
+ {LP5012_LED1_BRT, 0xff},
+ {LP5012_LED2_BRT, 0xff},
+ {LP5012_LED3_BRT, 0xff},
+ {LP5012_OUT0_CLR, 0x0f},
+ {LP5012_OUT1_CLR, 0x00},
+ {LP5012_OUT2_CLR, 0x00},
+ {LP5012_OUT3_CLR, 0x00},
+ {LP5012_OUT4_CLR, 0x00},
+ {LP5012_OUT5_CLR, 0x00},
+ {LP5012_OUT6_CLR, 0x00},
+ {LP5012_OUT7_CLR, 0x00},
+ {LP5012_OUT8_CLR, 0x00},
+ {LP5012_OUT9_CLR, 0x00},
+ {LP5012_OUT10_CLR, 0x00},
+ {LP5012_OUT11_CLR, 0x00},
+ {LP5012_RESET, 0x00}
+};
+
+static const struct reg_default lp5024_reg_defs[] = {
+ {LP50XX_DEV_CFG0, 0x0},
+ {LP50XX_DEV_CFG1, 0x3c},
+ {LP50XX_LED_CFG0, 0x0},
+ {LP5024_BNK_BRT, 0xff},
+ {LP5024_BNKA_CLR, 0x0f},
+ {LP5024_BNKB_CLR, 0x0f},
+ {LP5024_BNKC_CLR, 0x0f},
+ {LP5024_LED0_BRT, 0x0f},
+ {LP5024_LED1_BRT, 0xff},
+ {LP5024_LED2_BRT, 0xff},
+ {LP5024_LED3_BRT, 0xff},
+ {LP5024_LED4_BRT, 0xff},
+ {LP5024_LED5_BRT, 0xff},
+ {LP5024_LED6_BRT, 0xff},
+ {LP5024_LED7_BRT, 0xff},
+ {LP5024_OUT0_CLR, 0x0f},
+ {LP5024_OUT1_CLR, 0x00},
+ {LP5024_OUT2_CLR, 0x00},
+ {LP5024_OUT3_CLR, 0x00},
+ {LP5024_OUT4_CLR, 0x00},
+ {LP5024_OUT5_CLR, 0x00},
+ {LP5024_OUT6_CLR, 0x00},
+ {LP5024_OUT7_CLR, 0x00},
+ {LP5024_OUT8_CLR, 0x00},
+ {LP5024_OUT9_CLR, 0x00},
+ {LP5024_OUT10_CLR, 0x00},
+ {LP5024_OUT11_CLR, 0x00},
+ {LP5024_OUT12_CLR, 0x00},
+ {LP5024_OUT13_CLR, 0x00},
+ {LP5024_OUT14_CLR, 0x00},
+ {LP5024_OUT15_CLR, 0x00},
+ {LP5024_OUT16_CLR, 0x00},
+ {LP5024_OUT17_CLR, 0x00},
+ {LP5024_OUT18_CLR, 0x00},
+ {LP5024_OUT19_CLR, 0x00},
+ {LP5024_OUT20_CLR, 0x00},
+ {LP5024_OUT21_CLR, 0x00},
+ {LP5024_OUT22_CLR, 0x00},
+ {LP5024_OUT23_CLR, 0x00},
+ {LP5024_RESET, 0x00}
+};
+
+static const struct reg_default lp5036_reg_defs[] = {
+ {LP50XX_DEV_CFG0, 0x0},
+ {LP50XX_DEV_CFG1, 0x3c},
+ {LP50XX_LED_CFG0, 0x0},
+ {LP5036_LED_CFG1, 0x0},
+ {LP5036_BNK_BRT, 0xff},
+ {LP5036_BNKA_CLR, 0x0f},
+ {LP5036_BNKB_CLR, 0x0f},
+ {LP5036_BNKC_CLR, 0x0f},
+ {LP5036_LED0_BRT, 0x0f},
+ {LP5036_LED1_BRT, 0xff},
+ {LP5036_LED2_BRT, 0xff},
+ {LP5036_LED3_BRT, 0xff},
+ {LP5036_LED4_BRT, 0xff},
+ {LP5036_LED5_BRT, 0xff},
+ {LP5036_LED6_BRT, 0xff},
+ {LP5036_LED7_BRT, 0xff},
+ {LP5036_OUT0_CLR, 0x0f},
+ {LP5036_OUT1_CLR, 0x00},
+ {LP5036_OUT2_CLR, 0x00},
+ {LP5036_OUT3_CLR, 0x00},
+ {LP5036_OUT4_CLR, 0x00},
+ {LP5036_OUT5_CLR, 0x00},
+ {LP5036_OUT6_CLR, 0x00},
+ {LP5036_OUT7_CLR, 0x00},
+ {LP5036_OUT8_CLR, 0x00},
+ {LP5036_OUT9_CLR, 0x00},
+ {LP5036_OUT10_CLR, 0x00},
+ {LP5036_OUT11_CLR, 0x00},
+ {LP5036_OUT12_CLR, 0x00},
+ {LP5036_OUT13_CLR, 0x00},
+ {LP5036_OUT14_CLR, 0x00},
+ {LP5036_OUT15_CLR, 0x00},
+ {LP5036_OUT16_CLR, 0x00},
+ {LP5036_OUT17_CLR, 0x00},
+ {LP5036_OUT18_CLR, 0x00},
+ {LP5036_OUT19_CLR, 0x00},
+ {LP5036_OUT20_CLR, 0x00},
+ {LP5036_OUT21_CLR, 0x00},
+ {LP5036_OUT22_CLR, 0x00},
+ {LP5036_OUT23_CLR, 0x00},
+ {LP5036_OUT24_CLR, 0x00},
+ {LP5036_OUT25_CLR, 0x00},
+ {LP5036_OUT26_CLR, 0x00},
+ {LP5036_OUT27_CLR, 0x00},
+ {LP5036_OUT28_CLR, 0x00},
+ {LP5036_OUT29_CLR, 0x00},
+ {LP5036_OUT30_CLR, 0x00},
+ {LP5036_OUT31_CLR, 0x00},
+ {LP5036_OUT32_CLR, 0x00},
+ {LP5036_OUT33_CLR, 0x00},
+ {LP5036_OUT34_CLR, 0x00},
+ {LP5036_OUT35_CLR, 0x00},
+ {LP5036_RESET, 0x00}
+};
+
+static const struct regmap_config lp5012_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP5012_RESET,
+ .reg_defaults = lp5012_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lp5012_reg_defs),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct regmap_config lp5024_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP5024_RESET,
+ .reg_defaults = lp5024_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lp5024_reg_defs),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static const struct regmap_config lp5036_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP5036_RESET,
+ .reg_defaults = lp5036_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lp5036_reg_defs),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static struct lp50xx_led *mcled_cdev_to_led(struct led_classdev_mc *mcled_cdev)
+{
+ return container_of(mcled_cdev, struct lp50xx_led, mc_cdev);
+}
+
+static int lp50xx_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct lp50xx_led *led = container_of(cdev, struct lp50xx_led, led_dev);
+ int ret = 0;
+ u8 reg_val;
+
+ mutex_lock(&led->priv->lock);
+
+ if (led->ctrl_bank_enabled)
+ reg_val = led->priv->bank_brt_reg;
+ else
+ reg_val = led->priv->led_brightness0_reg + led->led_number;
+
+ ret = regmap_write(led->priv->regmap, reg_val, brightness);
+
+ mutex_unlock(&led->priv->lock);
+
+ return ret;
+}
+
+static enum led_brightness lp50xx_brightness_get(struct led_classdev *cdev)
+{
+ struct lp50xx_led *led = container_of(cdev, struct lp50xx_led, led_dev);
+ unsigned int brt_val;
+ u8 reg_val;
+ int ret;
+
+ mutex_lock(&led->priv->lock);
+
+ if (led->ctrl_bank_enabled)
+ reg_val = led->priv->bank_brt_reg;
+ else
+ reg_val = led->priv->led_brightness0_reg + led->led_number;
+
+ ret = regmap_read(led->priv->regmap, reg_val, &brt_val);
+
+ mutex_unlock(&led->priv->lock);
+
+ return brt_val;
+}
+
+static int lp50xx_get_color(struct led_classdev_mc *mcled_cdev, int color)
+{
+ struct lp50xx_led *led = mcled_cdev_to_led(mcled_cdev);
+ struct lp50xx *priv = led->priv;
+ u8 led_offset, reg_number, reg_color_off;
+ unsigned int value;
+ int ret;
+
+ if (color == LED_COLOR_ID_RED)
+ reg_color_off = 0;
+ else if (color == LED_COLOR_ID_GREEN)
+ reg_color_off = 1;
+ else if (color == LED_COLOR_ID_BLUE)
+ reg_color_off = 2;
+ else
+ goto out;
+
+ if (led->ctrl_bank_enabled) {
+ reg_number = priv->bank_mix_reg + reg_color_off;
+ } else {
+ led_offset = (led->led_number * 3) + reg_color_off;
+ reg_number = priv->mix_out0_reg + led_offset;
+ }
+
+ ret = regmap_read(led->priv->regmap, reg_number, &value);
+ if (ret) {
+ dev_err(&priv->client->dev, "Cannot write LED value\n");
+ goto out;
+ }
+out:
+ return value;
+}
+
+static int lp50xx_set_color(struct led_classdev_mc *mcled_cdev,
+ int color, int value)
+{
+ struct lp50xx_led *led = mcled_cdev_to_led(mcled_cdev);
+ struct lp50xx *priv = led->priv;
+ u8 led_offset, reg_number, reg_color_off;
+ int ret = -EINVAL;
+
+ if (color == LED_COLOR_ID_RED)
+ reg_color_off = 0;
+ else if (color == LED_COLOR_ID_GREEN)
+ reg_color_off = 1;
+ else if (color == LED_COLOR_ID_BLUE)
+ reg_color_off = 2;
+ else
+ goto out;
+
+ if (led->ctrl_bank_enabled) {
+ reg_number = priv->bank_mix_reg + reg_color_off;
+ } else {
+ led_offset = (led->led_number * 3) + reg_color_off;
+ reg_number = priv->mix_out0_reg + led_offset;
+ }
+
+ ret = regmap_write(priv->regmap, reg_number, value);
+ if (ret) {
+ dev_err(&priv->client->dev, "Cannot write LED value\n");
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static struct led_multicolor_ops lp50xx_mc_ops = {
+ .set_color_brightness = lp50xx_set_color,
+ .get_color_brightness = lp50xx_get_color,
+};
+
+static void lp50xx_set_led_values(struct lp50xx *priv)
+{
+ if (priv->model_id == LP5009 || priv->model_id == LP5012) {
+ priv->led_brightness0_reg = LP5012_LED0_BRT;
+ priv->mix_out0_reg = LP5012_OUT0_CLR;
+ priv->bank_brt_reg = LP5012_BNK_BRT;
+ priv->bank_mix_reg = LP5012_BNKA_CLR;
+ priv->reset_reg = LP5012_RESET;
+ } else if (priv->model_id == LP5018 || priv->model_id == LP5024) {
+ priv->led_brightness0_reg = LP5024_LED0_BRT;
+ priv->mix_out0_reg = LP5024_OUT0_CLR;
+ priv->bank_brt_reg = LP5024_BNK_BRT;
+ priv->bank_mix_reg = LP5024_BNKA_CLR;
+ priv->reset_reg = LP5024_RESET;
+ } else {
+ priv->led_brightness0_reg = LP5036_LED0_BRT;
+ priv->mix_out0_reg = LP5036_OUT0_CLR;
+ priv->bank_brt_reg = LP5036_BNK_BRT;
+ priv->bank_mix_reg = LP5036_BNKA_CLR;
+ priv->reset_reg = LP5036_RESET;
+ }
+}
+
+static int lp50xx_set_banks(struct lp50xx *priv, u32 led_strings[])
+{
+ u8 led_ctrl_enable = 0;
+ u8 led1_ctrl_enable = 0;
+ u8 ctrl_ext = 0;
+ int ret;
+ int j;
+
+ for (j = 0; j <= priv->max_leds - 1; j++) {
+ if (led_strings[j] > (LP5024_MAX_LED_MODULES - 1)) {
+ ctrl_ext = led_strings[j] - LP5024_MAX_LED_MODULES;
+ led1_ctrl_enable |= (1 << ctrl_ext);
+ } else {
+ led_ctrl_enable |= (1 << led_strings[j]);
+ }
+ }
+
+ ret = regmap_write(priv->regmap, LP50XX_LED_CFG0, led_ctrl_enable);
+
+ if (led1_ctrl_enable)
+ ret = regmap_write(priv->regmap, LP5036_LED_CFG1,
+ led1_ctrl_enable);
+
+ return ret;
+}
+
+static int lp50xx_reset(struct lp50xx *priv)
+{
+ if (priv->enable_gpio)
+ return gpiod_direction_output(priv->enable_gpio, 1);
+ else
+ return regmap_write(priv->regmap, priv->reset_reg,
+ LP50XX_SW_RESET);
+}
+
+static int lp50xx_enable_disable(struct lp50xx *priv, u8 enable_disable)
+{
+ return regmap_write(priv->regmap, LP50XX_DEV_CFG0, enable_disable);
+}
+
+static int lp50xx_probe_dt(struct lp50xx *priv)
+{
+ u32 led_strings[LP5036_MAX_LED_MODULES];
+ struct fwnode_handle *child = NULL;
+ struct fwnode_handle *led_node = NULL;
+ struct led_init_data init_data;
+ struct lp50xx_led *led;
+ int num_colors;
+ u32 color_id;
+ int led_number;
+ size_t i = 0;
+ int ret;
+
+ priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev,
+ "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->enable_gpio)) {
+ ret = PTR_ERR(priv->enable_gpio);
+ dev_err(&priv->client->dev, "Failed to get enable gpio: %d\n",
+ ret);
+ return ret;
+ }
+
+ priv->regulator = devm_regulator_get(&priv->client->dev, "vled");
+ if (IS_ERR(priv->regulator))
+ priv->regulator = NULL;
+
+ if (priv->model_id == LP5009)
+ priv->max_leds = LP5009_MAX_LED_MODULES;
+ else if (priv->model_id == LP5012)
+ priv->max_leds = LP5012_MAX_LED_MODULES;
+ else if (priv->model_id == LP5018)
+ priv->max_leds = LP5018_MAX_LED_MODULES;
+ else if (priv->model_id == LP5024)
+ priv->max_leds = LP5024_MAX_LED_MODULES;
+ else if (priv->model_id == LP5030)
+ priv->max_leds = LP5030_MAX_LED_MODULES;
+ else
+ priv->max_leds = LP5036_MAX_LED_MODULES;
+
+ device_for_each_child_node(&priv->client->dev, child) {
+ led = &priv->leds[i];
+ if (fwnode_property_present(child, "ti,led-bank")) {
+ ret = fwnode_property_read_u32_array(child,
+ "ti,led-bank",
+ NULL, 0);
+ ret = fwnode_property_read_u32_array(child,
+ "ti,led-bank",
+ led_strings,
+ ret);
+
+ priv->num_of_banked_leds = ARRAY_SIZE(led_strings);
+
+ ret = lp50xx_set_banks(priv, led_strings);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Cannot setup banked LEDs\n");
+ fwnode_handle_put(child);
+ goto child_out;
+ }
+ led->ctrl_bank_enabled = 1;
+
+ } else {
+ ret = fwnode_property_read_u32(child, "reg",
+ &led_number);
+
+ led->led_number = led_number;
+ }
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "led sourcing property missing\n");
+ fwnode_handle_put(child);
+ goto child_out;
+ }
+
+ if (led_number > priv->max_leds) {
+ dev_err(&priv->client->dev,
+ "led-sources property is invalid\n");
+ ret = -EINVAL;
+ fwnode_handle_put(child);
+ goto child_out;
+ }
+
+ init_data.fwnode = child;
+ init_data.devicename = priv->client->name;
+ init_data.default_label = ":";
+
+ fwnode_property_read_string(child, "linux,default-trigger",
+ &led->led_dev.default_trigger);
+ num_colors = 0;
+
+ fwnode_for_each_child_node(child, led_node) {
+ ret = fwnode_property_read_u32(led_node, "color",
+ &color_id);
+ if (ret)
+ dev_err(&priv->client->dev,
+ "Cannot read color\n");
+
+ led->mc_cdev.available_colors |= (1 << color_id);
+ num_colors++;
+
+ }
+
+ led->priv = priv;
+ led->mc_cdev.ops = &lp50xx_mc_ops;
+ led->mc_cdev.num_leds = num_colors;
+ led->mc_cdev.led_cdev = &led->led_dev;
+ led->led_dev.brightness_set_blocking = lp50xx_brightness_set;
+ led->led_dev.brightness_get = lp50xx_brightness_get;
+ ret = led_classdev_multicolor_register_ext(&priv->client->dev,
+ &led->mc_cdev,
+ &init_data);
+ if (ret) {
+ dev_err(&priv->client->dev, "led register err: %d\n",
+ ret);
+ fwnode_handle_put(child);
+ goto child_out;
+ }
+ i++;
+ }
+
+child_out:
+ return ret;
+}
+
+static int lp50xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lp50xx *led;
+ int count;
+ int ret;
+
+ count = device_get_child_node_count(&client->dev);
+ if (!count) {
+ dev_err(&client->dev, "LEDs are not defined in device tree!");
+ return -ENODEV;
+ }
+
+ led = devm_kzalloc(&client->dev, struct_size(led, leds, count),
+ GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ mutex_init(&led->lock);
+ led->client = client;
+ led->dev = &client->dev;
+ led->model_id = id->driver_data;
+ i2c_set_clientdata(client, led);
+
+ if (led->model_id == LP5009 || led->model_id == LP5012)
+ led->regmap = devm_regmap_init_i2c(client,
+ &lp5012_regmap_config);
+ else if (led->model_id == LP5018 || led->model_id == LP5024)
+ led->regmap = devm_regmap_init_i2c(client,
+ &lp5024_regmap_config);
+ else
+ led->regmap = devm_regmap_init_i2c(client,
+ &lp5036_regmap_config);
+
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = lp50xx_reset(led);
+ if (ret)
+ return ret;
+
+ ret = lp50xx_probe_dt(led);
+ if (ret)
+ return ret;
+
+ lp50xx_set_led_values(led);
+
+ return lp50xx_enable_disable(led, LP50XX_CHIP_EN);
+}
+
+static int lp50xx_remove(struct i2c_client *client)
+{
+ struct lp50xx *led = i2c_get_clientdata(client);
+ int ret;
+
+ ret = lp50xx_enable_disable(led, LP50XX_CHIP_EN);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed to disable regulator\n");
+ return ret;
+ }
+
+ if (led->enable_gpio)
+ gpiod_direction_output(led->enable_gpio, 0);
+
+ if (led->regulator) {
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(&led->client->dev,
+ "Failed to disable regulator\n");
+ }
+
+ mutex_destroy(&led->lock);
+
+ return 0;
+}
+
+static const struct i2c_device_id lp50xx_id[] = {
+ { "lp5009", LP5009 },
+ { "lp5012", LP5012 },
+ { "lp5018", LP5018 },
+ { "lp5024", LP5024 },
+ { "lp5030", LP5030 },
+ { "lp5036", LP5036 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp50xx_id);
+
+static const struct of_device_id of_lp50xx_leds_match[] = {
+ { .compatible = "ti,lp5009", },
+ { .compatible = "ti,lp5012", },
+ { .compatible = "ti,lp5018", },
+ { .compatible = "ti,lp5024", },
+ { .compatible = "ti,lp5030", },
+ { .compatible = "ti,lp5036", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lp50xx_leds_match);
+
+static struct i2c_driver lp50xx_driver = {
+ .driver = {
+ .name = "lp50xx",
+ .of_match_table = of_lp50xx_leds_match,
+ },
+ .probe = lp50xx_probe,
+ .remove = lp50xx_remove,
+ .id_table = lp50xx_id,
+};
+module_i2c_driver(lp50xx_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP50XX LED driver");
+MODULE_AUTHOR("Dan Murphy <[email protected]>");
+MODULE_LICENSE("GPL v2");
--
2.22.0.214.g8dca754b1e
Add DT bindings for the LEDs multicolor class framework.
Signed-off-by: Dan Murphy <[email protected]>
---
.../bindings/leds/leds-class-multicolor.txt | 96 +++++++++++++++++++
1 file changed, 96 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
diff --git a/Documentation/devicetree/bindings/leds/leds-class-multicolor.txt b/Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
new file mode 100644
index 000000000000..5d36327b18fc
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
@@ -0,0 +1,96 @@
+* Multicolor LED properties
+
+Multicolor LEDs can consist of a RGB, RGBW or a RGBA LED clusters. These devices
+can be grouped together and also provide a modeling mechanism so that the
+cluster LEDs can vary in hue and intensity to produce a wide range of colors.
+
+The nodes and properties defined in this document are unique to the multicolor
+LED class. Common LED nodes and properties are inherited from the common.txt
+within this documentation directory.
+
+Required LED Child properties:
+ - color : For multicolor LED support this property should be defined as
+ LED_COLOR_ID_MULTI and further definition can be found in
+ include/linux/leds/common.h.
+
+led-controller@30 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "ti,lp5024";
+ reg = <0x29>;
+
+ multi-led@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_;
+
+
+ led@3 {
+ reg = <3>;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@4 {
+ reg = <4>;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+
+ led@5 {
+ reg = <5>;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+
+ multi-led@2 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_ACTIVITY;
+ reg = <2>;
+ ti,led-bank = <2 3 5>;
+
+ led@6 {
+ reg = <0x6>;
+ color = <LED_COLOR_ID_RED>;
+ led-sources = <6 9 15>;
+ };
+
+ led@7 {
+ reg = <0x7>;
+ color = <LED_COLOR_ID_GREEN>;
+ led-sources = <7 10 16>;
+ };
+
+ led@8 {
+ reg = <0x8>;
+ color = <LED_COLOR_ID_BLUE>;
+ led-sources = <8 11 17>;
+ };
+ };
+
+ multi-led@4 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <4>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_ACTIVITY;
+
+ led@12 {
+ reg = <12>;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@13 {
+ reg = <13>;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+
+ led@14 {
+ reg = <14>;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+
+};
--
2.22.0.214.g8dca754b1e
Introduce the bindings for the Texas Instruments LP5036, LP5030, LP5024,
LP5018, LP5012 and LP5009 RGB LED device driver. The LP5036/30/24/18/12/9
can control RGB LEDs individually or as part of a control bank group.
These devices have the ability to adjust the mixing control for the RGB
LEDs to obtain different colors independent of the overall brightness of
the LED grouping.
Datasheet:
http://www.ti.com/lit/ds/symlink/lp5012.pdf
http://www.ti.com/lit/ds/symlink/lp5024.pdf
http://www.ti.com/lit/ds/symlink/lp5036.pdf
Signed-off-by: Dan Murphy <[email protected]>
---
.../devicetree/bindings/leds/leds-lp50xx.txt | 148 ++++++++++++++++++
1 file changed, 148 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-lp50xx.txt
diff --git a/Documentation/devicetree/bindings/leds/leds-lp50xx.txt b/Documentation/devicetree/bindings/leds/leds-lp50xx.txt
new file mode 100644
index 000000000000..9d05f43042e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-lp50xx.txt
@@ -0,0 +1,148 @@
+* Texas Instruments - LP5009/12/18/24/30/36 RGB LED driver
+
+The LP50XX is multi-channel, I2C RGB LED Drivers that can group RGB LEDs into
+a LED group or control them individually.
+
+The difference in these RGB LED drivers is the number of supported RGB modules.
+
+Required parent properties:
+ - compatible:
+ "ti,lp5009"
+ "ti,lp5012"
+ "ti,lp5018"
+ "ti,lp5024"
+ "ti,lp5030"
+ "ti,lp5036"
+ - reg : I2C slave address
+ lp5009/12 - 0x28
+ lp5018/24 - 0x28
+ lp5030/36 - 0x30
+ - #address-cells : 1
+ - #size-cells : 0
+
+Optional parent properties:
+ - enable-gpios : gpio pin to enable/disable the device.
+ - vled-supply : LED supply
+
+Required child properties:
+ - #address-cells : 1
+ - #size-cells : 0
+ - reg : This is the LED module number.
+ - color : see Documentation/devicetree/bindings/leds/common.txt
+ - function : see Documentation/devicetree/bindings/leds/common.txt
+
+Required child properties only is LED modules will be banked:
+ - ti,led-bank : This property denotes the LED module numbers that will
+ be controlled as a single RGB cluster. Each LED module
+ number will be controlled by a single LED class instance.
+ There can only be one instance of the ti,led-bank
+ property for each device node.
+
+Required grandchildren properties:
+ - reg : A single entry denoting the LED module that controls
+ the RGB cluster.
+ - color : see Documentation/devicetree/bindings/leds/leds-multicolor.txt
+ - led-sources : see Documentation/devicetree/bindings/leds/common.txt
+
+The LED outputs associated with the LED modules are defined in Table 1 of the
+corresponding data sheets.
+
+LP5009 - 2 Total RGB cluster LED outputs 0-1
+LP5012 - 4 Total RGB cluster LED outputs 0-3
+LP5018 - 6 Total RGB cluster LED outputs 0-5
+LP5024 - 8 Total RGB cluster LED outputs 0-7
+LP5030 - 10 Total RGB cluster LED outputs 0-9
+LP5036 - 12 Total RGB cluster LED outputs 0-11
+
+Optional child properties:
+ - label : see Documentation/devicetree/bindings/leds/common.txt
+ - linux,default-trigger :
+ see Documentation/devicetree/bindings/leds/common.txt
+
+Examples:
+led-controller@29 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ compatible = "ti,lp5024";
+ reg = <0x29>;
+ enable-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>;
+ vled-supply = <&vmmcsd_fixed>;
+
+ multi-led@1 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <1>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_STATUS;
+
+ led@3 {
+ reg = <3>;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@4 {
+ reg = <4>;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+
+ led@5 {
+ reg = <5>;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+
+ multi-led@2 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <2>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_STANDBY;
+ ti,led-bank = <2 3 5>;
+
+ led@6 {
+ reg = <0x6>;
+ color = <LED_COLOR_ID_RED>;
+ led-sources = <6 9 15>;
+ };
+
+ led@7 {
+ reg = <0x7>;
+ color = <LED_COLOR_ID_GREEN>;
+ led-sources = <7 10 16>;
+ };
+
+ led@8 {
+ reg = <0x8>;
+ color = <LED_COLOR_ID_BLUE>;
+ led-sources = <8 11 17>;
+ };
+ };
+
+ multi-led@4 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <4>;
+ color = <LED_COLOR_ID_MULTI>;
+ function = LED_FUNCTION_ACTIVITY;
+
+ led@12 {
+ reg = <12>;
+ color = <LED_COLOR_ID_RED>;
+ };
+
+ led@13 {
+ reg = <13>;
+ color = <LED_COLOR_ID_GREEN>;
+ };
+
+ led@14 {
+ reg = <14>;
+ color = <LED_COLOR_ID_BLUE>;
+ };
+ };
+};
+
+For more product information please see the link below:
+http://www.ti.com/lit/ds/symlink/lp5012.pdf
+http://www.ti.com/lit/ds/symlink/lp5024.pdf
+http://www.ti.com/lit/ds/symlink/lp5036.pdf
--
2.22.0.214.g8dca754b1e
Update the lp5523 to use the multi color framework.
Signed-off-by: Dan Murphy <[email protected]>
---
drivers/leds/leds-lp5523.c | 13 ++
drivers/leds/leds-lp55xx-common.c | 153 ++++++++++++++++++----
drivers/leds/leds-lp55xx-common.h | 10 ++
include/linux/platform_data/leds-lp55xx.h | 6 +
modules.builtin.modinfo | Bin 0 -> 43550 bytes
5 files changed, 159 insertions(+), 23 deletions(-)
create mode 100644 modules.builtin.modinfo
diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
index d0b931a136b9..45380b32563f 100644
--- a/drivers/leds/leds-lp5523.c
+++ b/drivers/leds/leds-lp5523.c
@@ -791,6 +791,18 @@ static ssize_t store_master_fader_leds(struct device *dev,
return ret;
}
+static int lp5523_led_intensity(struct lp55xx_led *led, int chan_num)
+{
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
+
+ mutex_lock(&chip->lock);
+ ret = lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + chan_num,
+ led->brightness);
+ mutex_unlock(&chip->lock);
+ return ret;
+}
+
static int lp5523_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
@@ -857,6 +869,7 @@ static struct lp55xx_device_config lp5523_cfg = {
.max_channel = LP5523_MAX_LEDS,
.post_init_device = lp5523_post_init_device,
.brightness_fn = lp5523_led_brightness,
+ .intensity_fn = lp5523_led_intensity,
.set_led_current = lp5523_set_led_current,
.firmware_cb = lp5523_firmware_loaded,
.run_engine = lp5523_run_engine,
diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
index 44ced02b49f9..32a4ab8664ca 100644
--- a/drivers/leds/leds-lp55xx-common.c
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -35,6 +35,11 @@ static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev)
return cdev_to_lp55xx_led(dev_get_drvdata(dev));
}
+static struct lp55xx_led *mcdev_to_lp55xx_led(struct led_classdev_mc *mc_dev)
+{
+ return container_of(mc_dev, struct lp55xx_led, mc_cdev);
+}
+
static void lp55xx_reset_device(struct lp55xx_chip *chip)
{
struct lp55xx_device_config *cfg = chip->cfg;
@@ -136,20 +141,55 @@ static int lp55xx_set_brightness(struct led_classdev *cdev,
{
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
struct lp55xx_device_config *cfg = led->chip->cfg;
+ int adj_value[LED_COLOR_ID_MAX];
+ int ret;
+ int i;
+
+ if (led->mc_cdev.num_leds > 1) {
+ set_cluster_brightness(&led->mc_cdev, brightness, adj_value);
+ for (i = 0; i < led->mc_cdev.num_leds; i++) {
+ led->brightness = adj_value[i];
+ ret = cfg->intensity_fn(led, led->grouped_channels[i]);
+ if (ret)
+ break;
+ }
+ } else {
+ led->brightness = (u8)brightness;
+ ret = cfg->brightness_fn(led);
+ }
+
+ return ret;
+}
+
+static int lp55xx_set_color(struct led_classdev_mc *mcled_cdev,
+ int color, int value)
+{
+ struct lp55xx_led *led = mcdev_to_lp55xx_led(mcled_cdev);
+ struct lp55xx_device_config *cfg = led->chip->cfg;
+ int i;
+
+ for (i = 0; i < led->mc_cdev.num_leds; i++) {
+ if (led->channel_color[i] == color)
+ break;
+ }
- led->brightness = (u8)brightness;
- return cfg->brightness_fn(led);
+ led->brightness = (u8)value;
+ return cfg->intensity_fn(led, led->grouped_channels[i]);
}
+static struct led_multicolor_ops lp55xx_mc_ops = {
+ .set_color_brightness = lp55xx_set_color,
+};
+
static int lp55xx_init_led(struct lp55xx_led *led,
struct lp55xx_chip *chip, int chan)
{
struct lp55xx_platform_data *pdata = chip->pdata;
struct lp55xx_device_config *cfg = chip->cfg;
struct device *dev = &chip->cl->dev;
+ int max_channel = cfg->max_channel;
char name[32];
int ret;
- int max_channel = cfg->max_channel;
if (chan >= max_channel) {
dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel);
@@ -159,10 +199,37 @@ static int lp55xx_init_led(struct lp55xx_led *led,
if (pdata->led_config[chan].led_current == 0)
return 0;
+ if (pdata->led_config[chan].name) {
+ led->cdev.name = pdata->led_config[chan].name;
+ } else {
+ snprintf(name, sizeof(name), "%s:channel%d",
+ pdata->label ? : chip->cl->name, chan);
+ led->cdev.name = name;
+ }
+
+ if (pdata->led_config[chan].num_colors > 1) {
+ led->mc_cdev.led_cdev = &led->cdev;
+ led->cdev.brightness_set_blocking = lp55xx_set_brightness;
+ led->cdev.name = led->cdev.name;
+ led->cdev.groups = lp55xx_led_groups;
+ led->mc_cdev.ops = &lp55xx_mc_ops;
+ led->mc_cdev.num_leds = pdata->led_config[chan].num_colors;
+ led->mc_cdev.available_colors = pdata->led_config[chan].available_colors;
+ memcpy(led->channel_color,
+ pdata->led_config[chan].channel_color,
+ sizeof(led->channel_color));
+ memcpy(led->grouped_channels,
+ pdata->led_config[chan].grouped_channels,
+ sizeof(led->grouped_channels));
+ } else {
+
+ led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
+ led->cdev.brightness_set_blocking = lp55xx_set_brightness;
+ } led->cdev.groups = lp55xx_led_groups;
+
led->led_current = pdata->led_config[chan].led_current;
led->max_current = pdata->led_config[chan].max_current;
led->chan_nr = pdata->led_config[chan].chan_nr;
- led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
if (led->chan_nr >= max_channel) {
dev_err(dev, "Use channel numbers between 0 and %d\n",
@@ -170,18 +237,11 @@ static int lp55xx_init_led(struct lp55xx_led *led,
return -EINVAL;
}
- led->cdev.brightness_set_blocking = lp55xx_set_brightness;
- led->cdev.groups = lp55xx_led_groups;
-
- if (pdata->led_config[chan].name) {
- led->cdev.name = pdata->led_config[chan].name;
- } else {
- snprintf(name, sizeof(name), "%s:channel%d",
- pdata->label ? : chip->cl->name, chan);
- led->cdev.name = name;
- }
+ if (pdata->led_config[chan].num_colors > 1)
+ ret = led_classdev_multicolor_register(dev, &led->mc_cdev);
+ else
+ ret = led_classdev_register(dev, &led->cdev);
- ret = led_classdev_register(dev, &led->cdev);
if (ret) {
dev_err(dev, "led register err: %d\n", ret);
return ret;
@@ -466,7 +526,6 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
dev_err(&chip->cl->dev, "empty brightness configuration\n");
return -EINVAL;
}
-
for (i = 0; i < num_channels; i++) {
/* do not initialize channels that are not connected */
@@ -538,6 +597,39 @@ void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
}
EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
+static int lp5xx_parse_channel_child(struct device_node *np,
+ struct lp55xx_led_config *cfg,
+ int chan_num)
+{
+ struct device_node *child;
+ int num_colors = 0;
+ u32 color_id;
+ u32 led_number;
+ int ret;
+
+ cfg[chan_num].default_trigger =
+ of_get_property(np, "linux,default-trigger", NULL);
+
+ for_each_child_of_node(np, child) {
+ of_property_read_string(child, "chan-name",
+ &cfg[chan_num].name);
+ of_property_read_u8(child, "led-cur",
+ &cfg[chan_num].led_current);
+ of_property_read_u8(child, "max-cur",
+ &cfg[chan_num].max_current);
+ of_property_read_u32(child, "color", &color_id);
+ cfg[chan_num].channel_color[num_colors] = color_id;
+ cfg[chan_num].available_colors |= 1 << color_id;
+ ret = of_property_read_u32(child, "reg", &led_number);
+ cfg[chan_num].grouped_channels[num_colors] = led_number;
+ num_colors++;
+ }
+
+ cfg->num_colors = num_colors;
+
+ return 0;
+}
+
struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
struct device_node *np)
{
@@ -545,6 +637,8 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
struct lp55xx_platform_data *pdata;
struct lp55xx_led_config *cfg;
int num_channels;
+ int num_chan_children;
+ u32 led_number;
int i = 0;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
@@ -565,13 +659,26 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
pdata->num_channels = num_channels;
for_each_child_of_node(np, child) {
- cfg[i].chan_nr = i;
-
- of_property_read_string(child, "chan-name", &cfg[i].name);
- of_property_read_u8(child, "led-cur", &cfg[i].led_current);
- of_property_read_u8(child, "max-cur", &cfg[i].max_current);
- cfg[i].default_trigger =
- of_get_property(child, "linux,default-trigger", NULL);
+ num_chan_children = of_get_child_count(child);
+ if (num_chan_children != 0)
+ lp5xx_parse_channel_child(child, cfg, i);
+ else {
+ of_property_read_string(child, "chan-name",
+ &cfg[i].name);
+ of_property_read_u8(child, "led-cur",
+ &cfg[i].led_current);
+ of_property_read_u8(child, "max-cur",
+ &cfg[i].max_current);
+ cfg[i].default_trigger =
+ of_get_property(child, "linux,default-trigger",
+ NULL);
+ of_property_read_u32(child, "reg", &led_number);
+
+ if (led_number < 0 || led_number > 6)
+ return ERR_PTR(EINVAL);
+
+ cfg[i].chan_nr = led_number;
+ }
i++;
}
diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h
index 783ed5103ce5..0737e79bc21b 100644
--- a/drivers/leds/leds-lp55xx-common.h
+++ b/drivers/leds/leds-lp55xx-common.h
@@ -12,6 +12,8 @@
#ifndef _LEDS_LP55XX_COMMON_H
#define _LEDS_LP55XX_COMMON_H
+#include <linux/led-class-multicolor.h>
+
enum lp55xx_engine_index {
LP55XX_ENGINE_INVALID,
LP55XX_ENGINE_1,
@@ -109,6 +111,9 @@ struct lp55xx_device_config {
/* access brightness register */
int (*brightness_fn)(struct lp55xx_led *led);
+ /* access specific brightness register */
+ int (*intensity_fn)(struct lp55xx_led *led, int chan_num);
+
/* current setting function */
void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
@@ -159,6 +164,7 @@ struct lp55xx_chip {
* struct lp55xx_led
* @chan_nr : Channel number
* @cdev : LED class device
+ * @mc_cdev : Multi color class device
* @led_current : Current setting at each led channel
* @max_current : Maximun current at each led channel
* @brightness : Brightness value
@@ -167,9 +173,13 @@ struct lp55xx_chip {
struct lp55xx_led {
int chan_nr;
struct led_classdev cdev;
+ struct led_classdev_mc mc_cdev;
u8 led_current;
u8 max_current;
u8 brightness;
+/* int num_colors;*/
+ int channel_color[LED_COLOR_ID_MAX];
+ int grouped_channels[LED_COLOR_ID_MAX];
struct lp55xx_chip *chip;
};
diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h
index 96a787100fda..adce8cec411b 100644
--- a/include/linux/platform_data/leds-lp55xx.h
+++ b/include/linux/platform_data/leds-lp55xx.h
@@ -12,6 +12,8 @@
#ifndef _LEDS_LP55XX_H
#define _LEDS_LP55XX_H
+#include <linux/led-class-multicolor.h>
+
/* Clock configuration */
#define LP55XX_CLOCK_AUTO 0
#define LP55XX_CLOCK_INT 1
@@ -23,6 +25,10 @@ struct lp55xx_led_config {
u8 chan_nr;
u8 led_current; /* mA x10, 0 if led is not connected */
u8 max_current;
+ int num_colors;
+ int available_colors;
+ u32 channel_color[LED_COLOR_ID_MAX];
+ int grouped_channels[LED_COLOR_ID_MAX];
};
struct lp55xx_predef_pattern {
diff --git a/modules.builtin.modinfo b/modules.builtin.modinfo
new file mode 100644
index 0000000000000000000000000000000000000000..e528d8f57796621b6cfef52ad0da44551a482481
GIT binary patch
literal 43550
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
literal 0
HcmV?d00001
--
2.22.0.214.g8dca754b1e
Introduce a multicolor class that groups colored LEDs
within a LED node.
The framework allows for dynamically setting individual LEDs
or setting brightness levels of LEDs and updating them virtually
simultaneously.
Signed-off-by: Dan Murphy <[email protected]>
---
drivers/leds/Kconfig | 10 +
drivers/leds/Makefile | 1 +
drivers/leds/led-class-multicolor.c | 402 +++++++++++++++++++++++++++
include/linux/led-class-multicolor.h | 96 +++++++
4 files changed, 509 insertions(+)
create mode 100644 drivers/leds/led-class-multicolor.c
create mode 100644 include/linux/led-class-multicolor.h
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index f7a3dd7ecf3d..7f780d5b8490 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH
for the flash related features of a LED device. It can be built
as a module.
+config LEDS_CLASS_MULTI_COLOR
+ tristate "LED Mulit Color LED Class Support"
+ depends on LEDS_CLASS
+ help
+ This option enables the multicolor LED sysfs class in /sys/class/leds.
+ It wraps LED Class and adds multicolor LED specific sysfs attributes
+ and kernel internal API to it. You'll need this to provide support
+ for multicolor LEDs that are grouped together. This class is not
+ intended for single color LEDs. It can be built as a module.
+
config LEDS_BRIGHTNESS_HW_CHANGED
bool "LED Class brightness_hw_changed attribute support"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 41fb073a39c1..897b810257dd 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -4,6 +4,7 @@
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
+obj-$(CONFIG_LEDS_CLASS_MULTI_COLOR) += led-class-multicolor.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
# LED Platform Drivers
diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c
new file mode 100644
index 000000000000..123443e6d3eb
--- /dev/null
+++ b/drivers/leds/led-class-multicolor.c
@@ -0,0 +1,402 @@
+// SPDX-License-Identifier: GPL-2.0
+// LED Multi Color class interface
+// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include "leds.h"
+
+struct led_classdev_mc_priv {
+ struct led_classdev_mc *mcled_cdev;
+
+ struct device_attribute max_intensity_attr;
+ struct device_attribute intensity_attr;
+ struct device_attribute color_index_attr;
+
+ enum led_brightness max_intensity;
+ enum led_brightness intensity;
+ enum led_brightness new_intensity;
+
+ struct list_head list;
+
+ int color_id;
+ int color_index;
+};
+static int calculate_brightness(int brightness, int intensity, int max_intensity)
+{
+ if (brightness && intensity && max_intensity)
+ return brightness * intensity / max_intensity;
+
+ return LED_OFF;
+}
+
+void set_cluster_brightness(struct led_classdev_mc *mcled_cdev,
+ enum led_brightness brightness, int adj_value[])
+{
+ struct led_classdev_mc_data *data = mcled_cdev->data;
+ struct led_classdev_mc_priv *priv;
+ int i = 0;
+
+ list_for_each_entry(priv, &data->color_list, list) {
+ adj_value[i] = calculate_brightness(brightness, priv->intensity,
+ priv->max_intensity);
+ i++;
+ }
+
+ data->cluster_brightness = brightness;
+}
+
+void get_cluster_brightness(struct led_classdev_mc *mcled_cdev,
+ int brightness_val[])
+{
+ struct led_classdev_mc_data *data = mcled_cdev->data;
+ struct led_classdev_mc_priv *priv;
+ int i = 0;
+
+ list_for_each_entry(priv, &data->color_list, list) {
+ brightness_val[i] = priv->intensity;
+ i++;
+ }
+}
+
+static ssize_t color_index_show(struct device *dev,
+ struct device_attribute *color_index_attr,
+ char *buf)
+{
+ struct led_classdev_mc_priv *priv = container_of(color_index_attr,
+ struct led_classdev_mc_priv,
+ color_index_attr);
+
+ return sprintf(buf, "%d\n", priv->color_id);
+}
+
+static ssize_t color_mix_store(struct device *dev,
+ struct device_attribute *color_mix_attr,
+ const char *buf, size_t size)
+{
+ struct led_classdev_mc_data *data = container_of(color_mix_attr,
+ struct led_classdev_mc_data,
+ color_mix_attr);
+ struct led_classdev_mc *mcled_cdev = data->mcled_cdev;
+ const struct led_multicolor_ops *ops = mcled_cdev->ops;
+ struct led_classdev_mc_priv *priv;
+ unsigned int value[LED_COLOR_ID_MAX];
+ int adj_brightness;
+ int nrchars, offset = 0;
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < mcled_cdev->num_leds; i++) {
+ ret = sscanf(buf + offset, "%i%n", &value[i], &nrchars);
+ if (ret != 1)
+ break;
+
+ offset += nrchars;
+ }
+
+ if (i != mcled_cdev->num_leds) {
+ for (; i < LED_COLOR_ID_MAX; i++)
+ value[i] = 0;
+ }
+
+ list_for_each_entry(priv, &data->color_list, list) {
+ if (data->cluster_brightness) {
+ adj_brightness = calculate_brightness(data->cluster_brightness,
+ value[priv->color_index],
+ priv->max_intensity);
+ ret = ops->set_color_brightness(priv->mcled_cdev,
+ priv->color_id,
+ adj_brightness);
+ if (ret < 0)
+ goto done;
+ }
+
+ priv->intensity = value[priv->color_index];
+ }
+
+done:
+ return size;
+}
+
+static ssize_t intensity_store(struct device *dev,
+ struct device_attribute *intensity_attr,
+ const char *buf, size_t size)
+{
+ struct led_classdev_mc_priv *priv = container_of(intensity_attr,
+ struct led_classdev_mc_priv,
+ intensity_attr);
+ struct led_classdev_mc_data *data = priv->mcled_cdev->data;
+ struct led_classdev *led_cdev = priv->mcled_cdev->led_cdev;
+ struct led_multicolor_ops *ops = priv->mcled_cdev->ops;
+ unsigned int adj_value;
+ unsigned long value;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ ret = kstrtoul(buf, 10, &value);
+ if (ret)
+ goto unlock;
+
+ if (value > priv->max_intensity) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ priv->new_intensity = value;
+
+ if (data->cluster_brightness) {
+ adj_value = calculate_brightness(data->cluster_brightness,
+ priv->new_intensity,
+ priv->max_intensity);
+ ret = ops->set_color_brightness(priv->mcled_cdev,
+ priv->color_id, adj_value);
+ if (ret < 0) {
+ priv->new_intensity = priv->intensity;
+ goto unlock;
+ }
+ }
+
+ priv->intensity = priv->new_intensity;
+ ret = size;
+
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t intensity_show(struct device *dev,
+ struct device_attribute *intensity_attr,
+ char *buf)
+{
+ struct led_classdev_mc_priv *priv = container_of(intensity_attr,
+ struct led_classdev_mc_priv,
+ intensity_attr);
+ const struct led_multicolor_ops *ops = priv->mcled_cdev->ops;
+ int value = 0;
+
+ if (ops->get_color_brightness) {
+ value = ops->get_color_brightness(priv->mcled_cdev,
+ priv->color_id);
+ priv->intensity = value;
+ } else {
+ value = priv->intensity;
+ }
+
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t max_intensity_show(struct device *dev,
+ struct device_attribute *max_intensity_attr,
+ char *buf)
+{
+ struct led_classdev_mc_priv *priv = container_of(max_intensity_attr,
+ struct led_classdev_mc_priv,
+ max_intensity_attr);
+
+ return sprintf(buf, "%d\n", priv->max_intensity);
+}
+
+static int led_multicolor_init_color(struct led_classdev_mc_data *data,
+ struct led_classdev_mc *mcled_cdev,
+ int color_id, int color_index)
+{
+ struct led_classdev *led_cdev = mcled_cdev->led_cdev;
+ struct led_classdev_mc_priv *mc_priv;
+ int ret;
+
+ mc_priv = devm_kzalloc(led_cdev->dev, sizeof(*mc_priv), GFP_KERNEL);
+ if (!mc_priv)
+ return -ENOMEM;
+
+ mc_priv->color_id = color_id;
+ mc_priv->mcled_cdev = mcled_cdev;
+
+ data->led_kobj = kobject_create_and_add(led_colors[color_id],
+ data->color_kobj);
+ if (!data->led_kobj)
+ return -EINVAL;
+
+ sysfs_attr_init(&mc_priv->intensity_attr.attr);
+ mc_priv->intensity_attr.attr.name = "intensity";
+ mc_priv->intensity_attr.attr.mode = S_IRUGO | S_IWUGO;
+ mc_priv->intensity_attr.store = intensity_store;
+ mc_priv->intensity_attr.show = intensity_show;
+ ret = sysfs_create_file(data->led_kobj,
+ &mc_priv->intensity_attr.attr);
+ if (ret)
+ goto err_out;
+
+ sysfs_attr_init(&mc_priv->max_intensity_attr.attr);
+ mc_priv->max_intensity_attr.attr.name = "max_intensity";
+ mc_priv->max_intensity_attr.attr.mode = S_IRUGO;
+ mc_priv->max_intensity_attr.show = max_intensity_show;
+ ret = sysfs_create_file(data->led_kobj,
+ &mc_priv->max_intensity_attr.attr);
+ if (ret)
+ goto err_out;
+
+ sysfs_attr_init(&mc_priv->color_index_attr.attr);
+ mc_priv->color_index_attr.attr.name = "color_id";
+ mc_priv->color_index_attr.attr.mode = S_IRUGO;
+ mc_priv->color_index_attr.show = color_index_show;
+ ret = sysfs_create_file(data->led_kobj,
+ &mc_priv->color_index_attr.attr);
+ if (ret)
+ goto err_out;
+
+ mc_priv->max_intensity = LED_FULL;
+ mc_priv->color_index = color_index;
+ list_add_tail(&mc_priv->list, &data->color_list);
+
+err_out:
+ return ret;
+}
+
+static int led_multicolor_init_color_dir(struct led_classdev_mc_data *data,
+ struct led_classdev_mc *mcled_cdev)
+{
+ struct led_classdev *led_cdev = mcled_cdev->led_cdev;
+ u32 color_id;
+ int ret;
+ int i, j = 0;
+
+ data->color_kobj = kobject_create_and_add("colors",
+ &led_cdev->dev->kobj);
+ if (!data->color_kobj)
+ return -EINVAL;
+
+ data->mcled_cdev = mcled_cdev;
+
+ for (i = 0; i < LED_COLOR_ID_MAX; i++) {
+ color_id = (mcled_cdev->available_colors & (1 << i));
+ if (color_id) {
+ ret = led_multicolor_init_color(data, mcled_cdev, i, j);
+ if (ret)
+ break;
+
+ j++;
+ }
+ }
+
+ sysfs_attr_init(&data->color_mix_attr.attr);
+ data->color_mix_attr.attr.name = "color_mix";
+ data->color_mix_attr.attr.mode = S_IRUGO | S_IWUGO;
+ data->color_mix_attr.store = color_mix_store;
+ ret = sysfs_create_file(data->color_kobj,
+ &data->color_mix_attr.attr);
+
+ return ret;
+}
+
+int led_classdev_multicolor_register_ext(struct device *parent,
+ struct led_classdev_mc *mcled_cdev,
+ struct led_init_data *init_data)
+{
+ struct led_classdev *led_cdev;
+ struct led_multicolor_ops *ops;
+ struct led_classdev_mc_data *data;
+ int ret;
+
+ if (!mcled_cdev)
+ return -EINVAL;
+
+ ops = mcled_cdev->ops;
+ if (!ops || !ops->set_color_brightness)
+ return -EINVAL;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ mcled_cdev->data = data;
+ led_cdev = mcled_cdev->led_cdev;
+ INIT_LIST_HEAD(&data->color_list);
+
+ /* Register led class device */
+ ret = led_classdev_register_ext(parent, led_cdev, init_data);
+ if (ret)
+ return ret;
+
+ return led_multicolor_init_color_dir(data, mcled_cdev);
+}
+EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
+
+void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
+{
+ if (!mcled_cdev)
+ return;
+
+ led_classdev_unregister(mcled_cdev->led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
+
+static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
+{
+ led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
+}
+
+/**
+ * devm_of_led_classdev_register - resource managed led_classdev_register()
+ *
+ * @parent: parent of LED device
+ * @led_cdev: the led_classdev structure for this device.
+ */
+int devm_led_classdev_multicolor_register(struct device *parent,
+ struct led_classdev_mc *mcled_cdev)
+{
+ struct led_classdev_mc **dr;
+ int ret;
+
+ dr = devres_alloc(devm_led_classdev_multicolor_release,
+ sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ ret = led_classdev_multicolor_register(parent, mcled_cdev);
+ if (ret) {
+ devres_free(dr);
+ return ret;
+ }
+
+ *dr = mcled_cdev;
+ devres_add(parent, dr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register);
+
+static int devm_led_classdev_multicolor_match(struct device *dev,
+ void *res, void *data)
+{
+ struct mcled_cdev **p = res;
+
+ if (WARN_ON(!p || !*p))
+ return 0;
+
+ return *p == data;
+}
+
+/**
+ * devm_led_classdev_multicolor_unregister() - resource managed
+ * led_classdev_multicolor_unregister()
+ * @parent: The device to unregister.
+ * @mcled_cdev: the led_classdev_mc structure for this device.
+ */
+void devm_led_classdev_multicolor_unregister(struct device *dev,
+ struct led_classdev_mc *mcled_cdev)
+{
+ WARN_ON(devres_release(dev,
+ devm_led_classdev_multicolor_release,
+ devm_led_classdev_multicolor_match, mcled_cdev));
+}
+EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
+
+MODULE_AUTHOR("Dan Murphy <[email protected]>");
+MODULE_DESCRIPTION("Multi Color LED class interface");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/led-class-multicolor.h b/include/linux/led-class-multicolor.h
new file mode 100644
index 000000000000..082883606246
--- /dev/null
+++ b/include/linux/led-class-multicolor.h
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* LED Multicolor class interface
+ * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
+ */
+
+#ifndef __LINUX_MULTICOLOR_LEDS_H_INCLUDED
+#define __LINUX_MULTICOLOR_LEDS_H_INCLUDED
+
+#include <linux/leds.h>
+#include <dt-bindings/leds/common.h>
+
+struct led_classdev_mc;
+
+struct led_classdev_mc_data {
+ struct led_classdev_mc *mcled_cdev;
+
+ struct kobject *color_kobj;
+ struct kobject *led_kobj;
+
+ struct device_attribute color_mix_attr;
+
+ struct list_head color_list;
+
+ enum led_brightness cluster_brightness;
+};
+
+struct led_multicolor_ops {
+ /* Set brightness for a specific color id */
+ int (*set_color_brightness)(struct led_classdev_mc *mcled_cdev,
+ int color_id, int value);
+ /* Read current color setting */
+ int (*get_color_brightness)(struct led_classdev_mc *mcled_cdev,
+ int color_id);
+};
+
+struct led_classdev_mc {
+ /* led class device */
+ struct led_classdev *led_cdev;
+
+ /* multicolor led specific ops */
+ struct led_multicolor_ops *ops;
+
+ struct led_classdev_mc_data *data;
+
+ u32 available_colors;
+ int num_leds;
+};
+
+static inline struct led_classdev_mc *lcdev_to_mccdev(
+ struct led_classdev *lcdev)
+{
+ return container_of(lcdev, struct led_classdev_mc, led_cdev);
+}
+
+/**
+ * led_classdev_multicolor_register_ext - register a new object of led_classdev
+ * class with support for multicolor LEDs
+ * @parent: the multicolor LED to register
+ * @mcled_cdev: the led_classdev_mc structure for this device
+ * @init_data: the LED class Multi color device initialization data
+ *
+ * Returns: 0 on success or negative error value on failure
+ */
+extern int led_classdev_multicolor_register_ext(struct device *parent,
+ struct led_classdev_mc *mcled_cdev,
+ struct led_init_data *init_data);
+
+#define led_classdev_multicolor_register(parent, mcled_cdev) \
+ led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL)
+
+/**
+ * led_classdev_multicolor_unregister - unregisters an object of led_classdev
+ * class with support for multicolor LEDs
+ * @mcled_cdev: the multicolor LED to unregister
+ *
+ * Unregister a previously registered via led_classdev_multicolor_register
+ * object
+ */
+extern void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev);
+
+extern int devm_led_classdev_multicolor_register(struct device *parent,
+ struct led_classdev_mc *mcled_cdev);
+
+extern void devm_led_classdev_multicolor_unregister(struct device *parent,
+ struct led_classdev_mc *mcled_cdev);
+
+/* Set brightness for the monochrome LED cluster */
+extern void set_cluster_brightness(struct led_classdev_mc *mcled_cdev,
+ enum led_brightness brightness,
+ int adj_value[]);
+
+/* Read current cluster brightness */
+extern void get_cluster_brightness(struct led_classdev_mc *mcled_cdev,
+ int brightness_val[]);
+
+#endif /* __LINUX_MULTICOLOR_LEDS_H_INCLUDED */
--
2.22.0.214.g8dca754b1e
Add a new color ID that is declared as MULTICOLOR as with the
multicolor framework declaring a definitive color is not accurate
as the node can contain multiple colors.
Signed-off-by: Dan Murphy <[email protected]>
---
include/dt-bindings/leds/common.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/dt-bindings/leds/common.h b/include/dt-bindings/leds/common.h
index 9e1256a7c1bf..7006d15f71e8 100644
--- a/include/dt-bindings/leds/common.h
+++ b/include/dt-bindings/leds/common.h
@@ -29,7 +29,8 @@
#define LED_COLOR_ID_VIOLET 5
#define LED_COLOR_ID_YELLOW 6
#define LED_COLOR_ID_IR 7
-#define LED_COLOR_ID_MAX 8
+#define LED_COLOR_ID_MULTI 8
+#define LED_COLOR_ID_MAX 9
/* Standard LED functions */
#define LED_FUNCTION_ACTIVITY "activity"
--
2.22.0.214.g8dca754b1e
Dan,
On 7/25/19 8:28 PM, Dan Murphy wrote:
> Add DT bindings for the LEDs multicolor class framework.
>
> Signed-off-by: Dan Murphy <[email protected]>
> ---
> .../bindings/leds/leds-class-multicolor.txt | 96 +++++++++++++++++++
> 1 file changed, 96 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
>
> diff --git a/Documentation/devicetree/bindings/leds/leds-class-multicolor.txt b/Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
> new file mode 100644
> index 000000000000..5d36327b18fc
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/leds/leds-class-multicolor.txt
> @@ -0,0 +1,96 @@
> +* Multicolor LED properties
> +
> +Multicolor LEDs can consist of a RGB, RGBW or a RGBA LED clusters. These devices
There are also RGBAUV LEDs. And maybe some other, I'd not strive to
mention all multicolor LED combinations available on the market.
> +can be grouped together and also provide a modeling mechanism so that the
> +cluster LEDs can vary in hue and intensity to produce a wide range of colors.
It looks as if RGBW, RGB and RGBA LEDs could be grouped together with
this LED multicolor class, which is not what it does. We need to
rephrase that rather to underline the possibility of grouping all the
LED colors of e.g. RGBA under single multi color LED child node.
> +
> +The nodes and properties defined in this document are unique to the multicolor
> +LED class. Common LED nodes and properties are inherited from the common.txt
> +within this documentation directory.
> +
> +Required LED Child properties:
> + - color : For multicolor LED support this property should be defined as
> + LED_COLOR_ID_MULTI and further definition can be found in
> + include/linux/leds/common.h.
> +
> +led-controller@30 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + compatible = "ti,lp5024";
> + reg = <0x29>;
> +
> + multi-led@1 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = <1>;
> + color = <LED_COLOR_ID_MULTI>;
> + function = LED_FUNCTION_;
suffix is missing here
> +
> +
> + led@3 {
> + reg = <3>;
> + color = <LED_COLOR_ID_RED>;
> + };
> +
> + led@4 {
> + reg = <4>;
> + color = <LED_COLOR_ID_GREEN>;
> + };
> +
> + led@5 {
> + reg = <5>;
> + color = <LED_COLOR_ID_BLUE>;
> + };
> + };
> +
> + multi-led@2 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + color = <LED_COLOR_ID_MULTI>;
> + function = LED_FUNCTION_ACTIVITY;
> + reg = <2>;
> + ti,led-bank = <2 3 5>;
> +
> + led@6 {
> + reg = <0x6>;
> + color = <LED_COLOR_ID_RED>;
> + led-sources = <6 9 15>;
> + };
> +
> + led@7 {
> + reg = <0x7>;
> + color = <LED_COLOR_ID_GREEN>;
> + led-sources = <7 10 16>;
> + };
> +
> + led@8 {
> + reg = <0x8>;
> + color = <LED_COLOR_ID_BLUE>;
> + led-sources = <8 11 17>;
> + };
> + };
> +
> + multi-led@4 {
> + #address-cells = <1>;
> + #size-cells = <0>;
> + reg = <4>;
> + color = <LED_COLOR_ID_MULTI>;
> + function = LED_FUNCTION_ACTIVITY;
> +
> + led@12 {
> + reg = <12>;
> + color = <LED_COLOR_ID_RED>;
> + };
> +
> + led@13 {
> + reg = <13>;
> + color = <LED_COLOR_ID_GREEN>;
> + };
> +
> + led@14 {
> + reg = <14>;
> + color = <LED_COLOR_ID_BLUE>;
> + };
> + };
> +
> +};
>
--
Best regards,
Jacek Anaszewski
Hi Dan,
Thank you for the v4.
I have a bunch of comments below. Please take a look.
On 7/25/19 8:28 PM, Dan Murphy wrote:
> Add a documentation of LED Multicolor LED class specific
> sysfs attributes.
>
> Signed-off-by: Dan Murphy <[email protected]>
> ---
> .../ABI/testing/sysfs-class-led-multicolor | 67 +++++++++++++++++++
> 1 file changed, 67 insertions(+)
> create mode 100644 Documentation/ABI/testing/sysfs-class-led-multicolor
>
> diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor b/Documentation/ABI/testing/sysfs-class-led-multicolor
> new file mode 100644
> index 000000000000..59839f0eae76
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-led-multicolor
> @@ -0,0 +1,67 @@
> +What: /sys/class/leds/<led>/brightness
> +Date: Sept 2019
> +KernelVersion: TBD
> +Contact: Dan Murphy <[email protected]>
> +Description: read/write
> + The multicolor class will redirect the device drivers call back
> + function for brightness control to the multicolor class
> + brightness control function.
> +
> + Writing to this file will update all LEDs within the group to a
> + calculated percentage of what each color LED in the group is set
> + to. Please refer to the leds-class-multicolor.txt in the
> + Documentation directory for a complete description.
Instead of redirecting the reader to led-class-multicolor.txt I'd prefer
to have at least the formula to calculate the colors laid out here.
Aside of that - it is more helpful to have a full path to the referenced
file.
> +
> + The value of the color is from 0 to
> + /sys/class/leds/<led>/max_brightness.
> +
> +What: /sys/class/leds/<led>/colors/color_mix
> +Date: Sept 2019
> +KernelVersion: TBD
> +Contact: Dan Murphy <[email protected]>
> +Description: read/write
> + The color_mix file allows writing all registered multicolor LEDs
> + virtually at the same time. The value(s) written to this file
I'd drop parentheses form "value(s)". Multi color LED class device is
supposed to always have more then one LED. And if I understand it
correctly we have to pass intensities of all colors supported by LED
multicolor class device here, even we're changing single one.
> + contain the intensity values for each multicolor LED within
> + the colors directory. The color indexes are reported in the
> + color_id file as defined in this document.
This is a bit misleading. It sounds as if single color_id file would be
reporting more than one index.
> + Please refer to the leds-class-multicolor.txt in the
> + Documentation directory for a complete description.
Here, similarly as for brightness, I would prefer to have complete
documentation of this file.
How about:
The values written to this file should contain the intensity values of
each multicolor LED within the colors directory. The index of given
color is reported by the color_id file present in colors/<color>
directory. The index determines the position in the sequence of
intensities on which the related intensity should be passed to this
file.
And here we could have the examples from leds-class-multicolor.txt.
> +
> +What: /sys/class/leds/<led>/colors/<led_color>/color_id
> +Date: Sept 2019
> +KernelVersion: TBD
> +Contact: Dan Murphy <[email protected]>
> +Description: read only
> + This file when read will return the index of the color in the
> + color_mix.
> + Please refer to the leds-class-multicolor.txt in the
> + Documentation directory for a complete description.
> +
> +What: /sys/class/leds/<led>/colors/<led_color>/intensity
> +Date: Sept 2019
> +KernelVersion: TBD
> +Contact: Dan Murphy <[email protected]>
> +Description: read/write
> + The led_color directory is dynamically created based on the
> + colors defined by the registrar of the class.
> + The led_color can be but not limited to red, green, blue,
> + white, amber, yellow and violet. Drivers can also declare a
Instead of this vague sentence about the available colors I propose to
maintain the list of supported colors in leds-class.rst or in a separate
file and keep it in sync with the led_colors array. Then we could refer
to that file here.
> + LED color for presentation. There is one directory per color
I'd not let drivers define their custom colors. It would entail issues
related to lack of generic LED_COLOR_ID and DT parsing failure.
> + presented. The brightness file is created under each
> + led_color directory and controls the individual LED color
> + setting.
> +
> + The value of the color is from 0 to
> + /sys/class/leds/<led>/colors/<led_color>/max_intensity.
> +
> +What: /sys/class/leds/<led>/colors/<led_color>/max_intensity
> +Date: Sept 2019
> +KernelVersion: TBD
> +Contact: Dan Murphy <[email protected]>
> +Description: read only
> + Maximum intensity level for the LED color, default is
> + 255 (LED_FULL).
> +
> + If the LED does not support different intensity levels, this
> + should be 1.
>
--
Best regards,
Jacek Anaszewski
Dan,
Now we will need this in rst format. It doesn't involve
much tweaking to the txt, but some details like links
to other documents can be enhanced.
I've also come across an issue of lack of line break after listed quoted
strings but I don't see such occurrence here. There might be still
some other quirks emerging after translating rst to html format.
Please check Documentation/doc-guide/sphinx.rst for starter.
On 7/25/19 8:28 PM, Dan Murphy wrote:
> Add the support documentation on the multicolor LED framework.
> This document defines the directores and file generated by the
s/directores/directories/
> multicolor framework. It also documents usage.
>
> Signed-off-by: Dan Murphy <[email protected]>
> ---
> Documentation/leds/leds-class-multicolor.txt | 152 +++++++++++++++++++
> 1 file changed, 152 insertions(+)
> create mode 100644 Documentation/leds/leds-class-multicolor.txt
>
> diff --git a/Documentation/leds/leds-class-multicolor.txt b/Documentation/leds/leds-class-multicolor.txt
> new file mode 100644
> index 000000000000..3b354e523660
> --- /dev/null
> +++ b/Documentation/leds/leds-class-multicolor.txt
> @@ -0,0 +1,152 @@
> +
> +Multi Color LED handling under Linux
> +=================================================
> +
> +Author: Dan Murphy <[email protected]>
> +
> +Description
> +-----------
> +There are varying monochrome LED colors available for application. These
> +LEDs can be used as a single use case LED or can be mixed with other color
> +LEDs to produce the full spectrum of color. Color LEDs that are grouped
> +can be presented under a single LED node with individual color control.
> +The multicolor class groups these LEDs and allows dynamically setting the value
> +of a single LED or setting the intensity values of the LEDs in the group and
> +updating the LEDs virtually simultaneously.
> +
> +Multicolor Class Control
> +-------------------------
> +The multicolor class presents the LED groups under a directory called "colors".
> +This directory is a child under the LED parent node created by the led_class
> +framework. The led_class framework is documented in led-class.txt within this
> +documentation directory.
> +
> +Each colored LED is given its own directory. These directories can be, but not
> +limited to red, green, blue, white, amber, yellow and violet. Under these
Similarly as in 1/9 please refer to the document with available LED
colors and mention that required color should be added if it is
missing.
> +directories the intensity and max_intensity files are presented for each LED.
> +
> +
> +Directory Layout Example
> +------------------------
> +root:/sys/class/leds/rgb:grouped_leds# ls -lR colors/
> +colors/:
> +drwxr-xr-x 2 root root 0 Jun 28 20:21 blue
> +drwxr-xr-x 2 root root 0 Jun 28 20:21 green
> +drwxr-xr-x 2 root root 0 Jun 28 20:21 red
> +-rw------- 1 root root 4096 Jun 28 20:21 color_mix
> +
> +colors/blue:
> +-rw------- 1 root root 4096 Jun 28 20:21 intensity
> +-r-------- 1 root root 4096 Jun 28 20:27 max_intensity
> +-r-------- 1 root root 4096 Jun 28 20:21 color_id
> +
> +colors/green:
> +-rw------- 1 root root 4096 Jun 28 20:22 intensity
> +-r-------- 1 root root 4096 Jun 28 20:27 max_intensity
> +-r-------- 1 root root 4096 Jun 28 20:21 color_id
> +
> +colors/red:
> +-rw------- 1 root root 4096 Jun 28 20:21 intensity
> +-r-------- 1 root root 4096 Jun 28 20:27 max_intensity
> +-r-------- 1 root root 4096 Jun 28 20:21 color_id
> +
> +Multicolor Color Mixing
> +-----------------------
> +Multicolor monochrome LEDs intensity can be modified and mixed to produce a
> +varying array of colors. The color_mix file gives the user the ability to write
> +all the monochrome LEDs registered in the directory with a single call.
> +To create a specific color from monochrome LEDs the color_mix file needs to be
> +written with each color's intensity. The order in which the monochrome LEDs
> +should be written is based on the colors color_id.
> +
> +For example:
> +cat /sys/class/leds/rgb:grouped_leds/red/color_id
> +0
> +cat /sys/class/leds/rgb:grouped_leds/green/color_id
> +1
> +cat /sys/class/leds/rgb:grouped_leds/blue/color_id
> +2
> +
> +red - color_id = 0
> +green - color_id = 1
> +blue - color_id = 2
> +
> +These id's are the order in which to write the color_mix file.
> +
> +echo "<red> <green> <blue>" > color_mix
> +
> +echo "80 00 80" > color_mix
> +
> +The order of the monochrome LEDs are determined during multicolor class
> +registration and will not change unless unregistered and re-registered.
> +
> +Other example with amber monochrome LED:
> +blue - color_id = 0
> +amber - color_id = 1
> +
> +In this exampe blue is at ID 0 and amber ID is 1 so the user would write
s/exampe/example/
> +echo "<blue> <amber>" > color_mix
> +
> +echo "38 80" > color_mix
> +
> +If a single monochrome LED needs to be modified then the user would write the
> +colors/<color>/intensity file.
> +
> +
> +Multicolor Class Brightness Control
> +-----------------------------------
> +The multiclor class framework will calculate each monochrome LEDs intensity.
> +
> +The brightness level for each LED is calculated based on the color LED
> +intensity setting divided by the color LED max intensity setting multiplied by
> +the requested value.
> +
> +led_brightness = requested_value * led_color_intensity/led_color_max_intensity
> +
> +Example:
> +Three LEDs are present in the group as defined in "Directory Layout Example"
> +within this document.
> +
> +A user first writes the color LED brightness file with the brightness level that
> +is necessary to achieve a blueish violet output from the RGB LED group.
> +
> +echo 138 > /sys/class/leds/rgb:grouped_leds/red/intensity
> +echo 43 > /sys/class/leds/rgb:grouped_leds/green/intensity
> +echo 226 > /sys/class/leds/rgb:grouped_leds/blue/intensity
> +
> +red -
> + intensity = 138
> + max_intensity = 255
> +green -
> + intensity = 43
> + max_intensity = 255
> +blue -
> + intensity = 226
> + max_intensity = 255
> +
> +The user can control the brightness of that RGB group by writing the parent
> +'brightness' control. Assuming a parent max_brightness of 255 the user may want
> +to dim the LED color group to half. The user would write a value of 128 to the
> +parent brightness file then the values written to each LED will be adjusted
> +base on this value
s/base/based/
> +
> +cat /sys/class/leds/rgb:grouped_leds/max_brightness
> +255
> +echo 128 > /sys/class/leds/rgb:grouped_leds/brightness
> +
> +adjusted_red_value = 128 * 138/255 = 69
> +adjusted_green_value = 128 * 43/255 = 21
> +adjusted_blue_value = 128 * 226/255 = 113
> +
> +Reading the parent brightness file will return the current brightness value of
> +the color LED group.
> +
> +cat /sys/class/leds/rgb:grouped_leds/max_brightness
> +255
> +
> +echo 128 > /sys/class/leds/rgb:grouped_leds/brightness
> +
> +cat /sys/class/leds/rgb:grouped_leds/max_brightness
s/max_brightness/brightness/
But I'd skip the three above examples - they are trivial and this part
of the interface works identically as the current LED class interface.
> +128
> +
> +
>
--
Best regards,
Jacek Anaszewski
Dan,
On 7/25/19 8:28 PM, Dan Murphy wrote:
> Introduce a multicolor class that groups colored LEDs
> within a LED node.
>
> The framework allows for dynamically setting individual LEDs
> or setting brightness levels of LEDs and updating them virtually
> simultaneously.
>
> Signed-off-by: Dan Murphy <[email protected]>
> ---
> drivers/leds/Kconfig | 10 +
> drivers/leds/Makefile | 1 +
> drivers/leds/led-class-multicolor.c | 402 +++++++++++++++++++++++++++
> include/linux/led-class-multicolor.h | 96 +++++++
> 4 files changed, 509 insertions(+)
> create mode 100644 drivers/leds/led-class-multicolor.c
> create mode 100644 include/linux/led-class-multicolor.h
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index f7a3dd7ecf3d..7f780d5b8490 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH
> for the flash related features of a LED device. It can be built
> as a module.
>
> +config LEDS_CLASS_MULTI_COLOR
> + tristate "LED Mulit Color LED Class Support"
> + depends on LEDS_CLASS
> + help
> + This option enables the multicolor LED sysfs class in /sys/class/leds.
> + It wraps LED Class and adds multicolor LED specific sysfs attributes
s/Class/class/
I'll need to fix it in LED flash class entry too.
> + and kernel internal API to it. You'll need this to provide support
> + for multicolor LEDs that are grouped together. This class is not
> + intended for single color LEDs. It can be built as a module.
> +
> config LEDS_BRIGHTNESS_HW_CHANGED
> bool "LED Class brightness_hw_changed attribute support"
> depends on LEDS_CLASS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 41fb073a39c1..897b810257dd 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -4,6 +4,7 @@
> obj-$(CONFIG_NEW_LEDS) += led-core.o
> obj-$(CONFIG_LEDS_CLASS) += led-class.o
> obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
> +obj-$(CONFIG_LEDS_CLASS_MULTI_COLOR) += led-class-multicolor.o
> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>
> # LED Platform Drivers
> diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c
> new file mode 100644
> index 000000000000..123443e6d3eb
> --- /dev/null
> +++ b/drivers/leds/led-class-multicolor.c
> @@ -0,0 +1,402 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// LED Multi Color class interface
> +// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
> +
> +#include <linux/device.h>
> +#include <linux/init.h>
> +#include <linux/led-class-multicolor.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/uaccess.h>
> +
> +#include "leds.h"
> +
> +struct led_classdev_mc_priv {
> + struct led_classdev_mc *mcled_cdev;
> +
> + struct device_attribute max_intensity_attr;
> + struct device_attribute intensity_attr;
> + struct device_attribute color_index_attr;
> +
> + enum led_brightness max_intensity;
> + enum led_brightness intensity;
> + enum led_brightness new_intensity;
> +
> + struct list_head list;
> +
> + int color_id;
> + int color_index;
We need to differentiate both more meaningfully.
Maybe led_color_id and cluster_color_id?
> +};
> +static int calculate_brightness(int brightness, int intensity, int max_intensity)
How about led_mc_calculate_brightness ?
> +{
> + if (brightness && intensity && max_intensity)
> + return brightness * intensity / max_intensity;
> +
> + return LED_OFF;
> +}
> +
> +void set_cluster_brightness(struct led_classdev_mc *mcled_cdev,
> + enum led_brightness brightness, int adj_value[])
s/set_cluster/led_mc_set_cluster/
And I think adj_value is not telling too much. How about:
s/adj_value/color_intensity/
> +{
> + struct led_classdev_mc_data *data = mcled_cdev->data;
> + struct led_classdev_mc_priv *priv;
> + int i = 0;
> +
> + list_for_each_entry(priv, &data->color_list, list) {
> + adj_value[i] = calculate_brightness(brightness, priv->intensity,
> + priv->max_intensity);
> + i++;
> + }
> +
> + data->cluster_brightness = brightness;
This property duplicates brightness stored in struct led_classdev and
thus it is redundant.
> +}
> +
> +void get_cluster_brightness(struct led_classdev_mc *mcled_cdev,
> + int brightness_val[])
s/get_cluster/led_mc_get_cluster/
s/brightness_val/color_intensity/
> +{
> + struct led_classdev_mc_data *data = mcled_cdev->data;
> + struct led_classdev_mc_priv *priv;
> + int i = 0;
> +
> + list_for_each_entry(priv, &data->color_list, list) {
> + brightness_val[i] = priv->intensity;
> + i++;
> + }
> +}
> +
> +static ssize_t color_index_show(struct device *dev,
> + struct device_attribute *color_index_attr,
> + char *buf)
> +{
> + struct led_classdev_mc_priv *priv = container_of(color_index_attr,
> + struct led_classdev_mc_priv,
> + color_index_attr);
> +
> + return sprintf(buf, "%d\n", priv->color_id);
> +}
> +
> +static ssize_t color_mix_store(struct device *dev,
> + struct device_attribute *color_mix_attr,
> + const char *buf, size_t size)
> +{
> + struct led_classdev_mc_data *data = container_of(color_mix_attr,
> + struct led_classdev_mc_data,
> + color_mix_attr);
> + struct led_classdev_mc *mcled_cdev = data->mcled_cdev;
> + const struct led_multicolor_ops *ops = mcled_cdev->ops;
> + struct led_classdev_mc_priv *priv;
> + unsigned int value[LED_COLOR_ID_MAX];
> + int adj_brightness;
> + int nrchars, offset = 0;
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < mcled_cdev->num_leds; i++) {
> + ret = sscanf(buf + offset, "%i%n", &value[i], &nrchars);
> + if (ret != 1)
> + break;
> +
> + offset += nrchars;
> + }
> +
> + if (i != mcled_cdev->num_leds) {
Shouldn't we return error if i != mcled_cdev->num_leds - 1 ?
How can we know which color the value will be for if there is less
values passed than the total number of colors in the cluster?
> + for (; i < LED_COLOR_ID_MAX; i++)
> + value[i] = 0;
What use case is it for?
> + }
> +
> + list_for_each_entry(priv, &data->color_list, list) {
> + if (data->cluster_brightness) {
> + adj_brightness = calculate_brightness(data->cluster_brightness,
> + value[priv->color_index],
> + priv->max_intensity);
> + ret = ops->set_color_brightness(priv->mcled_cdev,
> + priv->color_id,
> + adj_brightness);
> + if (ret < 0)
> + goto done;
> + }
> +
> + priv->intensity = value[priv->color_index];
> + }
Here we could use just brightness_set op as a single call. We should
always write all colors as a result of write to color_mix anyway.
> +
> +done:
> + return size;
> +}
> +
> +static ssize_t intensity_store(struct device *dev,
> + struct device_attribute *intensity_attr,
> + const char *buf, size_t size)
> +{
> + struct led_classdev_mc_priv *priv = container_of(intensity_attr,
> + struct led_classdev_mc_priv,
> + intensity_attr);
> + struct led_classdev_mc_data *data = priv->mcled_cdev->data;
> + struct led_classdev *led_cdev = priv->mcled_cdev->led_cdev;
> + struct led_multicolor_ops *ops = priv->mcled_cdev->ops;
> + unsigned int adj_value;
> + unsigned long value;
> + ssize_t ret;
> +
> + mutex_lock(&led_cdev->led_access);
> +
> + ret = kstrtoul(buf, 10, &value);
> + if (ret)
> + goto unlock;
> +
> + if (value > priv->max_intensity) {
> + ret = -EINVAL;
> + goto unlock;
> + }
We don't fail in this case in LED class, so let's proceed in the same
way here:
priv->intensity = min(value, priv->max_brightness);
> +
> + priv->new_intensity = value;
> +
> + if (data->cluster_brightness) {
> + adj_value = calculate_brightness(data->cluster_brightness,
> + priv->new_intensity,
> + priv->max_intensity);
> + ret = ops->set_color_brightness(priv->mcled_cdev,
> + priv->color_id, adj_value);
> + if (ret < 0) {
> + priv->new_intensity = priv->intensity;
This is unnecessary complication. Just write the calculated iout
intensity.
We need to highlight it in the documentation that exact requested color
intensity values are written to the hardware only when
brightness == max_brightness.
> + goto unlock;
> + }
> + }
> +
> + priv->intensity = priv->new_intensity;
> + ret = size;
> +
> +unlock:
> + mutex_unlock(&led_cdev->led_access);
> + return ret;
> +}
> +
> +static ssize_t intensity_show(struct device *dev,
> + struct device_attribute *intensity_attr,
> + char *buf)
> +{
> + struct led_classdev_mc_priv *priv = container_of(intensity_attr,
> + struct led_classdev_mc_priv,
> + intensity_attr);
> + const struct led_multicolor_ops *ops = priv->mcled_cdev->ops;
> + int value = 0;
> +
> + if (ops->get_color_brightness) {
> + value = ops->get_color_brightness(priv->mcled_cdev,
> + priv->color_id);
> + priv->intensity = value;
> + } else {
> + value = priv->intensity;
> + }
> +
> + return sprintf(buf, "%d\n", value);
> +}
> +
> +static ssize_t max_intensity_show(struct device *dev,
> + struct device_attribute *max_intensity_attr,
> + char *buf)
> +{
> + struct led_classdev_mc_priv *priv = container_of(max_intensity_attr,
> + struct led_classdev_mc_priv,
> + max_intensity_attr);
> +
> + return sprintf(buf, "%d\n", priv->max_intensity);
> +}
> +
> +static int led_multicolor_init_color(struct led_classdev_mc_data *data,
> + struct led_classdev_mc *mcled_cdev,
> + int color_id, int color_index)
> +{
> + struct led_classdev *led_cdev = mcled_cdev->led_cdev;
> + struct led_classdev_mc_priv *mc_priv;
> + int ret;
> +
> + mc_priv = devm_kzalloc(led_cdev->dev, sizeof(*mc_priv), GFP_KERNEL);
> + if (!mc_priv)
> + return -ENOMEM;
> +
> + mc_priv->color_id = color_id;
> + mc_priv->mcled_cdev = mcled_cdev;
> +
> + data->led_kobj = kobject_create_and_add(led_colors[color_id],
> + data->color_kobj);
You should not use kobject API in the drivers. See [0].
> + if (!data->led_kobj)
> + return -EINVAL;
> +
> + sysfs_attr_init(&mc_priv->intensity_attr.attr);
> + mc_priv->intensity_attr.attr.name = "intensity";
> + mc_priv->intensity_attr.attr.mode = S_IRUGO | S_IWUGO;
> + mc_priv->intensity_attr.store = intensity_store;
> + mc_priv->intensity_attr.show = intensity_show;
> + ret = sysfs_create_file(data->led_kobj,
> + &mc_priv->intensity_attr.attr);
> + if (ret)
> + goto err_out;
> +
> + sysfs_attr_init(&mc_priv->max_intensity_attr.attr);
> + mc_priv->max_intensity_attr.attr.name = "max_intensity";
> + mc_priv->max_intensity_attr.attr.mode = S_IRUGO;
> + mc_priv->max_intensity_attr.show = max_intensity_show;
> + ret = sysfs_create_file(data->led_kobj,
> + &mc_priv->max_intensity_attr.attr);
> + if (ret)
> + goto err_out;
> +
> + sysfs_attr_init(&mc_priv->color_index_attr.attr);
> + mc_priv->color_index_attr.attr.name = "color_id";
> + mc_priv->color_index_attr.attr.mode = S_IRUGO;
> + mc_priv->color_index_attr.show = color_index_show;
> + ret = sysfs_create_file(data->led_kobj,
> + &mc_priv->color_index_attr.attr);
> + if (ret)
> + goto err_out;
> +
> + mc_priv->max_intensity = LED_FULL;
> + mc_priv->color_index = color_index;
> + list_add_tail(&mc_priv->list, &data->color_list);
> +
> +err_out:
> + return ret;
> +}
> +
> +static int led_multicolor_init_color_dir(struct led_classdev_mc_data *data,
> + struct led_classdev_mc *mcled_cdev)
> +{
> + struct led_classdev *led_cdev = mcled_cdev->led_cdev;
> + u32 color_id;
> + int ret;
> + int i, j = 0;
> +
> + data->color_kobj = kobject_create_and_add("colors",
> + &led_cdev->dev->kobj);
> + if (!data->color_kobj)
> + return -EINVAL;
> +
> + data->mcled_cdev = mcled_cdev;
> +
> + for (i = 0; i < LED_COLOR_ID_MAX; i++) {
> + color_id = (mcled_cdev->available_colors & (1 << i));
> + if (color_id) {
> + ret = led_multicolor_init_color(data, mcled_cdev, i, j);
> + if (ret)
> + break;
> +
> + j++;
> + }
> + }
> +
> + sysfs_attr_init(&data->color_mix_attr.attr);
> + data->color_mix_attr.attr.name = "color_mix";
> + data->color_mix_attr.attr.mode = S_IRUGO | S_IWUGO;
> + data->color_mix_attr.store = color_mix_store;
> + ret = sysfs_create_file(data->color_kobj,
> + &data->color_mix_attr.attr);
> +
> + return ret;
> +}
> +
> +int led_classdev_multicolor_register_ext(struct device *parent,
> + struct led_classdev_mc *mcled_cdev,
> + struct led_init_data *init_data)
> +{
> + struct led_classdev *led_cdev;
> + struct led_multicolor_ops *ops;
> + struct led_classdev_mc_data *data;
> + int ret;
> +
> + if (!mcled_cdev)
> + return -EINVAL;
> +
> + ops = mcled_cdev->ops;
> + if (!ops || !ops->set_color_brightness)
> + return -EINVAL;
> +
> + data = kzalloc(sizeof(*data), GFP_KERNEL);
> + if (!data)
> + return -ENOMEM;
> +
> + mcled_cdev->data = data;
> + led_cdev = mcled_cdev->led_cdev;
> + INIT_LIST_HEAD(&data->color_list);
> +
> + /* Register led class device */
> + ret = led_classdev_register_ext(parent, led_cdev, init_data);
> + if (ret)
> + return ret;
> +
> + return led_multicolor_init_color_dir(data, mcled_cdev);
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
> +
> +void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
> +{
> + if (!mcled_cdev)
> + return;
> +
> + led_classdev_unregister(mcled_cdev->led_cdev);
> +}
> +EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
> +
> +static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
> +{
> + led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
> +}
> +
> +/**
> + * devm_of_led_classdev_register - resource managed led_classdev_register()
> + *
> + * @parent: parent of LED device
> + * @led_cdev: the led_classdev structure for this device.
> + */
> +int devm_led_classdev_multicolor_register(struct device *parent,
> + struct led_classdev_mc *mcled_cdev)
> +{
> + struct led_classdev_mc **dr;
> + int ret;
> +
> + dr = devres_alloc(devm_led_classdev_multicolor_release,
> + sizeof(*dr), GFP_KERNEL);
> + if (!dr)
> + return -ENOMEM;
> +
> + ret = led_classdev_multicolor_register(parent, mcled_cdev);
> + if (ret) {
> + devres_free(dr);
> + return ret;
> + }
> +
> + *dr = mcled_cdev;
> + devres_add(parent, dr);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register);
> +
> +static int devm_led_classdev_multicolor_match(struct device *dev,
> + void *res, void *data)
> +{
> + struct mcled_cdev **p = res;
> +
> + if (WARN_ON(!p || !*p))
> + return 0;
> +
> + return *p == data;
> +}
> +
> +/**
> + * devm_led_classdev_multicolor_unregister() - resource managed
> + * led_classdev_multicolor_unregister()
> + * @parent: The device to unregister.
> + * @mcled_cdev: the led_classdev_mc structure for this device.
> + */
> +void devm_led_classdev_multicolor_unregister(struct device *dev,
> + struct led_classdev_mc *mcled_cdev)
> +{
> + WARN_ON(devres_release(dev,
> + devm_led_classdev_multicolor_release,
> + devm_led_classdev_multicolor_match, mcled_cdev));
> +}
> +EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
> +
> +MODULE_AUTHOR("Dan Murphy <[email protected]>");
> +MODULE_DESCRIPTION("Multi Color LED class interface");
> +MODULE_LICENSE("GPL v2");
> diff --git a/include/linux/led-class-multicolor.h b/include/linux/led-class-multicolor.h
> new file mode 100644
> index 000000000000..082883606246
> --- /dev/null
> +++ b/include/linux/led-class-multicolor.h
> @@ -0,0 +1,96 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* LED Multicolor class interface
> + * Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
> + */
> +
> +#ifndef __LINUX_MULTICOLOR_LEDS_H_INCLUDED
> +#define __LINUX_MULTICOLOR_LEDS_H_INCLUDED
> +
> +#include <linux/leds.h>
> +#include <dt-bindings/leds/common.h>
> +
> +struct led_classdev_mc;
> +
> +struct led_classdev_mc_data {
> + struct led_classdev_mc *mcled_cdev;
> +
> + struct kobject *color_kobj;
> + struct kobject *led_kobj;
> +
> + struct device_attribute color_mix_attr;
> +
> + struct list_head color_list;
> +
> + enum led_brightness cluster_brightness;
> +};
> +
> +struct led_multicolor_ops {
> + /* Set brightness for a specific color id */
> + int (*set_color_brightness)(struct led_classdev_mc *mcled_cdev,
> + int color_id, int value);
> + /* Read current color setting */
> + int (*get_color_brightness)(struct led_classdev_mc *mcled_cdev,
> + int color_id);
> +};
> +
> +struct led_classdev_mc {
> + /* led class device */
> + struct led_classdev *led_cdev;
> +
> + /* multicolor led specific ops */
> + struct led_multicolor_ops *ops;
> +
> + struct led_classdev_mc_data *data;
> +
> + u32 available_colors;
> + int num_leds;
> +};
> +
> +static inline struct led_classdev_mc *lcdev_to_mccdev(
> + struct led_classdev *lcdev)
> +{
> + return container_of(lcdev, struct led_classdev_mc, led_cdev);
> +}
> +
> +/**
> + * led_classdev_multicolor_register_ext - register a new object of led_classdev
> + * class with support for multicolor LEDs
> + * @parent: the multicolor LED to register
> + * @mcled_cdev: the led_classdev_mc structure for this device
> + * @init_data: the LED class Multi color device initialization data
> + *
> + * Returns: 0 on success or negative error value on failure
> + */
> +extern int led_classdev_multicolor_register_ext(struct device *parent,
> + struct led_classdev_mc *mcled_cdev,
> + struct led_init_data *init_data);
> +
> +#define led_classdev_multicolor_register(parent, mcled_cdev) \
> + led_classdev_multicolor_register_ext(parent, mcled_cdev, NULL)
> +
> +/**
> + * led_classdev_multicolor_unregister - unregisters an object of led_classdev
> + * class with support for multicolor LEDs
> + * @mcled_cdev: the multicolor LED to unregister
> + *
> + * Unregister a previously registered via led_classdev_multicolor_register
> + * object
> + */
> +extern void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev);
> +
> +extern int devm_led_classdev_multicolor_register(struct device *parent,
> + struct led_classdev_mc *mcled_cdev);
> +
> +extern void devm_led_classdev_multicolor_unregister(struct device *parent,
> + struct led_classdev_mc *mcled_cdev);
> +
> +/* Set brightness for the monochrome LED cluster */
> +extern void set_cluster_brightness(struct led_classdev_mc *mcled_cdev,
> + enum led_brightness brightness,
> + int adj_value[]);
> +
> +/* Read current cluster brightness */
> +extern void get_cluster_brightness(struct led_classdev_mc *mcled_cdev,
> + int brightness_val[]);
> +
> +#endif /* __LINUX_MULTICOLOR_LEDS_H_INCLUDED */
>
I'll review remaining patches (drivers) next time.
[0] https://lore.kernel.org/linux-leds/[email protected]/
--
Best regards,
Jacek Anaszewski
Hi Dan,
Thank you for the patch I see few things to improve.
On 7/25/19 8:28 PM, Dan Murphy wrote:
> Introduce the LP5036/30/24/18 RGB LED driver.
> The difference in these parts are the number of
> LED outputs where the:
>
> LP5036 can control 36 LEDs
> LP5030 can control 30 LEDs
> LP5024 can control 24 LEDs
> LP5018 can control 18 LEDs
>
> The device has the ability to group LED output into control banks
> so that multiple LED banks can be controlled with the same mixing and
> brightness. Inversely the LEDs can also be controlled independently.
>
> Signed-off-by: Dan Murphy <[email protected]>
> ---
> drivers/leds/Kconfig | 7 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-lp50xx.c | 789 +++++++++++++++++++++++++++++++++++++
> 3 files changed, 797 insertions(+)
> create mode 100644 drivers/leds/leds-lp50xx.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 7f780d5b8490..69c037020f6b 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -350,6 +350,13 @@ config LEDS_LP3952
> To compile this driver as a module, choose M here: the
> module will be called leds-lp3952.
>
> +config LEDS_LP50XX
> + tristate "LED Support for TI LP5036/30/24/18 LED driver chip"
> + depends on LEDS_CLASS && REGMAP_I2C
> + help
> + If you say yes here you get support for the Texas Instruments
> + LP5036, LP5030, LP5024 and LP5018 LED driver.
> +
> config LEDS_LP55XX_COMMON
> tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
> depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 897b810257dd..438a5499f3ee 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
> obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
> obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
> obj-$(CONFIG_LEDS_LP3952) += leds-lp3952.o
> +obj-$(CONFIG_LEDS_LP50XX) += leds-lp50xx.o
> obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
> obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
> obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
> diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
> new file mode 100644
> index 000000000000..5a32410e4f34
[...]
> +static int lp50xx_probe_dt(struct lp50xx *priv)
> +{
> + u32 led_strings[LP5036_MAX_LED_MODULES];
> + struct fwnode_handle *child = NULL;
> + struct fwnode_handle *led_node = NULL;
> + struct led_init_data init_data;
> + struct lp50xx_led *led;
> + int num_colors;
> + u32 color_id;
> + int led_number;
> + size_t i = 0;
> + int ret;
> +
> + priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev,
> + "enable", GPIOD_OUT_LOW);
> + if (IS_ERR(priv->enable_gpio)) {
> + ret = PTR_ERR(priv->enable_gpio);
> + dev_err(&priv->client->dev, "Failed to get enable gpio: %d\n",
> + ret);
> + return ret;
> + }
> +
> + priv->regulator = devm_regulator_get(&priv->client->dev, "vled");
> + if (IS_ERR(priv->regulator))
> + priv->regulator = NULL;
> +
> + if (priv->model_id == LP5009)
> + priv->max_leds = LP5009_MAX_LED_MODULES;
> + else if (priv->model_id == LP5012)
> + priv->max_leds = LP5012_MAX_LED_MODULES;
> + else if (priv->model_id == LP5018)
> + priv->max_leds = LP5018_MAX_LED_MODULES;
> + else if (priv->model_id == LP5024)
> + priv->max_leds = LP5024_MAX_LED_MODULES;
> + else if (priv->model_id == LP5030)
> + priv->max_leds = LP5030_MAX_LED_MODULES;
> + else
> + priv->max_leds = LP5036_MAX_LED_MODULES;
You could simplify that by initializing data property of
lp50xx_leds_match array elements and then call of_match_device.
You can compare e.g. drivers/leds/leds-pca9532.c
> + device_for_each_child_node(&priv->client->dev, child) {
> + led = &priv->leds[i];
> + if (fwnode_property_present(child, "ti,led-bank")) {
> + ret = fwnode_property_read_u32_array(child,
> + "ti,led-bank",
> + NULL, 0);
> + ret = fwnode_property_read_u32_array(child,
> + "ti,led-bank",
> + led_strings,
> + ret);
> +
> + priv->num_of_banked_leds = ARRAY_SIZE(led_strings);
> +
> + ret = lp50xx_set_banks(priv, led_strings);
> + if (ret) {
> + dev_err(&priv->client->dev,
> + "Cannot setup banked LEDs\n");
> + fwnode_handle_put(child);
> + goto child_out;
> + }
> + led->ctrl_bank_enabled = 1;
> +
> + } else {
> + ret = fwnode_property_read_u32(child, "reg",
> + &led_number);
> +
> + led->led_number = led_number;
> + }
> + if (ret) {
> + dev_err(&priv->client->dev,
> + "led sourcing property missing\n");
> + fwnode_handle_put(child);
> + goto child_out;
> + }
> +
> + if (led_number > priv->max_leds) {
> + dev_err(&priv->client->dev,
> + "led-sources property is invalid\n");
> + ret = -EINVAL;
> + fwnode_handle_put(child);
> + goto child_out;
> + }
> +
> + init_data.fwnode = child;
> + init_data.devicename = priv->client->name;
We'd rather not have devicename in the name of this LED class device
according to the new LED naming convention. It's not a hot-pluggable
device, so for properly designed DT we should not encounter name
clash.
> + init_data.default_label = ":";
default_label should not be set for new drivers. See the comment in
the include/linux/leds.h:
/*
* string to be used for devicename section of LED class device
* either for label based LED name composition path or for fwnode
* based when devname_mandatory is true
*/
This is new driver and we should not add any convenience fallbacks
for label based DT bindings. Label DT property has been marked
deprecated, so for new drivers it should be deemed non-existent.
> +
> + fwnode_property_read_string(child, "linux,default-trigger",
> + &led->led_dev.default_trigger);
> + num_colors = 0;
> +
> + fwnode_for_each_child_node(child, led_node) {
> + ret = fwnode_property_read_u32(led_node, "color",
> + &color_id);
> + if (ret)
> + dev_err(&priv->client->dev,
> + "Cannot read color\n");
> +
> + led->mc_cdev.available_colors |= (1 << color_id);
> + num_colors++;
> +
> + }
> +
> + led->priv = priv;
> + led->mc_cdev.ops = &lp50xx_mc_ops;
> + led->mc_cdev.num_leds = num_colors;
> + led->mc_cdev.led_cdev = &led->led_dev;
> + led->led_dev.brightness_set_blocking = lp50xx_brightness_set;
> + led->led_dev.brightness_get = lp50xx_brightness_get;
> + ret = led_classdev_multicolor_register_ext(&priv->client->dev,
> + &led->mc_cdev,
> + &init_data);
Why not devm_* prefixed version?
[...]
--
Best regards,
Jacek Anaszewski
Hi Dan,
Thank you for the patch. My comments are below.
On 7/25/19 8:28 PM, Dan Murphy wrote:
> Update the lp5523 to use the multi color framework.
>
> Signed-off-by: Dan Murphy <[email protected]>
> ---
> drivers/leds/leds-lp5523.c | 13 ++
> drivers/leds/leds-lp55xx-common.c | 153 ++++++++++++++++++----
> drivers/leds/leds-lp55xx-common.h | 10 ++
> include/linux/platform_data/leds-lp55xx.h | 6 +
> modules.builtin.modinfo | Bin 0 -> 43550 bytes
> 5 files changed, 159 insertions(+), 23 deletions(-)
> create mode 100644 modules.builtin.modinfo
>
> diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
> index d0b931a136b9..45380b32563f 100644
> --- a/drivers/leds/leds-lp5523.c
> +++ b/drivers/leds/leds-lp5523.c
> @@ -791,6 +791,18 @@ static ssize_t store_master_fader_leds(struct device *dev,
> return ret;
> }
>
> +static int lp5523_led_intensity(struct lp55xx_led *led, int chan_num)
> +{
> + struct lp55xx_chip *chip = led->chip;
> + int ret;
> +
> + mutex_lock(&chip->lock);
> + ret = lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + chan_num,
> + led->brightness);
> + mutex_unlock(&chip->lock);
> + return ret;
> +}
> +
> static int lp5523_led_brightness(struct lp55xx_led *led)
> {
> struct lp55xx_chip *chip = led->chip;
> @@ -857,6 +869,7 @@ static struct lp55xx_device_config lp5523_cfg = {
> .max_channel = LP5523_MAX_LEDS,
> .post_init_device = lp5523_post_init_device,
> .brightness_fn = lp5523_led_brightness,
> + .intensity_fn = lp5523_led_intensity,
> .set_led_current = lp5523_set_led_current,
> .firmware_cb = lp5523_firmware_loaded,
> .run_engine = lp5523_run_engine,
> diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
> index 44ced02b49f9..32a4ab8664ca 100644
> --- a/drivers/leds/leds-lp55xx-common.c
> +++ b/drivers/leds/leds-lp55xx-common.c
> @@ -35,6 +35,11 @@ static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev)
> return cdev_to_lp55xx_led(dev_get_drvdata(dev));
> }
>
> +static struct lp55xx_led *mcdev_to_lp55xx_led(struct led_classdev_mc *mc_dev)
> +{
> + return container_of(mc_dev, struct lp55xx_led, mc_cdev);
> +}
> +
> static void lp55xx_reset_device(struct lp55xx_chip *chip)
> {
> struct lp55xx_device_config *cfg = chip->cfg;
> @@ -136,20 +141,55 @@ static int lp55xx_set_brightness(struct led_classdev *cdev,
> {
> struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
> struct lp55xx_device_config *cfg = led->chip->cfg;
> + int adj_value[LED_COLOR_ID_MAX];
> + int ret;
> + int i;
> +
> + if (led->mc_cdev.num_leds > 1) {
> + set_cluster_brightness(&led->mc_cdev, brightness, adj_value);
> + for (i = 0; i < led->mc_cdev.num_leds; i++) {
> + led->brightness = adj_value[i];
> + ret = cfg->intensity_fn(led, led->grouped_channels[i]);
At first glance I would have hard time discerning between brightness_fn
and intensity_fn purpose. Maybe color_intensity_fn?
> + if (ret)
> + break;
> + }
> + } else {
> + led->brightness = (u8)brightness;
What benefit stems actually from having the copy of brightness
in struct lp55xx_led, when we already have struct led_classdev
there? I know that this is pre-existing, but could be optimized away
while at it. We can have local u8 variable in the op setting brightness
and cast enum_led_brightness to it before passing to lp55xx_write().
> + ret = cfg->brightness_fn(led);
> + }
> +
> + return ret;
> +}
> +
> +static int lp55xx_set_color(struct led_classdev_mc *mcled_cdev,
> + int color, int value)
> +{
> + struct lp55xx_led *led = mcdev_to_lp55xx_led(mcled_cdev);
> + struct lp55xx_device_config *cfg = led->chip->cfg;
> + int i;
> +
> + for (i = 0; i < led->mc_cdev.num_leds; i++) {
> + if (led->channel_color[i] == color)
> + break;
> + }
>
> - led->brightness = (u8)brightness;
> - return cfg->brightness_fn(led);
> + led->brightness = (u8)value;
> + return cfg->intensity_fn(led, led->grouped_channels[i]);
> }
>
> +static struct led_multicolor_ops lp55xx_mc_ops = {
> + .set_color_brightness = lp55xx_set_color,
> +};
> +
> static int lp55xx_init_led(struct lp55xx_led *led,
> struct lp55xx_chip *chip, int chan)
> {
> struct lp55xx_platform_data *pdata = chip->pdata;
> struct lp55xx_device_config *cfg = chip->cfg;
> struct device *dev = &chip->cl->dev;
> + int max_channel = cfg->max_channel;
> char name[32];
> int ret;
> - int max_channel = cfg->max_channel;
>
> if (chan >= max_channel) {
> dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel);
> @@ -159,10 +199,37 @@ static int lp55xx_init_led(struct lp55xx_led *led,
> if (pdata->led_config[chan].led_current == 0)
> return 0;
>
> + if (pdata->led_config[chan].name) {
> + led->cdev.name = pdata->led_config[chan].name;
> + } else {
> + snprintf(name, sizeof(name), "%s:channel%d",
> + pdata->label ? : chip->cl->name, chan);
> + led->cdev.name = name;
> + }
> +
> + if (pdata->led_config[chan].num_colors > 1) {
> + led->mc_cdev.led_cdev = &led->cdev;
> + led->cdev.brightness_set_blocking = lp55xx_set_brightness;
> + led->cdev.name = led->cdev.name;
?
> + led->cdev.groups = lp55xx_led_groups;
> + led->mc_cdev.ops = &lp55xx_mc_ops;
> + led->mc_cdev.num_leds = pdata->led_config[chan].num_colors;
> + led->mc_cdev.available_colors = pdata->led_config[chan].available_colors;
> + memcpy(led->channel_color,
> + pdata->led_config[chan].channel_color,
> + sizeof(led->channel_color));
> + memcpy(led->grouped_channels,
> + pdata->led_config[chan].grouped_channels,
> + sizeof(led->grouped_channels));
> + } else {
> +
> + led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
> + led->cdev.brightness_set_blocking = lp55xx_set_brightness;
> + } led->cdev.groups = lp55xx_led_groups;
> +
> led->led_current = pdata->led_config[chan].led_current;
> led->max_current = pdata->led_config[chan].max_current;
> led->chan_nr = pdata->led_config[chan].chan_nr;
> - led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
>
> if (led->chan_nr >= max_channel) {
> dev_err(dev, "Use channel numbers between 0 and %d\n",
> @@ -170,18 +237,11 @@ static int lp55xx_init_led(struct lp55xx_led *led,
> return -EINVAL;
> }
>
> - led->cdev.brightness_set_blocking = lp55xx_set_brightness;
> - led->cdev.groups = lp55xx_led_groups;
> -
> - if (pdata->led_config[chan].name) {
> - led->cdev.name = pdata->led_config[chan].name;
> - } else {
> - snprintf(name, sizeof(name), "%s:channel%d",
> - pdata->label ? : chip->cl->name, chan);
> - led->cdev.name = name;
> - }
> + if (pdata->led_config[chan].num_colors > 1)
> + ret = led_classdev_multicolor_register(dev, &led->mc_cdev);
> + else
> + ret = led_classdev_register(dev, &led->cdev);
Why not devm ?
> - ret = led_classdev_register(dev, &led->cdev);
> if (ret) {
> dev_err(dev, "led register err: %d\n", ret);
> return ret;
> @@ -466,7 +526,6 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
> dev_err(&chip->cl->dev, "empty brightness configuration\n");
> return -EINVAL;
> }
> -
> for (i = 0; i < num_channels; i++) {
>
> /* do not initialize channels that are not connected */
> @@ -538,6 +597,39 @@ void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
> }
> EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
>
> +static int lp5xx_parse_channel_child(struct device_node *np,
> + struct lp55xx_led_config *cfg,
> + int chan_num)
> +{
> + struct device_node *child;
> + int num_colors = 0;
> + u32 color_id;
> + u32 led_number;
> + int ret;
> +
> + cfg[chan_num].default_trigger =
> + of_get_property(np, "linux,default-trigger", NULL);
> +
> + for_each_child_of_node(np, child) {
> + of_property_read_string(child, "chan-name",
> + &cfg[chan_num].name);
> + of_property_read_u8(child, "led-cur",
> + &cfg[chan_num].led_current);
> + of_property_read_u8(child, "max-cur",
> + &cfg[chan_num].max_current);
> + of_property_read_u32(child, "color", &color_id);
> + cfg[chan_num].channel_color[num_colors] = color_id;
> + cfg[chan_num].available_colors |= 1 << color_id;
set_bit(color_id, &cfg[chan_num].available_colors);
> + ret = of_property_read_u32(child, "reg", &led_number);
> + cfg[chan_num].grouped_channels[num_colors] = led_number;
> + num_colors++;
> + }
> +
> + cfg->num_colors = num_colors;
> +
> + return 0;
> +}
> +
> struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
> struct device_node *np)
> {
> @@ -545,6 +637,8 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
> struct lp55xx_platform_data *pdata;
> struct lp55xx_led_config *cfg;
> int num_channels;
> + int num_chan_children;
> + u32 led_number;
> int i = 0;
>
> pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
> @@ -565,13 +659,26 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
> pdata->num_channels = num_channels;
>
> for_each_child_of_node(np, child) {
> - cfg[i].chan_nr = i;
> -
> - of_property_read_string(child, "chan-name", &cfg[i].name);
> - of_property_read_u8(child, "led-cur", &cfg[i].led_current);
> - of_property_read_u8(child, "max-cur", &cfg[i].max_current);
> - cfg[i].default_trigger =
> - of_get_property(child, "linux,default-trigger", NULL);
> + num_chan_children = of_get_child_count(child);
> + if (num_chan_children != 0)
You're already parsing color. If it is present and equals
LED_COLOR_ID_MULTI then we should expect children in the node.
> + lp5xx_parse_channel_child(child, cfg, i);
> + else {
> + of_property_read_string(child, "chan-name",
> + &cfg[i].name);
> + of_property_read_u8(child, "led-cur",
> + &cfg[i].led_current);
> + of_property_read_u8(child, "max-cur",
> + &cfg[i].max_current);
> + cfg[i].default_trigger =
> + of_get_property(child, "linux,default-trigger",
> + NULL);
> + of_property_read_u32(child, "reg", &led_number);
> +
> + if (led_number < 0 || led_number > 6)
> + return ERR_PTR(EINVAL);
I see code redundancy here. There are common properties to parse
in both multi color and monochrome LED case: chan-name, led-cur,
max-cur, linux,default-trigger, reg. We could have one function
for parsing this common subset.
> +
> + cfg[i].chan_nr = led_number;
> + }
>
> i++;
> }
> diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h
> index 783ed5103ce5..0737e79bc21b 100644
> --- a/drivers/leds/leds-lp55xx-common.h
> +++ b/drivers/leds/leds-lp55xx-common.h
> @@ -12,6 +12,8 @@
> #ifndef _LEDS_LP55XX_COMMON_H
> #define _LEDS_LP55XX_COMMON_H
>
> +#include <linux/led-class-multicolor.h>
> +
> enum lp55xx_engine_index {
> LP55XX_ENGINE_INVALID,
> LP55XX_ENGINE_1,
> @@ -109,6 +111,9 @@ struct lp55xx_device_config {
> /* access brightness register */
> int (*brightness_fn)(struct lp55xx_led *led);
>
> + /* access specific brightness register */
> + int (*intensity_fn)(struct lp55xx_led *led, int chan_num);
> +
> /* current setting function */
> void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
>
> @@ -159,6 +164,7 @@ struct lp55xx_chip {
> * struct lp55xx_led
> * @chan_nr : Channel number
> * @cdev : LED class device
> + * @mc_cdev : Multi color class device
> * @led_current : Current setting at each led channel
> * @max_current : Maximun current at each led channel
> * @brightness : Brightness value
> @@ -167,9 +173,13 @@ struct lp55xx_chip {
> struct lp55xx_led {
> int chan_nr;
> struct led_classdev cdev;
> + struct led_classdev_mc mc_cdev;
> u8 led_current;
> u8 max_current;
> u8 brightness;
> +/* int num_colors;*/
> + int channel_color[LED_COLOR_ID_MAX];
> + int grouped_channels[LED_COLOR_ID_MAX];
> struct lp55xx_chip *chip;
> };
>
> diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h
> index 96a787100fda..adce8cec411b 100644
> --- a/include/linux/platform_data/leds-lp55xx.h
> +++ b/include/linux/platform_data/leds-lp55xx.h
> @@ -12,6 +12,8 @@
> #ifndef _LEDS_LP55XX_H
> #define _LEDS_LP55XX_H
>
> +#include <linux/led-class-multicolor.h>
> +
> /* Clock configuration */
> #define LP55XX_CLOCK_AUTO 0
> #define LP55XX_CLOCK_INT 1
> @@ -23,6 +25,10 @@ struct lp55xx_led_config {
> u8 chan_nr;
> u8 led_current; /* mA x10, 0 if led is not connected */
> u8 max_current;
> + int num_colors;
> + int available_colors;
unsigned long
> + u32 channel_color[LED_COLOR_ID_MAX];
> + int grouped_channels[LED_COLOR_ID_MAX];
> };
>
> struct lp55xx_predef_pattern {
> diff --git a/modules.builtin.modinfo b/modules.builtin.modinfo
> new file mode 100644
> index 0000000000000000000000000000000000000000..e528d8f57796621b6cfef52ad0da44551a482481
> GIT binary patch
> literal 43550
> zcmcJ2?Q+|=(x(2ta}}twKjNIV<j*)6t(~$X+ldo9*0G$-%sF+IMNyQ*NF)`K@<-~u
> zoV|v<f%nGt=>|YGNXqfd?pCE75g*+RpaC?x8;w!ycl>2BO|zrh@WId3MUofUG7gi1
> zeg~})3e2M*O!DyPegBfbM`0dh(V~db<mme9q^|~l3~tY^)aliUI=#90dUn&gPqW$g
[...]
This has gotten here by mistake I presume.
--
Best regards,
Jacek Anaszewski
Jacek
Thanks for looking
On 7/31/19 1:45 PM, Jacek Anaszewski wrote:
> Hi Dan,
>
> Thank you for the patch. My comments are below.
>
> On 7/25/19 8:28 PM, Dan Murphy wrote:
>> Update the lp5523 to use the multi color framework.
>>
>> Signed-off-by: Dan Murphy <[email protected]>
>> ---
>> drivers/leds/leds-lp5523.c | 13 ++
>> drivers/leds/leds-lp55xx-common.c | 153 ++++++++++++++++++----
>> drivers/leds/leds-lp55xx-common.h | 10 ++
>> include/linux/platform_data/leds-lp55xx.h | 6 +
>> modules.builtin.modinfo | Bin 0 -> 43550 bytes
>> 5 files changed, 159 insertions(+), 23 deletions(-)
>> create mode 100644 modules.builtin.modinfo
>>
>> diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
>> index d0b931a136b9..45380b32563f 100644
>> --- a/drivers/leds/leds-lp5523.c
>> +++ b/drivers/leds/leds-lp5523.c
>> @@ -791,6 +791,18 @@ static ssize_t store_master_fader_leds(struct device *dev,
>> return ret;
>> }
>>
>> +static int lp5523_led_intensity(struct lp55xx_led *led, int chan_num)
>> +{
>> + struct lp55xx_chip *chip = led->chip;
>> + int ret;
>> +
>> + mutex_lock(&chip->lock);
>> + ret = lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + chan_num,
>> + led->brightness);
>> + mutex_unlock(&chip->lock);
>> + return ret;
>> +}
>> +
>> static int lp5523_led_brightness(struct lp55xx_led *led)
>> {
>> struct lp55xx_chip *chip = led->chip;
>> @@ -857,6 +869,7 @@ static struct lp55xx_device_config lp5523_cfg = {
>> .max_channel = LP5523_MAX_LEDS,
>> .post_init_device = lp5523_post_init_device,
>> .brightness_fn = lp5523_led_brightness,
>> + .intensity_fn = lp5523_led_intensity,
>> .set_led_current = lp5523_set_led_current,
>> .firmware_cb = lp5523_firmware_loaded,
>> .run_engine = lp5523_run_engine,
>> diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
>> index 44ced02b49f9..32a4ab8664ca 100644
>> --- a/drivers/leds/leds-lp55xx-common.c
>> +++ b/drivers/leds/leds-lp55xx-common.c
>> @@ -35,6 +35,11 @@ static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev)
>> return cdev_to_lp55xx_led(dev_get_drvdata(dev));
>> }
>>
>> +static struct lp55xx_led *mcdev_to_lp55xx_led(struct led_classdev_mc *mc_dev)
>> +{
>> + return container_of(mc_dev, struct lp55xx_led, mc_cdev);
>> +}
>> +
>> static void lp55xx_reset_device(struct lp55xx_chip *chip)
>> {
>> struct lp55xx_device_config *cfg = chip->cfg;
>> @@ -136,20 +141,55 @@ static int lp55xx_set_brightness(struct led_classdev *cdev,
>> {
>> struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
>> struct lp55xx_device_config *cfg = led->chip->cfg;
>> + int adj_value[LED_COLOR_ID_MAX];
>> + int ret;
>> + int i;
>> +
>> + if (led->mc_cdev.num_leds > 1) {
>> + set_cluster_brightness(&led->mc_cdev, brightness, adj_value);
>> + for (i = 0; i < led->mc_cdev.num_leds; i++) {
>> + led->brightness = adj_value[i];
>> + ret = cfg->intensity_fn(led, led->grouped_channels[i]);
> At first glance I would have hard time discerning between brightness_fn
> and intensity_fn purpose. Maybe color_intensity_fn?
>
Ack
>> + if (ret)
>> + break;
>> + }
>> + } else {
>> + led->brightness = (u8)brightness;
> What benefit stems actually from having the copy of brightness
> in struct lp55xx_led, when we already have struct led_classdev
> there? I know that this is pre-existing, but could be optimized away
> while at it. We can have local u8 variable in the op setting brightness
> and cast enum_led_brightness to it before passing to lp55xx_write().
>
This was a carry over from the original driver. Ln 140.
I am not looking to change existing code functionality.
First pass was just to introduce mc_fw with no disruption or regression
to the base functionality.
>> + ret = cfg->brightness_fn(led);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +static int lp55xx_set_color(struct led_classdev_mc *mcled_cdev,
>> + int color, int value)
>> +{
>> + struct lp55xx_led *led = mcdev_to_lp55xx_led(mcled_cdev);
>> + struct lp55xx_device_config *cfg = led->chip->cfg;
>> + int i;
>> +
>> + for (i = 0; i < led->mc_cdev.num_leds; i++) {
>> + if (led->channel_color[i] == color)
>> + break;
>> + }
>>
>> - led->brightness = (u8)brightness;
>> - return cfg->brightness_fn(led);
>> + led->brightness = (u8)value;
>> + return cfg->intensity_fn(led, led->grouped_channels[i]);
>> }
>>
>> +static struct led_multicolor_ops lp55xx_mc_ops = {
>> + .set_color_brightness = lp55xx_set_color,
>> +};
>> +
>> static int lp55xx_init_led(struct lp55xx_led *led,
>> struct lp55xx_chip *chip, int chan)
>> {
>> struct lp55xx_platform_data *pdata = chip->pdata;
>> struct lp55xx_device_config *cfg = chip->cfg;
>> struct device *dev = &chip->cl->dev;
>> + int max_channel = cfg->max_channel;
>> char name[32];
>> int ret;
>> - int max_channel = cfg->max_channel;
>>
>> if (chan >= max_channel) {
>> dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel);
>> @@ -159,10 +199,37 @@ static int lp55xx_init_led(struct lp55xx_led *led,
>> if (pdata->led_config[chan].led_current == 0)
>> return 0;
>>
>> + if (pdata->led_config[chan].name) {
>> + led->cdev.name = pdata->led_config[chan].name;
>> + } else {
>> + snprintf(name, sizeof(name), "%s:channel%d",
>> + pdata->label ? : chip->cl->name, chan);
>> + led->cdev.name = name;
>> + }
>> +
>> + if (pdata->led_config[chan].num_colors > 1) {
>> + led->mc_cdev.led_cdev = &led->cdev;
>> + led->cdev.brightness_set_blocking = lp55xx_set_brightness;
>> + led->cdev.name = led->cdev.name;
> ?
Duplicate I see that now.
>> + led->cdev.groups = lp55xx_led_groups;
>> + led->mc_cdev.ops = &lp55xx_mc_ops;
>> + led->mc_cdev.num_leds = pdata->led_config[chan].num_colors;
>> + led->mc_cdev.available_colors = pdata->led_config[chan].available_colors;
>> + memcpy(led->channel_color,
>> + pdata->led_config[chan].channel_color,
>> + sizeof(led->channel_color));
>> + memcpy(led->grouped_channels,
>> + pdata->led_config[chan].grouped_channels,
>> + sizeof(led->grouped_channels));
>> + } else {
>> +
>> + led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
>> + led->cdev.brightness_set_blocking = lp55xx_set_brightness;
>> + } led->cdev.groups = lp55xx_led_groups;
>> +
>> led->led_current = pdata->led_config[chan].led_current;
>> led->max_current = pdata->led_config[chan].max_current;
>> led->chan_nr = pdata->led_config[chan].chan_nr;
>> - led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
>>
>> if (led->chan_nr >= max_channel) {
>> dev_err(dev, "Use channel numbers between 0 and %d\n",
>> @@ -170,18 +237,11 @@ static int lp55xx_init_led(struct lp55xx_led *led,
>> return -EINVAL;
>> }
>>
>> - led->cdev.brightness_set_blocking = lp55xx_set_brightness;
>> - led->cdev.groups = lp55xx_led_groups;
>> -
>> - if (pdata->led_config[chan].name) {
>> - led->cdev.name = pdata->led_config[chan].name;
>> - } else {
>> - snprintf(name, sizeof(name), "%s:channel%d",
>> - pdata->label ? : chip->cl->name, chan);
>> - led->cdev.name = name;
>> - }
>> + if (pdata->led_config[chan].num_colors > 1)
>> + ret = led_classdev_multicolor_register(dev, &led->mc_cdev);
>> + else
>> + ret = led_classdev_register(dev, &led->cdev);
> Why not devm ?
I will call the devm_ for multicolor but conversion for not MC should be
done separately
Again not trying to regress original functionality.
>> - ret = led_classdev_register(dev, &led->cdev);
>> if (ret) {
>> dev_err(dev, "led register err: %d\n", ret);
>> return ret;
>> @@ -466,7 +526,6 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
>> dev_err(&chip->cl->dev, "empty brightness configuration\n");
>> return -EINVAL;
>> }
>> -
>> for (i = 0; i < num_channels; i++) {
>>
>> /* do not initialize channels that are not connected */
>> @@ -538,6 +597,39 @@ void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
>> }
>> EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
>>
>> +static int lp5xx_parse_channel_child(struct device_node *np,
>> + struct lp55xx_led_config *cfg,
>> + int chan_num)
>> +{
>> + struct device_node *child;
>> + int num_colors = 0;
>> + u32 color_id;
>> + u32 led_number;
>> + int ret;
>> +
>> + cfg[chan_num].default_trigger =
>> + of_get_property(np, "linux,default-trigger", NULL);
>> +
>> + for_each_child_of_node(np, child) {
>> + of_property_read_string(child, "chan-name",
>> + &cfg[chan_num].name);
>> + of_property_read_u8(child, "led-cur",
>> + &cfg[chan_num].led_current);
>> + of_property_read_u8(child, "max-cur",
>> + &cfg[chan_num].max_current);
>> + of_property_read_u32(child, "color", &color_id);
>> + cfg[chan_num].channel_color[num_colors] = color_id;
>> + cfg[chan_num].available_colors |= 1 << color_id;
> set_bit(color_id, &cfg[chan_num].available_colors);
Ack
>
>> + ret = of_property_read_u32(child, "reg", &led_number);
>> + cfg[chan_num].grouped_channels[num_colors] = led_number;
>> + num_colors++;
>> + }
>> +
>> + cfg->num_colors = num_colors;
>> +
>> + return 0;
>> +}
>> +
>> struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
>> struct device_node *np)
>> {
>> @@ -545,6 +637,8 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
>> struct lp55xx_platform_data *pdata;
>> struct lp55xx_led_config *cfg;
>> int num_channels;
>> + int num_chan_children;
>> + u32 led_number;
>> int i = 0;
>>
>> pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
>> @@ -565,13 +659,26 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
>> pdata->num_channels = num_channels;
>>
>> for_each_child_of_node(np, child) {
>> - cfg[i].chan_nr = i;
>> -
>> - of_property_read_string(child, "chan-name", &cfg[i].name);
>> - of_property_read_u8(child, "led-cur", &cfg[i].led_current);
>> - of_property_read_u8(child, "max-cur", &cfg[i].max_current);
>> - cfg[i].default_trigger =
>> - of_get_property(child, "linux,default-trigger", NULL);
>> + num_chan_children = of_get_child_count(child);
>> + if (num_chan_children != 0)
> You're already parsing color. If it is present and equals
> LED_COLOR_ID_MULTI then we should expect children in the node.
I don't see any parsing for color here but I suppose that I can add that now
since it is a common property and if == ID_MULTI then parse children.
>
>> + lp5xx_parse_channel_child(child, cfg, i);
>> + else {
>> + of_property_read_string(child, "chan-name",
>> + &cfg[i].name);
>> + of_property_read_u8(child, "led-cur",
>> + &cfg[i].led_current);
>> + of_property_read_u8(child, "max-cur",
>> + &cfg[i].max_current);
>> + cfg[i].default_trigger =
>> + of_get_property(child, "linux,default-trigger",
>> + NULL);
>> + of_property_read_u32(child, "reg", &led_number);
>> +
>> + if (led_number < 0 || led_number > 6)
>> + return ERR_PTR(EINVAL);
> I see code redundancy here. There are common properties to parse
> in both multi color and monochrome LED case: chan-name, led-cur,
> max-cur, linux,default-trigger, reg. We could have one function
> for parsing this common subset.
ACK
>> +
>> + cfg[i].chan_nr = led_number;
>> + }
>>
>> i++;
>> }
>> diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h
>> index 783ed5103ce5..0737e79bc21b 100644
>> --- a/drivers/leds/leds-lp55xx-common.h
>> +++ b/drivers/leds/leds-lp55xx-common.h
>> @@ -12,6 +12,8 @@
>> #ifndef _LEDS_LP55XX_COMMON_H
>> #define _LEDS_LP55XX_COMMON_H
>>
>> +#include <linux/led-class-multicolor.h>
>> +
>> enum lp55xx_engine_index {
>> LP55XX_ENGINE_INVALID,
>> LP55XX_ENGINE_1,
>> @@ -109,6 +111,9 @@ struct lp55xx_device_config {
>> /* access brightness register */
>> int (*brightness_fn)(struct lp55xx_led *led);
>>
>> + /* access specific brightness register */
>> + int (*intensity_fn)(struct lp55xx_led *led, int chan_num);
>> +
>> /* current setting function */
>> void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
>>
>> @@ -159,6 +164,7 @@ struct lp55xx_chip {
>> * struct lp55xx_led
>> * @chan_nr : Channel number
>> * @cdev : LED class device
>> + * @mc_cdev : Multi color class device
>> * @led_current : Current setting at each led channel
>> * @max_current : Maximun current at each led channel
>> * @brightness : Brightness value
>> @@ -167,9 +173,13 @@ struct lp55xx_chip {
>> struct lp55xx_led {
>> int chan_nr;
>> struct led_classdev cdev;
>> + struct led_classdev_mc mc_cdev;
>> u8 led_current;
>> u8 max_current;
>> u8 brightness;
>> +/* int num_colors;*/
>> + int channel_color[LED_COLOR_ID_MAX];
>> + int grouped_channels[LED_COLOR_ID_MAX];
>> struct lp55xx_chip *chip;
>> };
>>
>> diff --git a/include/linux/platform_data/leds-lp55xx.h b/include/linux/platform_data/leds-lp55xx.h
>> index 96a787100fda..adce8cec411b 100644
>> --- a/include/linux/platform_data/leds-lp55xx.h
>> +++ b/include/linux/platform_data/leds-lp55xx.h
>> @@ -12,6 +12,8 @@
>> #ifndef _LEDS_LP55XX_H
>> #define _LEDS_LP55XX_H
>>
>> +#include <linux/led-class-multicolor.h>
>> +
>> /* Clock configuration */
>> #define LP55XX_CLOCK_AUTO 0
>> #define LP55XX_CLOCK_INT 1
>> @@ -23,6 +25,10 @@ struct lp55xx_led_config {
>> u8 chan_nr;
>> u8 led_current; /* mA x10, 0 if led is not connected */
>> u8 max_current;
>> + int num_colors;
>> + int available_colors;
> unsigned long
ACK
>
>> + u32 channel_color[LED_COLOR_ID_MAX];
>> + int grouped_channels[LED_COLOR_ID_MAX];
>> };
>>
>> struct lp55xx_predef_pattern {
>> diff --git a/modules.builtin.modinfo b/modules.builtin.modinfo
>> new file mode 100644
>> index 0000000000000000000000000000000000000000..e528d8f57796621b6cfef52ad0da44551a482481
>> GIT binary patch
>> literal 43550
>> zcmcJ2?Q+|=(x(2ta}}twKjNIV<j*)6t(~$X+ldo9*0G$-%sF+IMNyQ*NF)`K@<-~u
>> zoV|v<f%nGt=>|YGNXqfd?pCE75g*+RpaC?x8;w!ycl>2BO|zrh@WId3MUofUG7gi1
>> zeg~})3e2M*O!DyPegBfbM`0dh(V~db<mme9q^|~l3~tY^)aliUI=#90dUn&gPqW$g
> [...]
>
> This has gotten here by mistake I presume.
Yes Will remove this
Dan,
On 7/31/19 8:55 PM, Dan Murphy wrote:
> Jacek
>
> Thanks for looking
You're welcome.
> On 7/31/19 1:45 PM, Jacek Anaszewski wrote:
>> Hi Dan,
>>
>> Thank you for the patch. My comments are below.
>>
>> On 7/25/19 8:28 PM, Dan Murphy wrote:
>>> Update the lp5523 to use the multi color framework.
>>>
>>> Signed-off-by: Dan Murphy <[email protected]>
>>> ---
>>> drivers/leds/leds-lp5523.c | 13 ++
>>> drivers/leds/leds-lp55xx-common.c | 153 ++++++++++++++++++----
>>> drivers/leds/leds-lp55xx-common.h | 10 ++
>>> include/linux/platform_data/leds-lp55xx.h | 6 +
>>> modules.builtin.modinfo | Bin 0 -> 43550 bytes
>>> 5 files changed, 159 insertions(+), 23 deletions(-)
>>> create mode 100644 modules.builtin.modinfo
>>>
>>> diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
>>> index d0b931a136b9..45380b32563f 100644
>>> --- a/drivers/leds/leds-lp5523.c
>>> +++ b/drivers/leds/leds-lp5523.c
[...]
>
>>> + if (ret)
>>> + break;
>>> + }
>>> + } else {
>>> + led->brightness = (u8)brightness;
>> What benefit stems actually from having the copy of brightness
>> in struct lp55xx_led, when we already have struct led_classdev
>> there? I know that this is pre-existing, but could be optimized away
>> while at it. We can have local u8 variable in the op setting brightness
>> and cast enum_led_brightness to it before passing to lp55xx_write().
>>
> This was a carry over from the original driver. Ln 140.
>
> I am not looking to change existing code functionality.
>
> First pass was just to introduce mc_fw with no disruption or regression
>
> to the base functionality.
Sure thing. Just wanted to mention that for a record.
[...]
>>> - led->cdev.brightness_set_blocking = lp55xx_set_brightness;
>>> - led->cdev.groups = lp55xx_led_groups;
>>> -
>>> - if (pdata->led_config[chan].name) {
>>> - led->cdev.name = pdata->led_config[chan].name;
>>> - } else {
>>> - snprintf(name, sizeof(name), "%s:channel%d",
>>> - pdata->label ? : chip->cl->name, chan);
>>> - led->cdev.name = name;
>>> - }
>>> + if (pdata->led_config[chan].num_colors > 1)
>>> + ret = led_classdev_multicolor_register(dev, &led->mc_cdev);
>>> + else
>>> + ret = led_classdev_register(dev, &led->cdev);
>> Why not devm ?
>
> I will call the devm_ for multicolor but conversion for not MC should be
> done separately
>
> Again not trying to regress original functionality.
Then it's better to switch to devm later. Only then it will
allow to optimize the error paths.
>>> - ret = led_classdev_register(dev, &led->cdev);
>>> if (ret) {
>>> dev_err(dev, "led register err: %d\n", ret);
>>> return ret;
>>> @@ -466,7 +526,6 @@ int lp55xx_register_leds(struct lp55xx_led *led,
>>> struct lp55xx_chip *chip)
>>> dev_err(&chip->cl->dev, "empty brightness configuration\n");
>>> return -EINVAL;
>>> }
>>> -
>>> for (i = 0; i < num_channels; i++) {
>>> /* do not initialize channels that are not connected */
>>> @@ -538,6 +597,39 @@ void lp55xx_unregister_sysfs(struct lp55xx_chip
>>> *chip)
>>> }
>>> EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
>>> +static int lp5xx_parse_channel_child(struct device_node *np,
>>> + struct lp55xx_led_config *cfg,
>>> + int chan_num)
>>> +{
>>> + struct device_node *child;
>>> + int num_colors = 0;
>>> + u32 color_id;
>>> + u32 led_number;
>>> + int ret;
>>> +
>>> + cfg[chan_num].default_trigger =
>>> + of_get_property(np, "linux,default-trigger", NULL);
>>> +
>>> + for_each_child_of_node(np, child) {
>>> + of_property_read_string(child, "chan-name",
>>> + &cfg[chan_num].name);
>>> + of_property_read_u8(child, "led-cur",
>>> + &cfg[chan_num].led_current);
>>> + of_property_read_u8(child, "max-cur",
>>> + &cfg[chan_num].max_current);
>>> + of_property_read_u32(child, "color", &color_id);
>>> + cfg[chan_num].channel_color[num_colors] = color_id;
>>> + cfg[chan_num].available_colors |= 1 << color_id;
>> set_bit(color_id, &cfg[chan_num].available_colors);
>
> Ack
>
>
>>
>>> + ret = of_property_read_u32(child, "reg", &led_number);
>>> + cfg[chan_num].grouped_channels[num_colors] = led_number;
>>> + num_colors++;
>>> + }
>>> +
>>> + cfg->num_colors = num_colors;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device
>>> *dev,
>>> struct device_node *np)
>>> {
>>> @@ -545,6 +637,8 @@ struct lp55xx_platform_data
>>> *lp55xx_of_populate_pdata(struct device *dev,
>>> struct lp55xx_platform_data *pdata;
>>> struct lp55xx_led_config *cfg;
>>> int num_channels;
>>> + int num_chan_children;
>>> + u32 led_number;
>>> int i = 0;
>>> pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
>>> @@ -565,13 +659,26 @@ struct lp55xx_platform_data
>>> *lp55xx_of_populate_pdata(struct device *dev,
>>> pdata->num_channels = num_channels;
>>> for_each_child_of_node(np, child) {
>>> - cfg[i].chan_nr = i;
>>> -
>>> - of_property_read_string(child, "chan-name", &cfg[i].name);
>>> - of_property_read_u8(child, "led-cur", &cfg[i].led_current);
>>> - of_property_read_u8(child, "max-cur", &cfg[i].max_current);
>>> - cfg[i].default_trigger =
>>> - of_get_property(child, "linux,default-trigger", NULL);
>>> + num_chan_children = of_get_child_count(child);
>>> + if (num_chan_children != 0)
>> You're already parsing color. If it is present and equals
>> LED_COLOR_ID_MULTI then we should expect children in the node.
>
> I don't see any parsing for color here but I suppose that I can add that
> now
I thought about that occurrence in lp5xx_parse_channel_child().
[...]
--
Best regards,
Jacek Anaszewski
Jacek
Thanks for looking at the patch
On 7/30/19 4:24 PM, Jacek Anaszewski wrote:
> Hi Dan,
>
> Thank you for the patch I see few things to improve.
>
> On 7/25/19 8:28 PM, Dan Murphy wrote:
>> Introduce the LP5036/30/24/18 RGB LED driver.
>> The difference in these parts are the number of
>> LED outputs where the:
>>
>> LP5036 can control 36 LEDs
>> LP5030 can control 30 LEDs
>> LP5024 can control 24 LEDs
>> LP5018 can control 18 LEDs
>>
>> The device has the ability to group LED output into control banks
>> so that multiple LED banks can be controlled with the same mixing and
>> brightness. Inversely the LEDs can also be controlled independently.
>>
>> Signed-off-by: Dan Murphy <[email protected]>
>> ---
>> drivers/leds/Kconfig | 7 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/leds-lp50xx.c | 789 +++++++++++++++++++++++++++++++++++++
>> 3 files changed, 797 insertions(+)
>> create mode 100644 drivers/leds/leds-lp50xx.c
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index 7f780d5b8490..69c037020f6b 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -350,6 +350,13 @@ config LEDS_LP3952
>> To compile this driver as a module, choose M here: the
>> module will be called leds-lp3952.
>>
>> +config LEDS_LP50XX
>> + tristate "LED Support for TI LP5036/30/24/18 LED driver chip"
>> + depends on LEDS_CLASS && REGMAP_I2C
>> + help
>> + If you say yes here you get support for the Texas Instruments
>> + LP5036, LP5030, LP5024 and LP5018 LED driver.
>> +
>> config LEDS_LP55XX_COMMON
>> tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
>> depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>> index 897b810257dd..438a5499f3ee 100644
>> --- a/drivers/leds/Makefile
>> +++ b/drivers/leds/Makefile
>> @@ -34,6 +34,7 @@ obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
>> obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
>> obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
>> obj-$(CONFIG_LEDS_LP3952) += leds-lp3952.o
>> +obj-$(CONFIG_LEDS_LP50XX) += leds-lp50xx.o
>> obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
>> obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
>> obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
>> diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
>> new file mode 100644
>> index 000000000000..5a32410e4f34
> [...]
>> +static int lp50xx_probe_dt(struct lp50xx *priv)
>> +{
>> + u32 led_strings[LP5036_MAX_LED_MODULES];
>> + struct fwnode_handle *child = NULL;
>> + struct fwnode_handle *led_node = NULL;
>> + struct led_init_data init_data;
>> + struct lp50xx_led *led;
>> + int num_colors;
>> + u32 color_id;
>> + int led_number;
>> + size_t i = 0;
>> + int ret;
>> +
>> + priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev,
>> + "enable", GPIOD_OUT_LOW);
>> + if (IS_ERR(priv->enable_gpio)) {
>> + ret = PTR_ERR(priv->enable_gpio);
>> + dev_err(&priv->client->dev, "Failed to get enable gpio: %d\n",
>> + ret);
>> + return ret;
>> + }
>> +
>> + priv->regulator = devm_regulator_get(&priv->client->dev, "vled");
>> + if (IS_ERR(priv->regulator))
>> + priv->regulator = NULL;
>> +
>> + if (priv->model_id == LP5009)
>> + priv->max_leds = LP5009_MAX_LED_MODULES;
>> + else if (priv->model_id == LP5012)
>> + priv->max_leds = LP5012_MAX_LED_MODULES;
>> + else if (priv->model_id == LP5018)
>> + priv->max_leds = LP5018_MAX_LED_MODULES;
>> + else if (priv->model_id == LP5024)
>> + priv->max_leds = LP5024_MAX_LED_MODULES;
>> + else if (priv->model_id == LP5030)
>> + priv->max_leds = LP5030_MAX_LED_MODULES;
>> + else
>> + priv->max_leds = LP5036_MAX_LED_MODULES;
> You could simplify that by initializing data property of
> lp50xx_leds_match array elements and then call of_match_device.
> You can compare e.g. drivers/leds/leds-pca9532.c
Well I am not using of_ calls in driver anymore.
I would prefer to use device_get_match_data(&client->dev);
>> + device_for_each_child_node(&priv->client->dev, child) {
>> + led = &priv->leds[i];
>> + if (fwnode_property_present(child, "ti,led-bank")) {
>> + ret = fwnode_property_read_u32_array(child,
>> + "ti,led-bank",
>> + NULL, 0);
>> + ret = fwnode_property_read_u32_array(child,
>> + "ti,led-bank",
>> + led_strings,
>> + ret);
>> +
>> + priv->num_of_banked_leds = ARRAY_SIZE(led_strings);
>> +
>> + ret = lp50xx_set_banks(priv, led_strings);
>> + if (ret) {
>> + dev_err(&priv->client->dev,
>> + "Cannot setup banked LEDs\n");
>> + fwnode_handle_put(child);
>> + goto child_out;
>> + }
>> + led->ctrl_bank_enabled = 1;
>> +
>> + } else {
>> + ret = fwnode_property_read_u32(child, "reg",
>> + &led_number);
>> +
>> + led->led_number = led_number;
>> + }
>> + if (ret) {
>> + dev_err(&priv->client->dev,
>> + "led sourcing property missing\n");
>> + fwnode_handle_put(child);
>> + goto child_out;
>> + }
>> +
>> + if (led_number > priv->max_leds) {
>> + dev_err(&priv->client->dev,
>> + "led-sources property is invalid\n");
>> + ret = -EINVAL;
>> + fwnode_handle_put(child);
>> + goto child_out;
>> + }
>> +
>> + init_data.fwnode = child;
>> + init_data.devicename = priv->client->name;
> We'd rather not have devicename in the name of this LED class device
> according to the new LED naming convention. It's not a hot-pluggable
> device, so for properly designed DT we should not encounter name
> clash.
Ok I had this code from the previous led label patchset.
I will fix it
>
>> + init_data.default_label = ":";
> default_label should not be set for new drivers. See the comment in
> the include/linux/leds.h:
>
> /*
> * string to be used for devicename section of LED class device
> * either for label based LED name composition path or for fwnode
> * based when devname_mandatory is true
> */
>
> This is new driver and we should not add any convenience fallbacks
> for label based DT bindings. Label DT property has been marked
> deprecated, so for new drivers it should be deemed non-existent.
>
Same as above
>> +
>> + fwnode_property_read_string(child, "linux,default-trigger",
>> + &led->led_dev.default_trigger);
>> + num_colors = 0;
>> +
>> + fwnode_for_each_child_node(child, led_node) {
>> + ret = fwnode_property_read_u32(led_node, "color",
>> + &color_id);
>> + if (ret)
>> + dev_err(&priv->client->dev,
>> + "Cannot read color\n");
>> +
>> + led->mc_cdev.available_colors |= (1 << color_id);
>> + num_colors++;
>> +
>> + }
>> +
>> + led->priv = priv;
>> + led->mc_cdev.ops = &lp50xx_mc_ops;
>> + led->mc_cdev.num_leds = num_colors;
>> + led->mc_cdev.led_cdev = &led->led_dev;
>> + led->led_dev.brightness_set_blocking = lp50xx_brightness_set;
>> + led->led_dev.brightness_get = lp50xx_brightness_get;
>> + ret = led_classdev_multicolor_register_ext(&priv->client->dev,
>> + &led->mc_cdev,
>> + &init_data);
> Why not devm_* prefixed version?
I did create a devm_ version like you did for flash I will change it over
Dan
>
> [...]
>
Jacek
On 7/29/19 3:50 PM, Jacek Anaszewski wrote:
> Dan,
>
> On 7/25/19 8:28 PM, Dan Murphy wrote:
>> Introduce a multicolor class that groups colored LEDs
>> within a LED node.
>>
>> The framework allows for dynamically setting individual LEDs
>> or setting brightness levels of LEDs and updating them virtually
>> simultaneously.
>>
>> Signed-off-by: Dan Murphy <[email protected]>
>> ---
>> drivers/leds/Kconfig | 10 +
>> drivers/leds/Makefile | 1 +
>> drivers/leds/led-class-multicolor.c | 402 +++++++++++++++++++++++++++
>> include/linux/led-class-multicolor.h | 96 +++++++
>> 4 files changed, 509 insertions(+)
>> create mode 100644 drivers/leds/led-class-multicolor.c
>> create mode 100644 include/linux/led-class-multicolor.h
>>
>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>> index f7a3dd7ecf3d..7f780d5b8490 100644
>> --- a/drivers/leds/Kconfig
>> +++ b/drivers/leds/Kconfig
>> @@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH
>> for the flash related features of a LED device. It can be built
>> as a module.
>>
>> +config LEDS_CLASS_MULTI_COLOR
>> + tristate "LED Mulit Color LED Class Support"
>> + depends on LEDS_CLASS
>> + help
>> + This option enables the multicolor LED sysfs class in /sys/class/leds.
>> + It wraps LED Class and adds multicolor LED specific sysfs attributes
> s/Class/class/
>
> I'll need to fix it in LED flash class entry too.
>
>> + and kernel internal API to it. You'll need this to provide support
>> + for multicolor LEDs that are grouped together. This class is not
>> + intended for single color LEDs. It can be built as a module.
>> +
>> config LEDS_BRIGHTNESS_HW_CHANGED
>> bool "LED Class brightness_hw_changed attribute support"
>> depends on LEDS_CLASS
>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>> index 41fb073a39c1..897b810257dd 100644
>> --- a/drivers/leds/Makefile
>> +++ b/drivers/leds/Makefile
>> @@ -4,6 +4,7 @@
>> obj-$(CONFIG_NEW_LEDS) += led-core.o
>> obj-$(CONFIG_LEDS_CLASS) += led-class.o
>> obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
>> +obj-$(CONFIG_LEDS_CLASS_MULTI_COLOR) += led-class-multicolor.o
>> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>>
>> # LED Platform Drivers
>> diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c
>> new file mode 100644
>> index 000000000000..123443e6d3eb
>> --- /dev/null
>> +++ b/drivers/leds/led-class-multicolor.c
>> @@ -0,0 +1,402 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +// LED Multi Color class interface
>> +// Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com/
>> +
>> +#include <linux/device.h>
>> +#include <linux/init.h>
>> +#include <linux/led-class-multicolor.h>
>> +#include <linux/module.h>
>> +#include <linux/slab.h>
>> +#include <linux/uaccess.h>
>> +
>> +#include "leds.h"
>> +
>> +struct led_classdev_mc_priv {
>> + struct led_classdev_mc *mcled_cdev;
>> +
>> + struct device_attribute max_intensity_attr;
>> + struct device_attribute intensity_attr;
>> + struct device_attribute color_index_attr;
>> +
>> + enum led_brightness max_intensity;
>> + enum led_brightness intensity;
>> + enum led_brightness new_intensity;
>> +
>> + struct list_head list;
>> +
>> + int color_id;
>> + int color_index;
> We need to differentiate both more meaningfully.
> Maybe led_color_id and cluster_color_id?
Thats looks better.
>> +};
>> +static int calculate_brightness(int brightness, int intensity, int max_intensity)
> How about led_mc_calculate_brightness ?
I was trying to keep the function name short for this to keep it the LoC
boundary.
But I can change it.
>
>> +{
>> + if (brightness && intensity && max_intensity)
>> + return brightness * intensity / max_intensity;
>> +
>> + return LED_OFF;
>> +}
>> +
>> +void set_cluster_brightness(struct led_classdev_mc *mcled_cdev,
>> + enum led_brightness brightness, int adj_value[])
> s/set_cluster/led_mc_set_cluster/
>
> And I think adj_value is not telling too much. How about:
>
> s/adj_value/color_intensity/
Ack
...
Ack
>
>> +{
>> + struct led_classdev_mc_data *data = mcled_cdev->data;
>> + struct led_classdev_mc_priv *priv;
>> + int i = 0;
>> +
>> + list_for_each_entry(priv, &data->color_list, list) {
>> + adj_value[i] = calculate_brightness(brightness, priv->intensity,
>> + priv->max_intensity);
>> + i++;
>> + }
>> +
>> + data->cluster_brightness = brightness;
> This property duplicates brightness stored in struct led_classdev and
> thus it is redundant.
Ack
>> +}
>> +
>> +void get_cluster_brightness(struct led_classdev_mc *mcled_cdev,
>> + int brightness_val[])
> s/get_cluster/led_mc_get_cluster/
>
> s/brightness_val/color_intensity/
Ack
Ack
>
>> +{
>> + struct led_classdev_mc_data *data = mcled_cdev->data;
>> + struct led_classdev_mc_priv *priv;
>> + int i = 0;
>> +
>> + list_for_each_entry(priv, &data->color_list, list) {
>> + brightness_val[i] = priv->intensity;
>> + i++;
>> + }
>> +}
>> +
>> +static ssize_t color_index_show(struct device *dev,
>> + struct device_attribute *color_index_attr,
>> + char *buf)
>> +{
>> + struct led_classdev_mc_priv *priv = container_of(color_index_attr,
>> + struct led_classdev_mc_priv,
>> + color_index_attr);
>> +
>> + return sprintf(buf, "%d\n", priv->color_id);
>> +}
>> +
>> +static ssize_t color_mix_store(struct device *dev,
>> + struct device_attribute *color_mix_attr,
>> + const char *buf, size_t size)
>> +{
>> + struct led_classdev_mc_data *data = container_of(color_mix_attr,
>> + struct led_classdev_mc_data,
>> + color_mix_attr);
>> + struct led_classdev_mc *mcled_cdev = data->mcled_cdev;
>> + const struct led_multicolor_ops *ops = mcled_cdev->ops;
>> + struct led_classdev_mc_priv *priv;
>> + unsigned int value[LED_COLOR_ID_MAX];
>> + int adj_brightness;
>> + int nrchars, offset = 0;
>> + unsigned int i;
>> + int ret;
>> +
>> + for (i = 0; i < mcled_cdev->num_leds; i++) {
>> + ret = sscanf(buf + offset, "%i%n", &value[i], &nrchars);
>> + if (ret != 1)
>> + break;
>> +
>> + offset += nrchars;
>> + }
>> +
>> + if (i != mcled_cdev->num_leds) {
> Shouldn't we return error if i != mcled_cdev->num_leds - 1 ?
> How can we know which color the value will be for if there is less
> values passed than the total number of colors in the cluster?
Ok so during my testing if I had the monochrome array as <R G B>
When I wrote only <R G> and no blue I was getting random values in the
array for the
remaining indexes and the blue LED would randomly turn on/off at
different levels.
So if the user passes in less then expected only ids with data will be
written and the other colors will be turned off by the for loop below.
>
>> + for (; i < LED_COLOR_ID_MAX; i++)
>> + value[i] = 0;
> What use case is it for?
See above but this should be
for (; i < mcled_cdev->num_leds; i++)
>
>> + }
>> +
>> + list_for_each_entry(priv, &data->color_list, list) {
>> + if (data->cluster_brightness) {
>> + adj_brightness = calculate_brightness(data->cluster_brightness,
>> + value[priv->color_index],
>> + priv->max_intensity);
>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>> + priv->color_id,
>> + adj_brightness);
>> + if (ret < 0)
>> + goto done;
>> + }
>> +
>> + priv->intensity = value[priv->color_index];
>> + }
> Here we could use just brightness_set op as a single call. We should
> always write all colors as a result of write to color_mix anyway.
I guess what is gained by just passing the array down to the device
driver and having it
parse the array and do the peripheral call?
>
>> +
>> +done:
>> + return size;
>> +}
>> +
>> +static ssize_t intensity_store(struct device *dev,
>> + struct device_attribute *intensity_attr,
>> + const char *buf, size_t size)
>> +{
>> + struct led_classdev_mc_priv *priv = container_of(intensity_attr,
>> + struct led_classdev_mc_priv,
>> + intensity_attr);
>> + struct led_classdev_mc_data *data = priv->mcled_cdev->data;
>> + struct led_classdev *led_cdev = priv->mcled_cdev->led_cdev;
>> + struct led_multicolor_ops *ops = priv->mcled_cdev->ops;
>> + unsigned int adj_value;
>> + unsigned long value;
>> + ssize_t ret;
>> +
>> + mutex_lock(&led_cdev->led_access);
>> +
>> + ret = kstrtoul(buf, 10, &value);
>> + if (ret)
>> + goto unlock;
>> +
>> + if (value > priv->max_intensity) {
>> + ret = -EINVAL;
>> + goto unlock;
>> + }
> We don't fail in this case in LED class, so let's proceed in the same
> way here:
>
> priv->intensity = min(value, priv->max_brightness);
ACK
>> +
>> + priv->new_intensity = value;
>> +
>> + if (data->cluster_brightness) {
>> + adj_value = calculate_brightness(data->cluster_brightness,
>> + priv->new_intensity,
>> + priv->max_intensity);
>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>> + priv->color_id, adj_value);
>> + if (ret < 0) {
>> + priv->new_intensity = priv->intensity;
> This is unnecessary complication. Just write the calculated iout
> intensity.
Not sure what complication you are referring to.
> We need to highlight it in the documentation that exact requested color
> intensity values are written to the hardware only when
> brightness == max_brightness.
But that is not a true statement. Thats not really how it was designed.
>
>> + goto unlock;
>> + }
>> + }
>> +
>> + priv->intensity = priv->new_intensity;
>> + ret = size;
>> +
>> +unlock:
>> + mutex_unlock(&led_cdev->led_access);
>> + return ret;
>> +}
>> +
>> +static ssize_t intensity_show(struct device *dev,
>> + struct device_attribute *intensity_attr,
>> + char *buf)
>> +{
>> + struct led_classdev_mc_priv *priv = container_of(intensity_attr,
>> + struct led_classdev_mc_priv,
>> + intensity_attr);
>> + const struct led_multicolor_ops *ops = priv->mcled_cdev->ops;
>> + int value = 0;
>> +
>> + if (ops->get_color_brightness) {
>> + value = ops->get_color_brightness(priv->mcled_cdev,
>> + priv->color_id);
>> + priv->intensity = value;
>> + } else {
>> + value = priv->intensity;
>> + }
>> +
>> + return sprintf(buf, "%d\n", value);
>> +}
>> +
>> +static ssize_t max_intensity_show(struct device *dev,
>> + struct device_attribute *max_intensity_attr,
>> + char *buf)
>> +{
>> + struct led_classdev_mc_priv *priv = container_of(max_intensity_attr,
>> + struct led_classdev_mc_priv,
>> + max_intensity_attr);
>> +
>> + return sprintf(buf, "%d\n", priv->max_intensity);
>> +}
>> +
>> +static int led_multicolor_init_color(struct led_classdev_mc_data *data,
>> + struct led_classdev_mc *mcled_cdev,
>> + int color_id, int color_index)
>> +{
>> + struct led_classdev *led_cdev = mcled_cdev->led_cdev;
>> + struct led_classdev_mc_priv *mc_priv;
>> + int ret;
>> +
>> + mc_priv = devm_kzalloc(led_cdev->dev, sizeof(*mc_priv), GFP_KERNEL);
>> + if (!mc_priv)
>> + return -ENOMEM;
>> +
>> + mc_priv->color_id = color_id;
>> + mc_priv->mcled_cdev = mcled_cdev;
>> +
>> + data->led_kobj = kobject_create_and_add(led_colors[color_id],
>> + data->color_kobj);
> You should not use kobject API in the drivers. See [0].
I will look at it.
Dan
<snip> no other comments till v5
Jacek
On 7/31/19 2:45 PM, Jacek Anaszewski wrote:
> Dan,
>
> On 7/31/19 8:55 PM, Dan Murphy wrote:
>> Jacek
>>
>> Thanks for looking
> You're welcome.
> [...}
> I don't see any parsing for color here but I suppose that I can add that
> now
> I thought about that occurrence in lp5xx_parse_channel_child().
No that is color of the child subnode. These will be the actual color.
But we can check the color of the child node to the parent node and if
that is MULTI_ID
then process it as a multi color interface.
Dan
> [...]
>
Dan,
On 7/31/19 9:06 PM, Dan Murphy wrote:
> Jacek
>
> On 7/29/19 3:50 PM, Jacek Anaszewski wrote:
>> Dan,
>>
>> On 7/25/19 8:28 PM, Dan Murphy wrote:
>>> Introduce a multicolor class that groups colored LEDs
>>> within a LED node.
>>>
>>> The framework allows for dynamically setting individual LEDs
>>> or setting brightness levels of LEDs and updating them virtually
>>> simultaneously.
>>>
>>> Signed-off-by: Dan Murphy <[email protected]>
>>> ---
>>> drivers/leds/Kconfig | 10 +
>>> drivers/leds/Makefile | 1 +
>>> drivers/leds/led-class-multicolor.c | 402 +++++++++++++++++++++++++++
>>> include/linux/led-class-multicolor.h | 96 +++++++
>>> 4 files changed, 509 insertions(+)
>>> create mode 100644 drivers/leds/led-class-multicolor.c
>>> create mode 100644 include/linux/led-class-multicolor.h
>>>
>>> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
>>> index f7a3dd7ecf3d..7f780d5b8490 100644
>>> --- a/drivers/leds/Kconfig
>>> +++ b/drivers/leds/Kconfig
>>> @@ -30,6 +30,16 @@ config LEDS_CLASS_FLASH
>>> for the flash related features of a LED device. It can be built
>>> as a module.
>>> +config LEDS_CLASS_MULTI_COLOR
>>> + tristate "LED Mulit Color LED Class Support"
>>> + depends on LEDS_CLASS
>>> + help
>>> + This option enables the multicolor LED sysfs class in
>>> /sys/class/leds.
>>> + It wraps LED Class and adds multicolor LED specific sysfs
>>> attributes
>> s/Class/class/
>>
>> I'll need to fix it in LED flash class entry too.
>>
>>> + and kernel internal API to it. You'll need this to provide
>>> support
>>> + for multicolor LEDs that are grouped together. This class is not
>>> + intended for single color LEDs. It can be built as a module.
>>> +
>>> config LEDS_BRIGHTNESS_HW_CHANGED
>>> bool "LED Class brightness_hw_changed attribute support"
>>> depends on LEDS_CLASS
>>> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
>>> index 41fb073a39c1..897b810257dd 100644
>>> --- a/drivers/leds/Makefile
>>> +++ b/drivers/leds/Makefile
>>> @@ -4,6 +4,7 @@
>>> obj-$(CONFIG_NEW_LEDS) += led-core.o
>>> obj-$(CONFIG_LEDS_CLASS) += led-class.o
>>> obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
>>> +obj-$(CONFIG_LEDS_CLASS_MULTI_COLOR) += led-class-multicolor.o
>>> obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
>>> # LED Platform Drivers
>>> diff --git a/drivers/leds/led-class-multicolor.c
>>> b/drivers/leds/led-class-multicolor.c
>>> new file mode 100644
>>> index 000000000000..123443e6d3eb
>>> --- /dev/null
>>> +++ b/drivers/leds/led-class-multicolor.c
>>> @@ -0,0 +1,402 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +// LED Multi Color class interface
>>> +// Copyright (C) 2019 Texas Instruments Incorporated -
>>> http://www.ti.com/
>>> +
>>> +#include <linux/device.h>
>>> +#include <linux/init.h>
>>> +#include <linux/led-class-multicolor.h>
>>> +#include <linux/module.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/uaccess.h>
>>> +
>>> +#include "leds.h"
>>> +
>>> +struct led_classdev_mc_priv {
>>> + struct led_classdev_mc *mcled_cdev;
>>> +
>>> + struct device_attribute max_intensity_attr;
>>> + struct device_attribute intensity_attr;
>>> + struct device_attribute color_index_attr;
>>> +
>>> + enum led_brightness max_intensity;
>>> + enum led_brightness intensity;
>>> + enum led_brightness new_intensity;
>>> +
>>> + struct list_head list;
>>> +
>>> + int color_id;
>>> + int color_index;
>> We need to differentiate both more meaningfully.
>> Maybe led_color_id and cluster_color_id?
>
> Thats looks better.
>
Looking at it now I had to wonder what I had on mind.
So maybe even better shot:
int color_id // so as original
int color_seq_pos // i.e. position in the color_mix sequence
[...]
>>> +static ssize_t color_mix_store(struct device *dev,
>>> + struct device_attribute *color_mix_attr,
>>> + const char *buf, size_t size)
>>> +{
>>> + struct led_classdev_mc_data *data = container_of(color_mix_attr,
>>> + struct led_classdev_mc_data,
>>> + color_mix_attr);
>>> + struct led_classdev_mc *mcled_cdev = data->mcled_cdev;
>>> + const struct led_multicolor_ops *ops = mcled_cdev->ops;
>>> + struct led_classdev_mc_priv *priv;
>>> + unsigned int value[LED_COLOR_ID_MAX];
>>> + int adj_brightness;
>>> + int nrchars, offset = 0;
>>> + unsigned int i;
>>> + int ret;
>>> +
>>> + for (i = 0; i < mcled_cdev->num_leds; i++) {
>>> + ret = sscanf(buf + offset, "%i%n", &value[i], &nrchars);
>>> + if (ret != 1)
>>> + break;
>>> +
>>> + offset += nrchars;
>>> + }
>>> +
>>> + if (i != mcled_cdev->num_leds) {
>> Shouldn't we return error if i != mcled_cdev->num_leds - 1 ?
>> How can we know which color the value will be for if there is less
>> values passed than the total number of colors in the cluster?
>
> Ok so during my testing if I had the monochrome array as <R G B>
>
> When I wrote only <R G> and no blue I was getting random values in the
> array for the
>
> remaining indexes and the blue LED would randomly turn on/off at
> different levels.
>
> So if the user passes in less then expected only ids with data will be
> written and the other colors will be turned off by the for loop below.
From what I see it will lead to wrong mapping of given color to the
value array element in the following case:
echo "<green> <blue>" > color_mix
Then green intensity will be assigned to value[0] (expects red) and blue
to value[1] (expects green). Unless I don't get something.
Your ABI documentation doesn't mention any way to redefine the color_id
returned by <color>/color_id file. And that is good.
>
>>
>>> + for (; i < LED_COLOR_ID_MAX; i++)
>>> + value[i] = 0;
>> What use case is it for?
>
> See above but this should be
>
> for (; i < mcled_cdev->num_leds; i++)
>
>
>>
>>> + }
>>> +
>>> + list_for_each_entry(priv, &data->color_list, list) {
>>> + if (data->cluster_brightness) {
>>> + adj_brightness =
>>> calculate_brightness(data->cluster_brightness,
>>> + value[priv->color_index],
>>> + priv->max_intensity);
>>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>>> + priv->color_id,
>>> + adj_brightness);
>>> + if (ret < 0)
>>> + goto done;
>>> + }
>>> +
>>> + priv->intensity = value[priv->color_index];
>>> + }
>> Here we could use just brightness_set op as a single call. We should
>> always write all colors as a result of write to color_mix anyway.
>
> I guess what is gained by just passing the array down to the device
> driver and having it
>
> parse the array and do the peripheral call?
Those array values would not be directly written to the device,
but used for calculating the actual iout intensities. Driver
will just have to call calculate_brightness() (sticking to the naming
from this patch) and write the results calculated basing on brightness
and max_brightness.
>
[...]
>
>>> +
>>> + priv->new_intensity = value;
>>> +
>>> + if (data->cluster_brightness) {
>>> + adj_value = calculate_brightness(data->cluster_brightness,
>>> + priv->new_intensity,
>>> + priv->max_intensity);
>>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>>> + priv->color_id, adj_value);
>>> + if (ret < 0) {
>>> + priv->new_intensity = priv->intensity;
>> This is unnecessary complication. Just write the calculated iout
>> intensity.
>
> Not sure what complication you are referring to.
The whole need for new_intensity and cluster_brightness, and then
bringing back old intensity value on set_color_brightness() failure.
>
>> We need to highlight it in the documentation that exact requested color
>> intensity values are written to the hardware only when
>> brightness == max_brightness.
>
> But that is not a true statement. Thats not really how it was designed.
But it probably should be. It would simplify the design.
So my idea is like I previously described the way I had first understood
this design:
The colors set under colors directory don't reflect the iout
intensities, but are only used for calculating them, basing on the
brightness and max_intensity values.
Effectively, after changing the colors/<color>/intensity the global
(legacy monochrome) brightness value will be still valid, since iout
color will be recalculated basing on it and the new color intensity.
--
Best regards,
Jacek Anaszewski
Jacek
On 7/31/19 3:44 PM, Jacek Anaszewski wrote:
> Dan,
>
[...]
> + for (i = 0; i < mcled_cdev->num_leds; i++) {
> + ret = sscanf(buf + offset, "%i%n", &value[i], &nrchars);
> + if (ret != 1)
> + break;
> +
> + offset += nrchars;
> + }
> +
> + if (i != mcled_cdev->num_leds) {
>>> Shouldn't we return error if i != mcled_cdev->num_leds - 1 ?
>>> How can we know which color the value will be for if there is less
>>> values passed than the total number of colors in the cluster?
>> Ok so during my testing if I had the monochrome array as <R G B>
>>
>> When I wrote only <R G> and no blue I was getting random values in the
>> array for the
>>
>> remaining indexes and the blue LED would randomly turn on/off at
>> different levels.
>>
>> So if the user passes in less then expected only ids with data will be
>> written and the other colors will be turned off by the for loop below.
> From what I see it will lead to wrong mapping of given color to the
> value array element in the following case:
>
> echo "<green> <blue>" > color_mix
>
> Then green intensity will be assigned to value[0] (expects red) and blue
> to value[1] (expects green). Unless I don't get something.
> Your ABI documentation doesn't mention any way to redefine the color_id
> returned by <color>/color_id file. And that is good.
>
This is exactly the issue I had previously brought up. The user would
need to
write all leading colors with a value, even if 0, to correctly set the
target LEDs.
We can protect against the trailing colors but not leading colors.
The expectation is that the user space would read the color_id from the
file and align
the array accordingly. This is also why I exposed the intensity under
the color so if the
user wanted to not use color_mix file they can update the intensity per
LED color.
>>>> + for (; i < LED_COLOR_ID_MAX; i++)
>>>> + value[i] = 0;
>>> What use case is it for?
>> See above but this should be
>>
>> for (; i < mcled_cdev->num_leds; i++)
>>
I might be able to eliminate this loop by initializing the array to 0.
>>>> + }
>>>> +
>>>> + list_for_each_entry(priv, &data->color_list, list) {
>>>> + if (data->cluster_brightness) {
>>>> + adj_brightness =
>>>> calculate_brightness(data->cluster_brightness,
>>>> + value[priv->color_index],
>>>> + priv->max_intensity);
>>>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>>>> + priv->color_id,
>>>> + adj_brightness);
>>>> + if (ret < 0)
>>>> + goto done;
>>>> + }
>>>> +
>>>> + priv->intensity = value[priv->color_index];
>>>> + }
>>> Here we could use just brightness_set op as a single call. We should
>>> always write all colors as a result of write to color_mix anyway.
>> I guess what is gained by just passing the array down to the device
>> driver and having it
>>
>> parse the array and do the peripheral call?
> Those array values would not be directly written to the device,
> but used for calculating the actual iout intensities. Driver
> will just have to call calculate_brightness() (sticking to the naming
> from this patch) and write the results calculated basing on brightness
> and max_brightness.
I would expect that we would do the same behavior for the color_mix file
then.
> [...]
>>>> +
>>>> + priv->new_intensity = value;
>>>> +
>>>> + if (data->cluster_brightness) {
>>>> + adj_value = calculate_brightness(data->cluster_brightness,
>>>> + priv->new_intensity,
>>>> + priv->max_intensity);
>>>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>>>> + priv->color_id, adj_value);
>>>> + if (ret < 0) {
>>>> + priv->new_intensity = priv->intensity;
>>> This is unnecessary complication. Just write the calculated iout
>>> intensity.
>> Not sure what complication you are referring to.
> The whole need for new_intensity and cluster_brightness, and then
> bringing back old intensity value on set_color_brightness() failure.
OK
>
>>> We need to highlight it in the documentation that exact requested color
>>> intensity values are written to the hardware only when
>>> brightness == max_brightness.
>> But that is not a true statement. Thats not really how it was designed.
> But it probably should be. It would simplify the design.
>
> So my idea is like I previously described the way I had first understood
> this design:
>
> The colors set under colors directory don't reflect the iout
> intensities, but are only used for calculating them, basing on the
> brightness and max_intensity values.
>
> Effectively, after changing the colors/<color>/intensity the global
> (legacy monochrome) brightness value will be still valid, since iout
> color will be recalculated basing on it and the new color intensity.
>
OK. This this would remove the ops from the driver as it is no longer
needed.
The color_mix file will work the same way.
What is the trigger then to update the LEDs?
We cannot write the same brightness value to trigger as the class blocks
calling down
to the driver if brightness_in == brightness_current.
Dan
Dan,
On 8/2/19 4:14 PM, Dan Murphy wrote:
> Jacek
>
> On 7/31/19 3:44 PM, Jacek Anaszewski wrote:
>> Dan,
>>
> [...]
>> + for (i = 0; i < mcled_cdev->num_leds; i++) {
>> + ret = sscanf(buf + offset, "%i%n", &value[i], &nrchars);
>> + if (ret != 1)
>> + break;
>> +
>> + offset += nrchars;
>> + }
>> +
>> + if (i != mcled_cdev->num_leds) {
>>>> Shouldn't we return error if i != mcled_cdev->num_leds - 1 ?
>>>> How can we know which color the value will be for if there is less
>>>> values passed than the total number of colors in the cluster?
>>> Ok so during my testing if I had the monochrome array as <R G B>
>>>
>>> When I wrote only <R G> and no blue I was getting random values in the
>>> array for the
>>>
>>> remaining indexes and the blue LED would randomly turn on/off at
>>> different levels.
>>>
>>> So if the user passes in less then expected only ids with data will be
>>> written and the other colors will be turned off by the for loop below.
>> From what I see it will lead to wrong mapping of given color to the
>> value array element in the following case:
>>
>> echo "<green> <blue>" > color_mix
>>
>> Then green intensity will be assigned to value[0] (expects red) and blue
>> to value[1] (expects green). Unless I don't get something.
>> Your ABI documentation doesn't mention any way to redefine the color_id
>> returned by <color>/color_id file. And that is good.
>>
> This is exactly the issue I had previously brought up. The user would
> need to
>
> write all leading colors with a value, even if 0, to correctly set the
> target LEDs.
>
> We can protect against the trailing colors but not leading colors.
>
> The expectation is that the user space would read the color_id from the
> file and align
>
> the array accordingly. This is also why I exposed the intensity under
> the color so if the
>
> user wanted to not use color_mix file they can update the intensity per
> LED color.
>
I can't see the problem. Just require user to _always_ pass the number
of values equal to the number of colors under LED multicolor class
device. No more, no less. And fail it this condition is not met.
>>>>> + for (; i < LED_COLOR_ID_MAX; i++)
>>>>> + value[i] = 0;
>>>> What use case is it for?
>>> See above but this should be
>>>
>>> for (; i < mcled_cdev->num_leds; i++)
>>>
> I might be able to eliminate this loop by initializing the array to 0.
>
>
>>>>> + }
>>>>> +
>>>>> + list_for_each_entry(priv, &data->color_list, list) {
>>>>> + if (data->cluster_brightness) {
>>>>> + adj_brightness =
>>>>> calculate_brightness(data->cluster_brightness,
>>>>> + value[priv->color_index],
>>>>> + priv->max_intensity);
>>>>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>>>>> + priv->color_id,
>>>>> + adj_brightness);
>>>>> + if (ret < 0)
>>>>> + goto done;
>>>>> + }
>>>>> +
>>>>> + priv->intensity = value[priv->color_index];
>>>>> + }
>>>> Here we could use just brightness_set op as a single call. We should
>>>> always write all colors as a result of write to color_mix anyway.
>>> I guess what is gained by just passing the array down to the device
>>> driver and having it
>>>
>>> parse the array and do the peripheral call?
>> Those array values would not be directly written to the device,
>> but used for calculating the actual iout intensities. Driver
>> will just have to call calculate_brightness() (sticking to the naming
>> from this patch) and write the results calculated basing on brightness
>> and max_brightness.
>
> I would expect that we would do the same behavior for the color_mix file
> then.
Yes, this is what I meant while mentioning use of brightness_set op
for that. The final effect will be always the same - write of the
altered iout intensity registers. What will differ will be factors
of the equation that calculates iout intensity: for the call
originating from color_mix file the changing factor will be color
intensity and for the call originating from brightness file
it will be brightness. In both cases on the driver side we need
to call the same function to calculate iout values using the same
equation.
>
>> [...]
>>>>> +
>>>>> + priv->new_intensity = value;
>>>>> +
>>>>> + if (data->cluster_brightness) {
>>>>> + adj_value = calculate_brightness(data->cluster_brightness,
>>>>> + priv->new_intensity,
>>>>> + priv->max_intensity);
>>>>> + ret = ops->set_color_brightness(priv->mcled_cdev,
>>>>> + priv->color_id, adj_value);
>>>>> + if (ret < 0) {
>>>>> + priv->new_intensity = priv->intensity;
>>>> This is unnecessary complication. Just write the calculated iout
>>>> intensity.
>>> Not sure what complication you are referring to.
>> The whole need for new_intensity and cluster_brightness, and then
>> bringing back old intensity value on set_color_brightness() failure.
>
> OK
>
>>
>>>> We need to highlight it in the documentation that exact requested color
>>>> intensity values are written to the hardware only when
>>>> brightness == max_brightness.
>>> But that is not a true statement. Thats not really how it was designed.
>> But it probably should be. It would simplify the design.
>>
>> So my idea is like I previously described the way I had first understood
>> this design:
>>
>> The colors set under colors directory don't reflect the iout
>> intensities, but are only used for calculating them, basing on the
>> brightness and max_intensity values.
>>
>> Effectively, after changing the colors/<color>/intensity the global
>> (legacy monochrome) brightness value will be still valid, since iout
>> color will be recalculated basing on it and the new color intensity.
>>
> OK. This this would remove the ops from the driver as it is no longer
> needed.
>
> The color_mix file will work the same way.
I've been pondering over the need for color_set, but it should be
indeed not needed too. Write to <color>/intensity file will also
call brightness_set - it will be comparing anyway old intensities
and new ones and will write only the affected register.
> What is the trigger then to update the LEDs?
>
> We cannot write the same brightness value to trigger as the class blocks
> calling down
>
> to the driver if brightness_in == brightness_current.
All files (intensity, color_mix and brightness) will use the same
brightness_set op for changing the iout values.
--
Best regards,
Jacek Anaszewski
Jacek
OK finally getting back to this.
On 7/29/19 3:45 PM, Jacek Anaszewski wrote:
> Hi Dan,
>
> Thank you for the v4.
>
> I have a bunch of comments below. Please take a look.
>
> On 7/25/19 8:28 PM, Dan Murphy wrote:
>> Add a documentation of LED Multicolor LED class specific
>> sysfs attributes.
>>
>> Signed-off-by: Dan Murphy<[email protected]>
>> ---
>> .../ABI/testing/sysfs-class-led-multicolor | 67 +++++++++++++++++++
>> 1 file changed, 67 insertions(+)
>> create mode 100644 Documentation/ABI/testing/sysfs-class-led-multicolor
>>
>> diff --git a/Documentation/ABI/testing/sysfs-class-led-multicolor b/Documentation/ABI/testing/sysfs-class-led-multicolor
>> new file mode 100644
>> index 000000000000..59839f0eae76
>> --- /dev/null
>> +++ b/Documentation/ABI/testing/sysfs-class-led-multicolor
>> @@ -0,0 +1,67 @@
>> +What: /sys/class/leds/<led>/brightness
>> +Date: Sept 2019
>> +KernelVersion: TBD
>> +Contact: Dan Murphy<[email protected]>
>> +Description: read/write
>> + The multicolor class will redirect the device drivers call back
>> + function for brightness control to the multicolor class
>> + brightness control function.
>> +
>> + Writing to this file will update all LEDs within the group to a
>> + calculated percentage of what each color LED in the group is set
>> + to. Please refer to the leds-class-multicolor.txt in the
>> + Documentation directory for a complete description.
> Instead of redirecting the reader to led-class-multicolor.txt I'd prefer
> to have at least the formula to calculate the colors laid out here.
> Aside of that - it is more helpful to have a full path to the referenced
> file.
Ack
>> +
>> + The value of the color is from 0 to
>> + /sys/class/leds/<led>/max_brightness.
>> +
>> +What: /sys/class/leds/<led>/colors/color_mix
>> +Date: Sept 2019
>> +KernelVersion: TBD
>> +Contact: Dan Murphy<[email protected]>
>> +Description: read/write
>> + The color_mix file allows writing all registered multicolor LEDs
>> + virtually at the same time. The value(s) written to this file
> I'd drop parentheses form "value(s)". Multi color LED class device is
> supposed to always have more then one LED. And if I understand it
> correctly we have to pass intensities of all colors supported by LED
> multicolor class device here, even we're changing single one.
Yes that is true.
>> + contain the intensity values for each multicolor LED within
>> + the colors directory. The color indexes are reported in the
>> + color_id file as defined in this document.
> This is a bit misleading. It sounds as if single color_id file would be
> reporting more than one index.
>
>> + Please refer to the leds-class-multicolor.txt in the
>> + Documentation directory for a complete description.
> Here, similarly as for brightness, I would prefer to have complete
> documentation of this file.
>
> How about:
>
> The values written to this file should contain the intensity values of
> each multicolor LED within the colors directory. The index of given
> color is reported by the color_id file present in colors/<color>
> directory. The index determines the position in the sequence of
> intensities on which the related intensity should be passed to this
> file.
>
> And here we could have the examples from leds-class-multicolor.txt.
I prefer to keep the examples in the leds-class-multicolor.rst.
This is an ABI document not a document describing the code.
I updated the doc to what you have above.
>> +
>> +What: /sys/class/leds/<led>/colors/<led_color>/color_id
>> +Date: Sept 2019
>> +KernelVersion: TBD
>> +Contact: Dan Murphy<[email protected]>
>> +Description: read only
>> + This file when read will return the index of the color in the
>> + color_mix.
>> + Please refer to the leds-class-multicolor.txt in the
>> + Documentation directory for a complete description.
>> +
>> +What: /sys/class/leds/<led>/colors/<led_color>/intensity
>> +Date: Sept 2019
>> +KernelVersion: TBD
>> +Contact: Dan Murphy<[email protected]>
>> +Description: read/write
>> + The led_color directory is dynamically created based on the
>> + colors defined by the registrar of the class.
>> + The led_color can be but not limited to red, green, blue,
>> + white, amber, yellow and violet. Drivers can also declare a
> Instead of this vague sentence about the available colors I propose to
> maintain the list of supported colors in leds-class.rst or in a separate
> file and keep it in sync with the led_colors array. Then we could refer
> to that file here.
I would rather point to the file that contains the colors. This way we
don't have added documentation
maintenance to add a new color.
>
>> + LED color for presentation. There is one directory per color
> I'd not let drivers define their custom colors. It would entail issues
> related to lack of generic LED_COLOR_ID and DT parsing failure.
Ack
Dan
>
>> + presented. The brightness file is created under each
>> + led_color directory and controls the individual LED color
>> + setting.
>> +
>> + The value of the color is from 0 to
>> + /sys/class/leds/<led>/colors/<led_color>/max_intensity.
>> +
>> +What: /sys/class/leds/<led>/colors/<led_color>/max_intensity
>> +Date: Sept 2019
>> +KernelVersion: TBD
>> +Contact: Dan Murphy<[email protected]>
>> +Description: read only
>> + Maximum intensity level for the LED color, default is
>> + 255 (LED_FULL).
>> +
>> + If the LED does not support different intensity levels, this
>> + should be 1.
>>