2021-11-09 19:15:04

by Florian Eckert

[permalink] [raw]
Subject: [PATCH 0/2] leds: Add KTD20xx RGB LEDs driver from Kinetic

Introduce the KTD2061/58/59/60 RGB LEDs driver. The difference in these
parts are the address number on the I2C bus the device is listen on.

All KT20xx device could control up to 12 LEDs. The chip can be operated
in two variants.

Florian Eckert (2):
leds: ktd20xx: Add the KTD20xx family of the RGB LEDs driver from
Kinetic
dt: bindings: KTD20xx: Introduce the ktd20xx family of RGB drivers

.../bindings/leds/leds-ktd20xx.yaml | 123 +++
MAINTAINERS | 7 +
drivers/leds/Kconfig | 13 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-ktd20xx.c | 801 ++++++++++++++++++
5 files changed, 945 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-ktd20xx.yaml
create mode 100644 drivers/leds/leds-ktd20xx.c

--
2.20.1


2021-11-09 19:16:13

by Florian Eckert

[permalink] [raw]
Subject: [PATCH 2/2] dt: bindings: KTD20xx: Introduce the ktd20xx family of RGB drivers

Introduce the bindings for the Kinetic KTD2061/58/59/60RGB LED device
driver. The KTD20xx can control RGB LEDs individually or as part of a
control bank group.

Signed-off-by: Florian Eckert <[email protected]>
---
.../bindings/leds/leds-ktd20xx.yaml | 123 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 124 insertions(+)
create mode 100644 Documentation/devicetree/bindings/leds/leds-ktd20xx.yaml

diff --git a/Documentation/devicetree/bindings/leds/leds-ktd20xx.yaml b/Documentation/devicetree/bindings/leds/leds-ktd20xx.yaml
new file mode 100644
index 000000000000..b10b5fd507db
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-ktd20xx.yaml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/leds-ktd20xx.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: LED driver for KTD20xx RGB LED from Kinetic.
+
+maintainers:
+ - Florian Eckert <[email protected]>
+
+description: |
+ The KTD20XX 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 I2C address number the device is
+ listen on.
+
+properties:
+ compatible:
+ enum:
+ - kinetic,ktd20xx
+
+ reg:
+ maxItems: 1
+ description:
+ I2C slave address
+ ktd2061/58/59/60 0x68 0x69 0x6A 0x6B
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ 'kinetic,color-current0':
+ description:
+ Specifies the current selection for the RGB color0.
+ Value 1 must be the current value for the color red.
+ Value 2 must be the current value for the color green.
+ Value 3 must be the current value for the color blue.
+ The current setting range is from 0mA to 24mA with 125uA steps.
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ items:
+ - minItems: 3
+ - maxItems: 3
+
+ 'kinetic,color-current1':
+ description:
+ Specifies the current selection for the RGB color0.
+ Value 1 must be the current value for the color red.
+ Value 2 must be the current value for the color green.
+ Value 3 must be the current value for the color blue.
+ The current setting range is from 0mA to 24mA with 125uA steps.
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ items:
+ - minItems: 3
+ - maxItems: 3
+
+patternProperties:
+ '^multi-led@[0-9a-f]$':
+ type: object
+ allOf:
+ - $ref: leds-class-multicolor.yaml#
+ properties:
+ reg:
+ minItems: 1
+ maxItems: 12
+ description:
+ This property denotes the LED module number(s) that is used on the
+ for the child node.
+ 'kinetic,color-selection':
+ description:
+ Specifies the color selection for this LED.
+ Value 1 selects the color register for color red.
+ Value 2 selects the color register for color green.
+ Value 3 selects the color register for color blue.
+ The value can be either 0 or 1. If 0, the current for the color
+ from color register 0 is used. If 1, the current for the color
+ from color register 1 is used.
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ items:
+ - minItems: 3
+ - maxItems: 3
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/leds/common.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ led-controller@14 {
+ compatible = "ti,lp5009";
+ reg = <0x14>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ color-current0 = [ 00 00 00 ] // Current for RGB is 0mA
+ color-current1 = [ 28 28 28 ] // Current for RGB is 5mA
+
+ multi-led@0 {
+ reg = <0x0>;
+ color = <LED_COLOR_ID_RGB>;
+ function = LED_FUNCTION_CHARGING;
+ kinetic,color-selection = [ 00 01 00 ]; // Red=0mA Green=5mA Blue=0mA
+ };
+
+ multi-led@2 {
+ reg = <0x2>;
+ color = <LED_COLOR_ID_RGB>;
+ function = LED_FUNCTION_STANDBY;
+ };
+ };
+ };
+
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 736d564f7e93..125bae48c2d1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10607,6 +10607,7 @@ KTD20XX LED CONTROLLER DRIVER
M: Florian Eckert <[email protected]>
L: [email protected]
S: Maintained
+F: Documentation/devicetree/bindings/leds/leds-ktd20xx.yaml
F: drivers/leds/leds-ktd20xx.c

KTEST
--
2.20.1

2021-11-09 20:10:00

by Florian Eckert

[permalink] [raw]
Subject: [PATCH 1/2] leds: ktd20xx: Add the KTD20xx family of the RGB LEDs driver from Kinetic

Introduce the KTD2061/58/59/60 RGB LEDs driver. The difference in these
parts are the address number on the I2C bus the device is listen on.

All KT20xx device could control up to 12 LEDs. The chip can be operated
in two variants.

Variant 1:
The device has the ability to group LED outputs into two banks so that
the two LED banks can be controlled with the same color. This could not
be done via the LEDs 'sysfs' entry because of the limitation on the color
register count. The color of the two banks can be configured via device
'sysfs' entry for all LEDs at once [current_color0|current_color1].
Which color the LED is to be used can be set via the 'sysfs' of the
individual LEDs via the 'multi_intensity' file. Valid values for the
colors (RGB) are 0 | 1. The value 0 selects the color register 0 and the
value 1 selects the color register 1.

Variant 2:
The device can also set the LED color independently. Since the chip only
has two color registers, but we want to control the 12 LEDs
independently via the 'led-class-multicolour' sysfs entry,
the full RGB color depth cannot be used. Due to this limitation, only 7
colors and the color black (off) can be set. To use this mode the color
registers must be preset via the device tree or the device 'sysfs'. The
color registers 0 must be preset with 0x00 (Red=0x00 Green=0x00 Blue=0x00).
The color register1 should be preset all with the same value. This value
depends on which light intensity is to be used in the setup.

Signed-off-by: Florian Eckert <[email protected]>
---
MAINTAINERS | 6 +
drivers/leds/Kconfig | 13 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-ktd20xx.c | 801 ++++++++++++++++++++++++++++++++++++
4 files changed, 821 insertions(+)
create mode 100644 drivers/leds/leds-ktd20xx.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 9096c64d8d09..736d564f7e93 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -10603,6 +10603,12 @@ S: Maintained
F: Documentation/devicetree/bindings/leds/backlight/kinetic,ktd253.yaml
F: drivers/video/backlight/ktd253-backlight.c

+KTD20XX LED CONTROLLER DRIVER
+M: Florian Eckert <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/leds/leds-ktd20xx.c
+
KTEST
M: Steven Rostedt <[email protected]>
M: John Hawley <[email protected]>
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index ed800f5da7d8..b313b5c6a445 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -157,6 +157,19 @@ config LEDS_EL15203000
To compile this driver as a module, choose M here: the module
will be called leds-el15203000.

+config LEDS_KTD20XX
+ tristate "LED Support for KTD2061/58/59/60 LED driver chip"
+ depends on LEDS_CLASS && I2C
+ depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR
+ depends on OF
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the Kinetic
+ KTD2061, KTD2058, KTD2059 and KTD2060 LED driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-ktd20xx.
+
config LEDS_TURRIS_OMNIA
tristate "LED support for CZ.NIC's Turris Omnia"
depends on LEDS_CLASS_MULTICOLOR
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index c636ec069612..7004ec953c87 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_LEDS_IP30) += leds-ip30.o
obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o
obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
+obj-${CONFIG_LEDS_KTD20XX} += leds-ktd20xx.o
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
obj-$(CONFIG_LEDS_LM3532) += leds-lm3532.o
obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
diff --git a/drivers/leds/leds-ktd20xx.c b/drivers/leds/leds-ktd20xx.c
new file mode 100644
index 000000000000..ffe125c2f7d9
--- /dev/null
+++ b/drivers/leds/leds-ktd20xx.c
@@ -0,0 +1,801 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LEDs driver for the Kinetic KDT20xx device
+ *
+ * Copyright (C) 2021 TDT AG
+ * Florian Eckert <[email protected]>
+ */
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+
+/* I2C Register Map */
+#define KTD20XX_ID 0x00
+#define KTD20XX_MONITOR 0x01
+#define KTD20XX_CONTROL 0x02
+
+/* Color0 Configuration Registers */
+#define KTD20XX_IRED0 0x03
+#define KTD20XX_IGRN0 0x04
+#define KTD20XX_IBLU0 0x05
+
+/* Color1 Configuration Registers */
+#define KTD20XX_IRED1 0x06
+#define KTD20XX_IGRN1 0x07
+#define KTD20XX_IBLU1 0x08
+
+/* Selection Configuration Register */
+#define KTD20XX_ISELA12 0x09
+#define KTD20XX_ISELA34 0x0A
+#define KTD20XX_ISELB12 0x0B
+#define KTD20XX_ISELB34 0x0C
+#define KTD20XX_ISELC12 0x0D
+#define KTD20XX_ISELC34 0x0E
+
+#define KTD20XX_MAX_LEDS 12
+#define KTD20XX_LED_CHANNELS 3
+
+enum ktd20xx_led_number {
+ /* ISELA12 */
+ RGB_A1,
+ RGB_A2,
+ /* ISELA34 */
+ RGB_A3,
+ RGB_A4,
+ /* ISELB12 */
+ RGB_B1,
+ RGB_B2,
+ /* ISELB34 */
+ RGB_B3,
+ RGB_B4,
+ /* ISELC12 */
+ RGB_C1,
+ RGB_C2,
+ /* ISELC34 */
+ RGB_C3,
+ RGB_C4,
+};
+
+enum ktd20xx_rgb {
+ RED,
+ GRN,
+ BLU,
+};
+
+enum ktd20xx_control_mode {
+ CONTROL_MODE_OFF,
+ CONTROL_MODE_NIGHT,
+ CONTROL_MODE_NORMAL,
+ CONTROL_MODE_RESET,
+};
+
+static const struct reg_default ktd20xx_reg_defs[] = {
+ /* Color0 Configuration Registers */
+ {KTD20XX_IRED0, 0x28},
+ {KTD20XX_IGRN0, 0x28},
+ {KTD20XX_IBLU0, 0x28},
+ /* Color1 Configuration Registers */
+ {KTD20XX_IRED1, 0x60},
+ {KTD20XX_IGRN1, 0x60},
+ {KTD20XX_IBLU1, 0x60},
+ /* Selection Configuration Register */
+ {KTD20XX_ISELA12, 0x00},
+ {KTD20XX_ISELA34, 0x00},
+ {KTD20XX_ISELB12, 0x00},
+ {KTD20XX_ISELB34, 0x00},
+ {KTD20XX_ISELC12, 0x00},
+ {KTD20XX_ISELC34, 0x00}
+};
+
+/* Chip values */
+static const struct reg_field kt20xx_control_mode = REG_FIELD(KTD20XX_CONTROL, 6, 7);
+static const struct reg_field kt20xx_faderate = REG_FIELD(KTD20XX_CONTROL, 0, 2);
+static const struct reg_field kt20xx_vendor = REG_FIELD(KTD20XX_ID, 5, 7);
+static const struct reg_field kt20xx_chip_id = REG_FIELD(KTD20XX_ID, 0, 4);
+static const struct reg_field kt20xx_chip_rev = REG_FIELD(KTD20XX_MONITOR, 4, 7);
+
+/* ISELA1 and ISELA2 */
+static const struct reg_field kt20xx_a1_select = REG_FIELD(KTD20XX_ISELA12, 4, 6);
+static const struct reg_field kt20xx_a1_enable = REG_FIELD(KTD20XX_ISELA12, 7, 7);
+static const struct reg_field kt20xx_a2_select = REG_FIELD(KTD20XX_ISELA12, 0, 2);
+static const struct reg_field kt20xx_a2_enable = REG_FIELD(KTD20XX_ISELA12, 3, 3);
+
+/* ISELA3 and ISELA4 */
+static const struct reg_field kt20xx_a3_select = REG_FIELD(KTD20XX_ISELA34, 4, 6);
+static const struct reg_field kt20xx_a3_enable = REG_FIELD(KTD20XX_ISELA34, 7, 7);
+static const struct reg_field kt20xx_a4_select = REG_FIELD(KTD20XX_ISELA34, 0, 2);
+static const struct reg_field kt20xx_a4_enable = REG_FIELD(KTD20XX_ISELA34, 3, 3);
+
+/* ISELB1 and ISELB2 */
+static const struct reg_field kt20xx_b1_select = REG_FIELD(KTD20XX_ISELB12, 4, 6);
+static const struct reg_field kt20xx_b1_enable = REG_FIELD(KTD20XX_ISELB12, 7, 7);
+static const struct reg_field kt20xx_b2_select = REG_FIELD(KTD20XX_ISELB12, 0, 2);
+static const struct reg_field kt20xx_b2_enable = REG_FIELD(KTD20XX_ISELB12, 3, 3);
+
+/* ISELB3 and ISELB4 */
+static const struct reg_field kt20xx_b3_select = REG_FIELD(KTD20XX_ISELB34, 4, 6);
+static const struct reg_field kt20xx_b3_enable = REG_FIELD(KTD20XX_ISELB34, 7, 7);
+static const struct reg_field kt20xx_b4_select = REG_FIELD(KTD20XX_ISELB34, 0, 2);
+static const struct reg_field kt20xx_b4_enable = REG_FIELD(KTD20XX_ISELB34, 3, 3);
+
+/* ISELC1 and ISELC2 */
+static const struct reg_field kt20xx_c1_select = REG_FIELD(KTD20XX_ISELC12, 4, 6);
+static const struct reg_field kt20xx_c1_enable = REG_FIELD(KTD20XX_ISELC12, 7, 7);
+static const struct reg_field kt20xx_c2_select = REG_FIELD(KTD20XX_ISELC12, 0, 2);
+static const struct reg_field kt20xx_c2_enable = REG_FIELD(KTD20XX_ISELC12, 3, 3);
+
+/* ISELC3 and ISELC4 */
+static const struct reg_field kt20xx_c3_select = REG_FIELD(KTD20XX_ISELC34, 4, 6);
+static const struct reg_field kt20xx_c3_enable = REG_FIELD(KTD20XX_ISELC34, 7, 7);
+static const struct reg_field kt20xx_c4_select = REG_FIELD(KTD20XX_ISELC34, 0, 2);
+static const struct reg_field kt20xx_c4_enable = REG_FIELD(KTD20XX_ISELC34, 3, 3);
+
+static const struct regmap_range ktd20xx_volatile_ranges = {
+ .range_min = KTD20XX_ID,
+ .range_max = KTD20XX_CONTROL,
+};
+
+static const struct regmap_access_table ktd20xx_volatile_table = {
+ .yes_ranges = &ktd20xx_volatile_ranges,
+ .n_yes_ranges = 1,
+};
+
+static const struct regmap_range ktd20xx_readable_ranges = {
+ .range_min = KTD20XX_ID,
+ .range_max = KTD20XX_MONITOR,
+};
+
+static const struct regmap_access_table ktd20xx_readable_table = {
+ .yes_ranges = &ktd20xx_readable_ranges,
+ .n_yes_ranges = 1,
+};
+
+static const struct regmap_config ktd20xx_regmap_config = {
+ .name = "ktd20xx_regmap",
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = KTD20XX_ISELC34,
+
+ .volatile_table = &ktd20xx_volatile_table,
+ .rd_table = &ktd20xx_readable_table,
+
+ .reg_defaults = ktd20xx_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(ktd20xx_reg_defs),
+ .cache_type = REGCACHE_FLAT,
+};
+
+struct ktd20xx_led {
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[KTD20XX_LED_CHANNELS];
+ int index;
+ struct regmap_field *enable;
+ struct regmap_field *select;
+ struct ktd20xx *chip;
+};
+
+struct ktd20xx {
+ struct mutex lock;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct device *dev;
+ struct regmap_field *control_mode;
+ struct regmap_field *faderate;
+ struct regmap_field *vendor;
+ struct regmap_field *chip_id;
+ struct regmap_field *chip_rev;
+ struct ktd20xx_led leds[KTD20XX_MAX_LEDS];
+};
+
+static int ktd20xx_hwinit(struct ktd20xx *chip)
+{
+ struct fwnode_handle *fwnode;
+ struct device *dev = &chip->client->dev;
+ u8 value[3];
+
+ fwnode = dev_fwnode(dev);
+
+ if (fwnode_property_read_u8_array(fwnode, "kinetic,color-current0", value, 3)) {
+ dev_warn(dev, "no kinetic,color-current0 property, use chip default value 0x28.\n");
+ } else {
+ regmap_write(chip->regmap, KTD20XX_IRED0, value[0]);
+ regmap_write(chip->regmap, KTD20XX_IGRN0, value[1]);
+ regmap_write(chip->regmap, KTD20XX_IBLU0, value[2]);
+ }
+
+ if (fwnode_property_read_u8_array(fwnode, "kinetic,color-current1", value, 3)) {
+ dev_warn(dev, "no kinetic,color-current1 property, use chip default value 0x60.\n");
+ } else {
+ regmap_write(chip->regmap, KTD20XX_IRED1, value[0]);
+ regmap_write(chip->regmap, KTD20XX_IGRN1, value[1]);
+ regmap_write(chip->regmap, KTD20XX_IBLU1, value[2]);
+ }
+
+ return 0;
+}
+
+static struct ktd20xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
+{
+ return container_of(mc_cdev, struct ktd20xx_led, mc_cdev);
+}
+
+static int ktd20xx_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
+ struct ktd20xx_led *led = mcled_cdev_to_led(mc_dev);
+ struct device *dev = led->chip->dev;
+ int ret = 0;
+ int i = 0;
+ unsigned int rgb = 0;
+ unsigned int bit = 0;
+
+ mutex_lock(&led->chip->lock);
+ if (brightness > 0)
+ ret = regmap_field_write(led->enable, 1);
+ else
+ ret = regmap_field_write(led->enable, 0);
+
+ if (ret) {
+ dev_err(dev, "Cannot set enable flag of LED %d error: %d\n",
+ led->index, ret);
+ goto out;
+ }
+
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ unsigned int intensity = mc_dev->subled_info[i].intensity;
+
+ switch (i) {
+ case RED:
+ bit = 4;
+ break;
+ case GRN:
+ bit = 2;
+ break;
+ case BLU:
+ bit = 1;
+ break;
+ }
+
+ if (intensity > 0)
+ rgb += bit;
+ }
+
+ ret = regmap_field_write(led->select, rgb);
+ if (ret) {
+ dev_err(dev, "Can not set RGB for LED %d error: %d\n",
+ led->index, ret);
+ goto out;
+ }
+
+out:
+ mutex_unlock(&led->chip->lock);
+ return ret;
+}
+
+static int ktd20xx_probe_dt(struct ktd20xx *chip)
+{
+ struct fwnode_handle *child = NULL;
+ struct led_init_data init_data = {};
+ struct led_classdev *led_cdev;
+ struct ktd20xx_led *led;
+ struct device *dev = &chip->client->dev;
+ u8 value[3] = { 0, 0, 0 };
+ int i = 0;
+ int ret = -EINVAL;
+ int color;
+
+ device_for_each_child_node(chip->dev, child) {
+ led = &chip->leds[i];
+
+ ret = fwnode_property_read_u32(child, "reg", &led->index);
+ if (ret) {
+ dev_err(dev, "missing property: reg\n");
+ goto child_out;
+ }
+ if (led->index >= KTD20XX_MAX_LEDS) {
+ dev_warn(dev, "property 'reg' must contain value between '0' and '%i'\n",
+ KTD20XX_MAX_LEDS);
+ goto child_out;
+ }
+
+ ret = fwnode_property_read_u32(child, "color", &color);
+ if (ret) {
+ dev_err(dev, "missing property: color\n");
+ goto child_out;
+ }
+ if (color != LED_COLOR_ID_RGB) {
+ dev_warn(dev, "property 'color' must contain value 'LED_COLOR_ID_RGB'\n");
+ goto child_out;
+ }
+
+ /* Get default color register selection */
+ if (fwnode_property_read_u8_array(child, "kinetic,color-selection", value, 3))
+ dev_warn(dev, "no kinetic,color-selection property found, use default rgbt color selection from register 0.\n");
+
+ led->subled_info[0].color_index = LED_COLOR_ID_RED;
+ led->subled_info[0].channel = 0;
+ led->subled_info[0].intensity = value[0];
+ led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ led->subled_info[1].channel = 1;
+ led->subled_info[1].intensity = value[1];
+ led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ led->subled_info[2].channel = 2;
+ led->subled_info[2].intensity = value[2];
+
+ led->mc_cdev.subled_info = led->subled_info;
+ led->mc_cdev.num_colors = KTD20XX_LED_CHANNELS;
+
+ init_data.fwnode = child;
+
+ led->chip = chip;
+ led_cdev = &led->mc_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = ktd20xx_brightness_set;
+
+ switch (led->index) {
+ case RGB_A1:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a1_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a1_enable);
+ break;
+ case RGB_A2:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a2_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a2_enable);
+ break;
+ case RGB_A3:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a3_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a3_enable);
+ break;
+ case RGB_A4:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a4_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_a4_enable);
+ break;
+ case RGB_B1:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b1_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b1_enable);
+ break;
+ case RGB_B2:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b2_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b2_enable);
+ break;
+ case RGB_B3:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b3_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b3_enable);
+ break;
+ case RGB_B4:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b4_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_b4_enable);
+ break;
+ case RGB_C1:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c1_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c1_enable);
+ break;
+ case RGB_C2:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c2_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c2_enable);
+ break;
+ case RGB_C3:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c3_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c3_enable);
+ break;
+ case RGB_C4:
+ led->select = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c4_select);
+ led->enable = devm_regmap_field_alloc(chip->dev,
+ chip->regmap, kt20xx_c4_enable);
+ break;
+ }
+
+ ret = devm_led_classdev_multicolor_register_ext(&chip->client->dev,
+ &led->mc_cdev,
+ &init_data);
+
+ if (ret) {
+ dev_err(&chip->client->dev, "led register err: %d\n", ret);
+ goto child_out;
+ }
+
+ i++;
+ fwnode_handle_put(child);
+ }
+
+ return 0;
+
+child_out:
+ fwnode_handle_put(child);
+ return ret;
+}
+
+/* Device attribute for color0 register
+ *
+ * The device attribute colour1 is intended to adjust the colour space.
+ * The colour strength can be controlled via the current in 125uA steps.
+ * The maximum current for the individual channels is limited to 24mA.
+ * To set a new RGB value, 3 values must be passed. This value may not be
+ * less than 0 and also not greater than 194. The chip can only process the
+ * maximum current of 24mA. This means that any value greater than 194
+ * cannot be set.
+ */
+static ssize_t current_color0_show(struct device *dev,
+ struct device_attribute *a,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned int value;
+ int len = 0;
+
+ mutex_lock(&chip->lock);
+ regmap_read(chip->regmap, KTD20XX_IRED0, &value);
+ len += sprintf(buf + len, "%d", value);
+ len += sprintf(buf + len, " ");
+
+ regmap_read(chip->regmap, KTD20XX_IGRN0, &value);
+ len += sprintf(buf + len, "%d", value);
+ len += sprintf(buf + len, " ");
+
+ regmap_read(chip->regmap, KTD20XX_IBLU0, &value);
+ len += sprintf(buf + len, "%d", value);
+ mutex_unlock(&chip->lock);
+
+ buf[len++] = '\n';
+ return len;
+}
+
+static ssize_t current_color0_store(struct device *dev,
+ struct device_attribute *a,
+ const char *buf, size_t size)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned int value[3];
+ int i;
+ ssize_t ret;
+
+ ret = sscanf(buf, "%u %u %u", &value[0], &value[1], &value[2]);
+ if (ret < 3) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (value[i] > 194) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ if (value[i] < 0) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ }
+
+ mutex_lock(&chip->lock);
+ regmap_write(chip->regmap, KTD20XX_IRED0, value[0]);
+ regmap_write(chip->regmap, KTD20XX_IGRN0, value[1]);
+ regmap_write(chip->regmap, KTD20XX_IBLU0, value[2]);
+ mutex_unlock(&chip->lock);
+ return size;
+
+err_out:
+ return ret;
+}
+static DEVICE_ATTR_RW(current_color0);
+
+/* Device attribute for color1 register
+ *
+ * The device attribute colour1 is intended to adjust the colour space.
+ * The colour strength can be controlled via the current in 125uA steps.
+ * The maximum current for the individual channels is limited to 24mA.
+ * To set a new RGB value, 3 values must be passed. This value may not be
+ * less than 0 and also not greater than 194. The chip can only process the
+ * maximum current of 24mA. This means that any value greater than 194
+ * cannot be set.
+ */
+static ssize_t current_color1_show(struct device *dev,
+ struct device_attribute *a,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned int value;
+ int len = 0;
+
+ mutex_lock(&chip->lock);
+ regmap_read(chip->regmap, KTD20XX_IRED1, &value);
+ len += sprintf(buf + len, "%d", value);
+ len += sprintf(buf + len, " ");
+
+ regmap_read(chip->regmap, KTD20XX_IGRN1, &value);
+ len += sprintf(buf + len, "%d", value);
+ len += sprintf(buf + len, " ");
+
+ regmap_read(chip->regmap, KTD20XX_IBLU1, &value);
+ len += sprintf(buf + len, "%d", value);
+ mutex_unlock(&chip->lock);
+
+ buf[len++] = '\n';
+ return len;
+}
+static ssize_t current_color1_store(struct device *dev,
+ struct device_attribute *a,
+ const char *buf, size_t size)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned int value[3];
+ int i;
+ ssize_t ret;
+
+ ret = sscanf(buf, "%u %u %u", &value[0], &value[1], &value[2]);
+ if (ret < 3) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (value[i] > 194) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ if (value[i] < 0) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ }
+
+ mutex_lock(&chip->lock);
+ regmap_write(chip->regmap, KTD20XX_IRED1, value[0]);
+ regmap_write(chip->regmap, KTD20XX_IGRN1, value[1]);
+ regmap_write(chip->regmap, KTD20XX_IBLU1, value[2]);
+ mutex_unlock(&chip->lock);
+ return size;
+
+err_out:
+ return ret;
+}
+static DEVICE_ATTR_RW(current_color1);
+
+/*
+ * The chip also offers the option "Night Mode".
+ * All LED current settings are divided by 16 for a 0 to 1.5mA current
+ * setting range.
+ */
+static ssize_t nightmode_show(struct device *dev, struct device_attribute *a,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned int value;
+
+ mutex_lock(&chip->lock);
+ regmap_field_read(chip->control_mode, &value);
+ mutex_unlock(&chip->lock);
+
+ if (value == CONTROL_MODE_NIGHT)
+ return sprintf(buf, "%d\n", 1);
+ else
+ return sprintf(buf, "%d\n", 0);
+}
+
+static ssize_t nightmode_store(struct device *dev, struct device_attribute *a,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned long nightmode;
+ int ret;
+
+ if (kstrtoul(buf, 10, &nightmode))
+ return -EINVAL;
+
+ if (nightmode > 1)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ if (nightmode == 1)
+ ret = regmap_field_write(chip->control_mode, CONTROL_MODE_NIGHT);
+ else
+ ret = regmap_field_write(chip->control_mode, CONTROL_MODE_NORMAL);
+ mutex_unlock(&chip->lock);
+
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_RW(nightmode);
+
+/*
+ * The chip also offers the option "Fade rate".
+ */
+static ssize_t faderate_show(struct device *dev, struct device_attribute *a,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned int value;
+
+ mutex_lock(&chip->lock);
+ regmap_field_read(chip->faderate, &value);
+ mutex_unlock(&chip->lock);
+
+ return sprintf(buf, "%d\n", value);
+}
+
+static ssize_t faderate_store(struct device *dev, struct device_attribute *a,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+ unsigned long faderate;
+
+ if (kstrtoul(buf, 10, &faderate))
+ return -EINVAL;
+
+ if (faderate < 0)
+ return -EINVAL;
+
+ if (faderate > 7)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ regmap_field_write(chip->faderate, faderate);
+ mutex_unlock(&chip->lock);
+
+ return count;
+}
+static DEVICE_ATTR_RW(faderate);
+
+static struct attribute *ktd20xx_led_controller_attrs[] = {
+ &dev_attr_current_color0.attr,
+ &dev_attr_current_color1.attr,
+ &dev_attr_nightmode.attr,
+ &dev_attr_faderate.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(ktd20xx_led_controller);
+
+static int ktd20xx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ktd20xx *chip;
+ unsigned int vendor;
+ unsigned int chip_id;
+ unsigned int chip_rev;
+ int ret;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ mutex_init(&chip->lock);
+ chip->client = client;
+ chip->dev = &client->dev;
+ i2c_set_clientdata(client, chip);
+
+ chip->regmap = devm_regmap_init_i2c(client, &ktd20xx_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ goto error;
+ }
+
+ chip->control_mode = devm_regmap_field_alloc(chip->dev, chip->regmap,
+ kt20xx_control_mode);
+ chip->faderate = devm_regmap_field_alloc(chip->dev, chip->regmap,
+ kt20xx_faderate);
+ chip->vendor = devm_regmap_field_alloc(chip->dev, chip->regmap,
+ kt20xx_vendor);
+ chip->chip_id = devm_regmap_field_alloc(chip->dev, chip->regmap,
+ kt20xx_chip_id);
+ chip->chip_rev = devm_regmap_field_alloc(chip->dev, chip->regmap,
+ kt20xx_chip_rev);
+
+ regmap_field_write(chip->control_mode, CONTROL_MODE_RESET);
+ ret = regmap_field_read(chip->vendor, &vendor);
+ if (ret) {
+ dev_err(&client->dev, "Failed to read vendor: %d\n", ret);
+ goto error;
+ }
+
+ ret = regmap_field_read(chip->chip_id, &chip_id);
+ if (ret) {
+ dev_err(&client->dev, "Failed to read chip id: %d\n", ret);
+ goto error;
+ }
+
+ ret = regmap_field_read(chip->chip_rev, &chip_rev);
+ if (ret) {
+ dev_err(&client->dev, "Failed to read chip rev: %d\n", ret);
+ goto error;
+ }
+
+ dev_info(&client->dev, "vendor: 0x%02x chip-id: 0x%02x chip-rev: 0x%02x\n",
+ vendor, chip_id, chip_rev);
+
+ regmap_field_write(chip->control_mode, CONTROL_MODE_NORMAL);
+
+ if (devm_device_add_groups(chip->dev, ktd20xx_led_controller_groups))
+ dev_warn(&client->dev, "Could not add attribute group!\n");
+
+ ret = ktd20xx_hwinit(chip);
+ if (ret)
+ goto error;
+
+ ret = ktd20xx_probe_dt(chip);
+ if (ret)
+ goto error;
+
+ return 0;
+
+error:
+ mutex_destroy(&chip->lock);
+ return ret;
+}
+
+static int ktd20xx_remove(struct i2c_client *client)
+{
+ struct ktd20xx *chip = i2c_get_clientdata(client);
+
+ mutex_lock(&chip->lock);
+ regmap_field_write(chip->control_mode, CONTROL_MODE_OFF);
+ mutex_unlock(&chip->lock);
+
+ mutex_destroy(&chip->lock);
+
+ return 0;
+}
+
+static const struct i2c_device_id ktd20xx_id[] = {
+ { "ktd20xx", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ktd20xx_id);
+
+static const struct of_device_id of_ktd20xx_leds_match[] = {
+ { .compatible = "kinetic,ktd20xx", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_ktd20xx_leds_match);
+
+static struct i2c_driver ktd20xx_driver = {
+ .driver = {
+ .name = "ktd20xx",
+ .of_match_table = of_ktd20xx_leds_match,
+ },
+ .probe = ktd20xx_probe,
+ .remove = ktd20xx_remove,
+ .id_table = ktd20xx_id
+};
+module_i2c_driver(ktd20xx_driver);
+
+MODULE_DESCRIPTION("Kinetic KTD20xx LED driver");
+MODULE_AUTHOR("Florian Eckert <[email protected]>");
+MODULE_LICENSE("GPL v2");
--
2.20.1

2021-11-10 00:50:08

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: ktd20xx: Add the KTD20xx family of the RGB LEDs driver from Kinetic

hi!

> Introduce the KTD2061/58/59/60 RGB LEDs driver. The difference in these
> parts are the address number on the I2C bus the device is listen on.
>
> All KT20xx device could control up to 12 LEDs. The chip can be operated
> in two variants.

How are the variants selected?

> Variant 1:
> The device has the ability to group LED outputs into two banks so that
> the two LED banks can be controlled with the same color. This could not
> be done via the LEDs 'sysfs' entry because of the limitation on the color
> register count. The color of the two banks can be configured via device
> 'sysfs' entry for all LEDs at once [current_color0|current_color1].
> Which color the LED is to be used can be set via the 'sysfs' of the
> individual LEDs via the 'multi_intensity' file. Valid values for the
> colors (RGB) are 0 | 1. The value 0 selects the color register 0 and the
> value 1 selects the color register 1.

So... you can select two colors (current_color0, current_color1), and
then then each of the 12 LEDs get one of those colors. What about
intensities? Can brightness be set arbitrarily for each LED?

> Variant 2:
> The device can also set the LED color independently. Since the chip only
> has two color registers, but we want to control the 12 LEDs
> independently via the 'led-class-multicolour' sysfs entry,
> the full RGB color depth cannot be used. Due to this limitation, only 7
> colors and the color black (off) can be set. To use this mode the color
> registers must be preset via the device tree or the device 'sysfs'. The
> color registers 0 must be preset with 0x00 (Red=0x00 Green=0x00 Blue=0x00).
> The color register1 should be preset all with the same value. This value
> depends on which light intensity is to be used in the setup.

So now we have 7 colors we can select from. That sounds better than
two colors. Why would we ever want to use variant 1?

Can we simply pretend this is 7 LED RGB driver?

> +/* Device attribute for color0 register
> + *
> + * The device attribute colour1 is intended to adjust the colour space.

Use color, not colour. Plus run this through checkpatch.

> + * The colour strength can be controlled via the current in 125uA steps.

I don't know what "colour strength" is.

> +/*
> + * The chip also offers the option "Fade rate".
> + */
> +static ssize_t faderate_show(struct device *dev, struct device_attribute *a,
> + char *buf)
> +{
> + struct i2c_client *client = to_i2c_client(dev);
> + struct ktd20xx *chip = i2c_get_clientdata(client);
> + unsigned int value;
> +
> + mutex_lock(&chip->lock);
> + regmap_field_read(chip->faderate, &value);
> + mutex_unlock(&chip->lock);
> +
> + return sprintf(buf, "%d\n", value);
> +}

That's way too hardware specific.

Best regards,
Pavel
--
http://www.livejournal.com/~pavelmachek


Attachments:
(No filename) (2.81 kB)
signature.asc (188.00 B)
Digital signature
Download all attachments

2021-11-10 00:51:03

by Pavel Machek

[permalink] [raw]
Subject: Re: [PATCH 0/2] leds: Add KTD20xx RGB LEDs driver from Kinetic

Hi!

> Introduce the KTD2061/58/59/60 RGB LEDs driver. The difference in these
> parts are the address number on the I2C bus the device is listen on.
>
> All KT20xx device could control up to 12 LEDs. The chip can be operated
> in two variants.
>
> Florian Eckert (2):
> leds: ktd20xx: Add the KTD20xx family of the RGB LEDs driver from
> Kinetic
> dt: bindings: KTD20xx: Introduce the ktd20xx family of RGB drivers

That's... not a nice piece of hardware.

We'll want dt changes first, driver next please.

If this uses non-standard interface, it will need to be
documented. But I would like to understand the limitations first.

Do you have actual device where this is used?

Best regards,
Pavel
--
http://www.livejournal.com/~pavelmachek


Attachments:
(No filename) (786.00 B)
signature.asc (188.00 B)
Digital signature
Download all attachments

2021-11-10 09:38:41

by Florian Eckert

[permalink] [raw]
Subject: Re: [PATCH 0/2] leds: Add KTD20xx RGB LEDs driver from Kinetic

> Hi!

Thanks for reviewing my patchset.

>> Florian Eckert (2):
>> leds: ktd20xx: Add the KTD20xx family of the RGB LEDs driver from
>> Kinetic
>> dt: bindings: KTD20xx: Introduce the ktd20xx family of RGB drivers
>
> That's... not a nice piece of hardware.

Yes, that may be, but I have tried to use what the chip can do.
So that it works for my use case. It would be great if we could
integrate it
into the color LED framework of the kernel.


> If this uses non-standard interface, it will need to be
> documented. But I would like to understand the limitations first.

OK Then I will try it.

Register Layout:

| Address | Name | Type | Access | Default | B7 | B6 | B5 | B4 | B3
| B2 | B1 | B0 |
|:-------:|:-------:|:------:|:------:|:-------:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| 0x00 | ID | Data | R | 0xA4 | VENDOR[2:0] |
DIE_ID[4:0] |
| 0x01 | MONITOR | Status | R | 0x30 | DIE_REV[3:0] | SC
| BE | CE | UV |
| 0x02 | CONTROL | Config | R/W | 0x00 |MODE[1:0]| BE
|TEMP[1:0]|FADE_RATE[2:0]|
| 0x03 | IRED0 | Config | R/W | 0x28 |
IRED_SET0[7:0] |
| 0x04 | IGRN0 | Config | R/W | 0x28 |
IGRN_SET0[7:0] |
| 0x05 | IBLU0 | Config | R/W | 0x28 |
IBLU_SET0[7:0] |
| 0x06 | IRED1 | Config | R/W | 0x60 |
IRED_SET1[7:0] |
| 0x07 | IGRN1 | Config | R/W | 0x60 |
IGRN_SET1[7:0] |
| 0x08 | IBLU1 | Config | R/W | 0x60 |
IBLU_SET1[7:0] |
| 0x09 | ISELA12 | Config | R/W | 0x00
|ENA1|RGBA1_SEL[2:0]|ENA2|RGBA2_SEL[2:0]|
| 0x0A | ISELA34 | Config | R/W | 0x00
|ENA3|RGBA3_SEL[2:0]|ENA4|RGBA4_SEL[2:0]|
| 0x0B | ISELB12 | Config | R/W | 0x00
|ENB1|RGBB1_SEL[2:0]|ENB2|RGBB2_SEL[2:0]|
| 0x0C | ISELB34 | Config | R/W | 0x00
|ENB3|RGBB3_SEL[2:0]|ENB4|RGBB4_SEL[2:0]|
| 0x0D | ISELC12 | Config | R/W | 0x00
|ENC1|RGBC1_SEL[2:0]|ENC2|RGBC2_SEL[2:0]|
| 0x0E | ISELc34 | Config | R/W | 0x00
|ENC3|RGBC3_SEL[2:0]|ENC4|RGBC4_SEL[2:0]|

The registers 0x0A to 0x0E controls the LEDs. Each register controls two
LEDs.
The top bit [3] of each byte nibble controls whether the LED is on or
off.
The other bits [0:2] of each nibble, select which color register to use
Ixxx_SET0
and Ixxx_SET1 (0x03 to 0x08).

Bit 0 -> Blue (RGBxx_SEL):
If this bit is set to 1 then use IBLU_SET1 value otherwise bit is 0 use
IBLU_SET0 value.

Bit 1 -> Green RGBxx_SEL):
If this bit is set to 1 then use IGRN_SET1 value otherwise bit is 0 use
IGRN_SET0 value.

Bit 2 -> Red RGBxx_SEL):
If this bit is set to 1 then use IRED_SET1 value otherwise bit is 0 use
IRED_SET0 value.

This means that we can define two colors from the full RGB range in the
Ixxx_SET0
and Ixxx_SET1 respectively. And the LEDs can select which RGB color,
value they want
to use for the individual basic color via the RGBxx_SEL. In reality,
this is the electrical
current that the LED gets. The different electric currents produce a
color depending
on the current ratio.

There are now various possibilities for the whole chip to fit into the
color LED framework
of the kernel.

Variant1:
Prefill Ixxx_SET0 with one value for example 0x28 and set Ixxx_SET1 to
0x00.
Then we could select with the RGBxx_SEl[2:0] of an LED which color we
want.
But we only could have 8 colors because of the limition

This are the colors we could produce with this setup.
| Red | Green | Blue | Color |
|:---:|:-----:|:----:|:-------:|
| 0 | 0 | 1 | Blue |
| 0 | 1 | 0 | Green |
| 0 | 1 | 1 |Turquoise|
| 1 | 0 | 0 | Red |
| 1 | 0 | 1 | Purple |
| 1 | 1 | 0 | Yellow |
| 1 | 1 | 1 | White |
| 0 | 0 | 0 | Black |

The color brightness for the eight RGB colors can only be changed
together.
To do this, the value of the entire Ixxx_SET0 must be changed at once,
for
example 0x28 -> 0x14 to halve the brightness. However, this applies to
all LEDS!
This variant would fit into the color LED framework of the kernel.

Variant2:
Prefill Ixxx_SET0 and Ixxx_SET1 via device sysfs with an RGB color, and
the select for every LEDS with RGBxx[2:0] which color ratio we want to
use.
The problem with this is that we have to decide beforehand which color
we want to use. We should not change the Ixxx_SET0 and Ixx_SET1 register
value because that would affect all the LEDs! This variant would not fit
so well
into the color LED framework of the kernel, as we could not selectively
change the
color for each LED.

I hope I could make it understandable, if not please ask :-)

> Do you have actual device where this is used?

I don't know where the chip is installed, but we just have
new hardware in the pipeline that has this chip for LED control.
For our setup we only need 7 colors. Therefore, this chip is sufficient.


- Best regards

Florian

2021-11-22 07:17:33

by Florian Eckert

[permalink] [raw]
Subject: Re: [PATCH 0/2] leds: Add KTD20xx RGB LEDs driver from Kinetic

Hello Pavel,

> If this uses non-standard interface, it will need to be
> documented. But I would like to understand the limitations first.

I have already sent you an explanation and the register description.
Did you get it? Do you need anything else?

Best regards,
Florian

2021-11-22 10:33:22

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH 1/2] leds: ktd20xx: Add the KTD20xx family of the RGB LEDs driver from Kinetic

Hi Florian,

url: https://github.com/0day-ci/linux/commits/Florian-Eckert/leds-Add-KTD20xx-RGB-LEDs-driver-from-Kinetic/20211109-181728
base: git://git.kernel.org/pub/scm/linux/kernel/git/pavel/linux-leds.git for-next
config: i386-randconfig-m031-20211115 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-22) 9.3.0

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <[email protected]>
Reported-by: Dan Carpenter <[email protected]>

smatch warnings:
drivers/leds/leds-ktd20xx.c:304 ktd20xx_probe_dt() warn: missing error code 'ret'
drivers/leds/leds-ktd20xx.c:492 current_color0_store() warn: unsigned 'value[i]' is never less than zero.
drivers/leds/leds-ktd20xx.c:566 current_color1_store() warn: unsigned 'value[i]' is never less than zero.
drivers/leds/leds-ktd20xx.c:661 faderate_store() warn: unsigned 'faderate' is never less than zero.

vim +/ret +304 drivers/leds/leds-ktd20xx.c

004e74105bb360 Florian Eckert 2021-11-09 281 static int ktd20xx_probe_dt(struct ktd20xx *chip)
004e74105bb360 Florian Eckert 2021-11-09 282 {
004e74105bb360 Florian Eckert 2021-11-09 283 struct fwnode_handle *child = NULL;
004e74105bb360 Florian Eckert 2021-11-09 284 struct led_init_data init_data = {};
004e74105bb360 Florian Eckert 2021-11-09 285 struct led_classdev *led_cdev;
004e74105bb360 Florian Eckert 2021-11-09 286 struct ktd20xx_led *led;
004e74105bb360 Florian Eckert 2021-11-09 287 struct device *dev = &chip->client->dev;
004e74105bb360 Florian Eckert 2021-11-09 288 u8 value[3] = { 0, 0, 0 };
004e74105bb360 Florian Eckert 2021-11-09 289 int i = 0;
004e74105bb360 Florian Eckert 2021-11-09 290 int ret = -EINVAL;
004e74105bb360 Florian Eckert 2021-11-09 291 int color;
004e74105bb360 Florian Eckert 2021-11-09 292
004e74105bb360 Florian Eckert 2021-11-09 293 device_for_each_child_node(chip->dev, child) {
004e74105bb360 Florian Eckert 2021-11-09 294 led = &chip->leds[i];
004e74105bb360 Florian Eckert 2021-11-09 295
004e74105bb360 Florian Eckert 2021-11-09 296 ret = fwnode_property_read_u32(child, "reg", &led->index);
004e74105bb360 Florian Eckert 2021-11-09 297 if (ret) {
004e74105bb360 Florian Eckert 2021-11-09 298 dev_err(dev, "missing property: reg\n");
004e74105bb360 Florian Eckert 2021-11-09 299 goto child_out;
004e74105bb360 Florian Eckert 2021-11-09 300 }
004e74105bb360 Florian Eckert 2021-11-09 301 if (led->index >= KTD20XX_MAX_LEDS) {
004e74105bb360 Florian Eckert 2021-11-09 302 dev_warn(dev, "property 'reg' must contain value between '0' and '%i'\n",
004e74105bb360 Florian Eckert 2021-11-09 303 KTD20XX_MAX_LEDS);

ret = -EINVAL;

004e74105bb360 Florian Eckert 2021-11-09 @304 goto child_out;
004e74105bb360 Florian Eckert 2021-11-09 305 }
004e74105bb360 Florian Eckert 2021-11-09 306
004e74105bb360 Florian Eckert 2021-11-09 307 ret = fwnode_property_read_u32(child, "color", &color);
004e74105bb360 Florian Eckert 2021-11-09 308 if (ret) {
004e74105bb360 Florian Eckert 2021-11-09 309 dev_err(dev, "missing property: color\n");
004e74105bb360 Florian Eckert 2021-11-09 310 goto child_out;
004e74105bb360 Florian Eckert 2021-11-09 311 }
004e74105bb360 Florian Eckert 2021-11-09 312 if (color != LED_COLOR_ID_RGB) {
004e74105bb360 Florian Eckert 2021-11-09 313 dev_warn(dev, "property 'color' must contain value 'LED_COLOR_ID_RGB'\n");

ret = -EINVAL;

004e74105bb360 Florian Eckert 2021-11-09 314 goto child_out;
004e74105bb360 Florian Eckert 2021-11-09 315 }
004e74105bb360 Florian Eckert 2021-11-09 316
004e74105bb360 Florian Eckert 2021-11-09 317 /* Get default color register selection */
004e74105bb360 Florian Eckert 2021-11-09 318 if (fwnode_property_read_u8_array(child, "kinetic,color-selection", value, 3))
004e74105bb360 Florian Eckert 2021-11-09 319 dev_warn(dev, "no kinetic,color-selection property found, use default rgbt color selection from register 0.\n");
004e74105bb360 Florian Eckert 2021-11-09 320
004e74105bb360 Florian Eckert 2021-11-09 321 led->subled_info[0].color_index = LED_COLOR_ID_RED;
004e74105bb360 Florian Eckert 2021-11-09 322 led->subled_info[0].channel = 0;
004e74105bb360 Florian Eckert 2021-11-09 323 led->subled_info[0].intensity = value[0];
004e74105bb360 Florian Eckert 2021-11-09 324 led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
004e74105bb360 Florian Eckert 2021-11-09 325 led->subled_info[1].channel = 1;
004e74105bb360 Florian Eckert 2021-11-09 326 led->subled_info[1].intensity = value[1];
004e74105bb360 Florian Eckert 2021-11-09 327 led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
004e74105bb360 Florian Eckert 2021-11-09 328 led->subled_info[2].channel = 2;
004e74105bb360 Florian Eckert 2021-11-09 329 led->subled_info[2].intensity = value[2];
004e74105bb360 Florian Eckert 2021-11-09 330
004e74105bb360 Florian Eckert 2021-11-09 331 led->mc_cdev.subled_info = led->subled_info;
004e74105bb360 Florian Eckert 2021-11-09 332 led->mc_cdev.num_colors = KTD20XX_LED_CHANNELS;
004e74105bb360 Florian Eckert 2021-11-09 333
004e74105bb360 Florian Eckert 2021-11-09 334 init_data.fwnode = child;
004e74105bb360 Florian Eckert 2021-11-09 335
004e74105bb360 Florian Eckert 2021-11-09 336 led->chip = chip;
004e74105bb360 Florian Eckert 2021-11-09 337 led_cdev = &led->mc_cdev.led_cdev;
004e74105bb360 Florian Eckert 2021-11-09 338 led_cdev->brightness_set_blocking = ktd20xx_brightness_set;
004e74105bb360 Florian Eckert 2021-11-09 339
004e74105bb360 Florian Eckert 2021-11-09 340 switch (led->index) {
004e74105bb360 Florian Eckert 2021-11-09 341 case RGB_A1:
004e74105bb360 Florian Eckert 2021-11-09 342 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 343 chip->regmap, kt20xx_a1_select);
004e74105bb360 Florian Eckert 2021-11-09 344 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 345 chip->regmap, kt20xx_a1_enable);
004e74105bb360 Florian Eckert 2021-11-09 346 break;
004e74105bb360 Florian Eckert 2021-11-09 347 case RGB_A2:
004e74105bb360 Florian Eckert 2021-11-09 348 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 349 chip->regmap, kt20xx_a2_select);
004e74105bb360 Florian Eckert 2021-11-09 350 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 351 chip->regmap, kt20xx_a2_enable);
004e74105bb360 Florian Eckert 2021-11-09 352 break;
004e74105bb360 Florian Eckert 2021-11-09 353 case RGB_A3:
004e74105bb360 Florian Eckert 2021-11-09 354 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 355 chip->regmap, kt20xx_a3_select);
004e74105bb360 Florian Eckert 2021-11-09 356 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 357 chip->regmap, kt20xx_a3_enable);
004e74105bb360 Florian Eckert 2021-11-09 358 break;
004e74105bb360 Florian Eckert 2021-11-09 359 case RGB_A4:
004e74105bb360 Florian Eckert 2021-11-09 360 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 361 chip->regmap, kt20xx_a4_select);
004e74105bb360 Florian Eckert 2021-11-09 362 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 363 chip->regmap, kt20xx_a4_enable);
004e74105bb360 Florian Eckert 2021-11-09 364 break;
004e74105bb360 Florian Eckert 2021-11-09 365 case RGB_B1:
004e74105bb360 Florian Eckert 2021-11-09 366 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 367 chip->regmap, kt20xx_b1_select);
004e74105bb360 Florian Eckert 2021-11-09 368 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 369 chip->regmap, kt20xx_b1_enable);
004e74105bb360 Florian Eckert 2021-11-09 370 break;
004e74105bb360 Florian Eckert 2021-11-09 371 case RGB_B2:
004e74105bb360 Florian Eckert 2021-11-09 372 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 373 chip->regmap, kt20xx_b2_select);
004e74105bb360 Florian Eckert 2021-11-09 374 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 375 chip->regmap, kt20xx_b2_enable);
004e74105bb360 Florian Eckert 2021-11-09 376 break;
004e74105bb360 Florian Eckert 2021-11-09 377 case RGB_B3:
004e74105bb360 Florian Eckert 2021-11-09 378 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 379 chip->regmap, kt20xx_b3_select);
004e74105bb360 Florian Eckert 2021-11-09 380 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 381 chip->regmap, kt20xx_b3_enable);
004e74105bb360 Florian Eckert 2021-11-09 382 break;
004e74105bb360 Florian Eckert 2021-11-09 383 case RGB_B4:
004e74105bb360 Florian Eckert 2021-11-09 384 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 385 chip->regmap, kt20xx_b4_select);
004e74105bb360 Florian Eckert 2021-11-09 386 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 387 chip->regmap, kt20xx_b4_enable);
004e74105bb360 Florian Eckert 2021-11-09 388 break;
004e74105bb360 Florian Eckert 2021-11-09 389 case RGB_C1:
004e74105bb360 Florian Eckert 2021-11-09 390 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 391 chip->regmap, kt20xx_c1_select);
004e74105bb360 Florian Eckert 2021-11-09 392 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 393 chip->regmap, kt20xx_c1_enable);
004e74105bb360 Florian Eckert 2021-11-09 394 break;
004e74105bb360 Florian Eckert 2021-11-09 395 case RGB_C2:
004e74105bb360 Florian Eckert 2021-11-09 396 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 397 chip->regmap, kt20xx_c2_select);
004e74105bb360 Florian Eckert 2021-11-09 398 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 399 chip->regmap, kt20xx_c2_enable);
004e74105bb360 Florian Eckert 2021-11-09 400 break;
004e74105bb360 Florian Eckert 2021-11-09 401 case RGB_C3:
004e74105bb360 Florian Eckert 2021-11-09 402 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 403 chip->regmap, kt20xx_c3_select);
004e74105bb360 Florian Eckert 2021-11-09 404 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 405 chip->regmap, kt20xx_c3_enable);
004e74105bb360 Florian Eckert 2021-11-09 406 break;
004e74105bb360 Florian Eckert 2021-11-09 407 case RGB_C4:
004e74105bb360 Florian Eckert 2021-11-09 408 led->select = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 409 chip->regmap, kt20xx_c4_select);
004e74105bb360 Florian Eckert 2021-11-09 410 led->enable = devm_regmap_field_alloc(chip->dev,
004e74105bb360 Florian Eckert 2021-11-09 411 chip->regmap, kt20xx_c4_enable);
004e74105bb360 Florian Eckert 2021-11-09 412 break;
004e74105bb360 Florian Eckert 2021-11-09 413 }
004e74105bb360 Florian Eckert 2021-11-09 414
004e74105bb360 Florian Eckert 2021-11-09 415 ret = devm_led_classdev_multicolor_register_ext(&chip->client->dev,
004e74105bb360 Florian Eckert 2021-11-09 416 &led->mc_cdev,
004e74105bb360 Florian Eckert 2021-11-09 417 &init_data);
004e74105bb360 Florian Eckert 2021-11-09 418
004e74105bb360 Florian Eckert 2021-11-09 419 if (ret) {
004e74105bb360 Florian Eckert 2021-11-09 420 dev_err(&chip->client->dev, "led register err: %d\n", ret);
004e74105bb360 Florian Eckert 2021-11-09 421 goto child_out;
004e74105bb360 Florian Eckert 2021-11-09 422 }
004e74105bb360 Florian Eckert 2021-11-09 423
004e74105bb360 Florian Eckert 2021-11-09 424 i++;
004e74105bb360 Florian Eckert 2021-11-09 425 fwnode_handle_put(child);
004e74105bb360 Florian Eckert 2021-11-09 426 }
004e74105bb360 Florian Eckert 2021-11-09 427
004e74105bb360 Florian Eckert 2021-11-09 428 return 0;
004e74105bb360 Florian Eckert 2021-11-09 429
004e74105bb360 Florian Eckert 2021-11-09 430 child_out:
004e74105bb360 Florian Eckert 2021-11-09 431 fwnode_handle_put(child);
004e74105bb360 Florian Eckert 2021-11-09 432 return ret;
004e74105bb360 Florian Eckert 2021-11-09 433 }
004e74105bb360 Florian Eckert 2021-11-09 434
004e74105bb360 Florian Eckert 2021-11-09 435 /* Device attribute for color0 register
004e74105bb360 Florian Eckert 2021-11-09 436 *
004e74105bb360 Florian Eckert 2021-11-09 437 * The device attribute colour1 is intended to adjust the colour space.
004e74105bb360 Florian Eckert 2021-11-09 438 * The colour strength can be controlled via the current in 125uA steps.
004e74105bb360 Florian Eckert 2021-11-09 439 * The maximum current for the individual channels is limited to 24mA.
004e74105bb360 Florian Eckert 2021-11-09 440 * To set a new RGB value, 3 values must be passed. This value may not be
004e74105bb360 Florian Eckert 2021-11-09 441 * less than 0 and also not greater than 194. The chip can only process the
004e74105bb360 Florian Eckert 2021-11-09 442 * maximum current of 24mA. This means that any value greater than 194
004e74105bb360 Florian Eckert 2021-11-09 443 * cannot be set.
004e74105bb360 Florian Eckert 2021-11-09 444 */
004e74105bb360 Florian Eckert 2021-11-09 445 static ssize_t current_color0_show(struct device *dev,
004e74105bb360 Florian Eckert 2021-11-09 446 struct device_attribute *a,
004e74105bb360 Florian Eckert 2021-11-09 447 char *buf)
004e74105bb360 Florian Eckert 2021-11-09 448 {
004e74105bb360 Florian Eckert 2021-11-09 449 struct i2c_client *client = to_i2c_client(dev);
004e74105bb360 Florian Eckert 2021-11-09 450 struct ktd20xx *chip = i2c_get_clientdata(client);
004e74105bb360 Florian Eckert 2021-11-09 451 unsigned int value;
004e74105bb360 Florian Eckert 2021-11-09 452 int len = 0;
004e74105bb360 Florian Eckert 2021-11-09 453
004e74105bb360 Florian Eckert 2021-11-09 454 mutex_lock(&chip->lock);
004e74105bb360 Florian Eckert 2021-11-09 455 regmap_read(chip->regmap, KTD20XX_IRED0, &value);
004e74105bb360 Florian Eckert 2021-11-09 456 len += sprintf(buf + len, "%d", value);
004e74105bb360 Florian Eckert 2021-11-09 457 len += sprintf(buf + len, " ");
004e74105bb360 Florian Eckert 2021-11-09 458
004e74105bb360 Florian Eckert 2021-11-09 459 regmap_read(chip->regmap, KTD20XX_IGRN0, &value);
004e74105bb360 Florian Eckert 2021-11-09 460 len += sprintf(buf + len, "%d", value);
004e74105bb360 Florian Eckert 2021-11-09 461 len += sprintf(buf + len, " ");
004e74105bb360 Florian Eckert 2021-11-09 462
004e74105bb360 Florian Eckert 2021-11-09 463 regmap_read(chip->regmap, KTD20XX_IBLU0, &value);
004e74105bb360 Florian Eckert 2021-11-09 464 len += sprintf(buf + len, "%d", value);
004e74105bb360 Florian Eckert 2021-11-09 465 mutex_unlock(&chip->lock);
004e74105bb360 Florian Eckert 2021-11-09 466
004e74105bb360 Florian Eckert 2021-11-09 467 buf[len++] = '\n';
004e74105bb360 Florian Eckert 2021-11-09 468 return len;
004e74105bb360 Florian Eckert 2021-11-09 469 }
004e74105bb360 Florian Eckert 2021-11-09 470
004e74105bb360 Florian Eckert 2021-11-09 471 static ssize_t current_color0_store(struct device *dev,
004e74105bb360 Florian Eckert 2021-11-09 472 struct device_attribute *a,
004e74105bb360 Florian Eckert 2021-11-09 473 const char *buf, size_t size)
004e74105bb360 Florian Eckert 2021-11-09 474 {
004e74105bb360 Florian Eckert 2021-11-09 475 struct i2c_client *client = to_i2c_client(dev);
004e74105bb360 Florian Eckert 2021-11-09 476 struct ktd20xx *chip = i2c_get_clientdata(client);
004e74105bb360 Florian Eckert 2021-11-09 477 unsigned int value[3];
004e74105bb360 Florian Eckert 2021-11-09 478 int i;
004e74105bb360 Florian Eckert 2021-11-09 479 ssize_t ret;
004e74105bb360 Florian Eckert 2021-11-09 480
004e74105bb360 Florian Eckert 2021-11-09 481 ret = sscanf(buf, "%u %u %u", &value[0], &value[1], &value[2]);
004e74105bb360 Florian Eckert 2021-11-09 482 if (ret < 3) {
004e74105bb360 Florian Eckert 2021-11-09 483 ret = -EINVAL;
004e74105bb360 Florian Eckert 2021-11-09 484 goto err_out;
004e74105bb360 Florian Eckert 2021-11-09 485 }
004e74105bb360 Florian Eckert 2021-11-09 486
004e74105bb360 Florian Eckert 2021-11-09 487 for (i = 0; i < 3; i++) {
004e74105bb360 Florian Eckert 2021-11-09 488 if (value[i] > 194) {
004e74105bb360 Florian Eckert 2021-11-09 489 ret = -EINVAL;
004e74105bb360 Florian Eckert 2021-11-09 490 goto err_out;
004e74105bb360 Florian Eckert 2021-11-09 491 }
004e74105bb360 Florian Eckert 2021-11-09 @492 if (value[i] < 0) {
^^^^^^^^^^^^
Impossible

004e74105bb360 Florian Eckert 2021-11-09 493 ret = -EINVAL;
004e74105bb360 Florian Eckert 2021-11-09 494 goto err_out;
004e74105bb360 Florian Eckert 2021-11-09 495 }
004e74105bb360 Florian Eckert 2021-11-09 496 }
004e74105bb360 Florian Eckert 2021-11-09 497
004e74105bb360 Florian Eckert 2021-11-09 498 mutex_lock(&chip->lock);
004e74105bb360 Florian Eckert 2021-11-09 499 regmap_write(chip->regmap, KTD20XX_IRED0, value[0]);
004e74105bb360 Florian Eckert 2021-11-09 500 regmap_write(chip->regmap, KTD20XX_IGRN0, value[1]);
004e74105bb360 Florian Eckert 2021-11-09 501 regmap_write(chip->regmap, KTD20XX_IBLU0, value[2]);
004e74105bb360 Florian Eckert 2021-11-09 502 mutex_unlock(&chip->lock);
004e74105bb360 Florian Eckert 2021-11-09 503 return size;
004e74105bb360 Florian Eckert 2021-11-09 504
004e74105bb360 Florian Eckert 2021-11-09 505 err_out:
004e74105bb360 Florian Eckert 2021-11-09 506 return ret;
004e74105bb360 Florian Eckert 2021-11-09 507 }

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/[email protected]