Skip to contentSkip to conversation

leds: pca995x: Minor fixes; reset, output-gain, error reporting and group brightness support

PATCH In review Nora Schiffer 3 participants 25 messages Oct 5 – Oct 27

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v5 cover leds: pca995x: Minor fixes; reset, output-gain, error reporting and group brightness support

This series adds several new features to the PCA995x LED driver:

- Reset GPIO
- Output current configuration
- Error reporting via sysfs (short/open circuit)
- Group brightness control (modulating individual LED brightness with a second, chip-global PWM signal)

The series also includes a number of low-priority fixes:

- Prevent configuration of out-of-bounds LED indices on 16-channel variants
- Avoid fwnode leaks in error paths
- Finish initializing hardware before making LED devices visible to userspace

Changelog:

v5:
- Collect Reviewed-bys
- Limit fault detection to PCA9955B/PCA9956B. PCA9952/PCA9955 have a fault test feature that works differently and is not covered by this series.
- Correctly specify MODE2 reset defaults for all supported chips. The initialization of MODE2 is introduced in a separate patch now.

v4:
- Make code for output-gain range check and default value clearer
- Use dev_get_drvdata() instead of i2c_get_clientdata()
- Use devres for sysfs group

v3:
- Collect Reviewed-bys
- New patch: Fix fwnode handle leaks in error paths
- New patch: Write global registers before creating LED devices
- Add chip-global sysfs groups before creating LED devices

v2:
- Collect Reviewed-bys
- Use full hardware model names in commit descriptions
- Add nxp, to output-gain property
- Fix nxp,output-gain default in binding docs
- Describe rationale for using register value directly in commit message
- New patch: Fix maximum LED index for 16-channel variants
- Use correct #includes for GPIO consumer and delays
- Use udelay for small delay
- Use sysfs_streq() for has_errors write
- Explicitly initialize MODE2 register, clear reported errors on probe
- Set GRPPWM register on probe

Nora Schiffer (11):
dt-bindings: leds: pca995x: Describe reset-gpios property
dt-bindings: leds: pca995x: Describe nxp,output-gain property
leds: pca995x: Fix maximum LED index for 16-channel variants
leds: pca995x: Fix fwnode handle leaks in error paths
leds: pca995x: Write global registers before creating LED devices
leds: pca995x: Add support for reset GPIO
leds: pca995x: Make output gain configurable
leds: pca995x: Initialize MODE2 register on probe
leds: pca995x: Add sysfs files for error reporting
leds: pca995x: Do not use full on LED mode
leds: pca995x: Add support for group brightness control

 .../devicetree/bindings/leds/nxp,pca995x.yaml |  13 +
 drivers/leds/leds-pca995x.c                   | 265 +++++++++++++++---
 2 files changed, 244 insertions(+), 34 deletions(-)
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#1 lore raw

PATCH v5 1/11 dt-bindings: leds: pca995x: Describe reset-gpios property

The PCA9952/PCA9955B/PCA9956B have a reset pin. Allow describing it in the Device Tree.

---
 Documentation/devicetree/bindings/leds/nxp,pca995x.yaml | 5 +++++
 1 file changed, 5 insertions(+)
Documentation/devicetree/bindings/leds/nxp,pca995x.yaml +5 -0
index ab8c90cbadb5c..201f32f1b8139 100644
@@ -36,6 +36,9 @@ properties:
   "#size-cells":
     const: 0
 
+  reset-gpios:
+    maxItems: 1
+
 patternProperties:
   "^led@[0-9a-f]+$":
     type: object
@@ -54,6 +57,7 @@ additionalProperties: false
 
 examples:
   - |
+    #include <dt-bindings/gpio/gpio.h>
     #include <dt-bindings/leds/common.h>
 
     i2c {
@@ -65,6 +69,7 @@ examples:
             reg = <0x01>;
             #address-cells = <1>;
             #size-cells = <0>;
+            reset-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>;
 
             led@0 {
                 reg = <0x0>;
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#2 lore raw

Re: [PATCH v5 01/11] dt-bindings: leds: pca995x: Describe reset-gpios property

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 5 lines

The PCA9952/PCA9955B/PCA9956B have a reset pin. Allow describing it in the Device Tree.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#3 lore raw

PATCH v5 2/11 dt-bindings: leds: pca995x: Describe nxp,output-gain property

In reply to Nora Schiffer

Allow configuring the output current to adjust for different hardware designs.

The output gain relates the output current with an external resistor connected to the LED driver. It does not directly correspond to a meaningful physical unit of measurement, so we refer to the datasheet for information on its calculation.

---
 Documentation/devicetree/bindings/leds/nxp,pca995x.yaml | 8 ++++++++
 1 file changed, 8 insertions(+)
Documentation/devicetree/bindings/leds/nxp,pca995x.yaml +8 -0
index 201f32f1b8139..372ecd18043e0 100644
@@ -39,6 +39,14 @@ properties:
   reset-gpios:
     maxItems: 1
 
+  nxp,output-gain:
+    description:
+      Set the output gain controlling the LED current as described in the
+      PCA995x datasheets.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    default: 127
+    maximum: 255
+
 patternProperties:
   "^led@[0-9a-f]+$":
     type: object
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#4 lore raw

Re: [PATCH v5 02/11] dt-bindings: leds: pca995x: Describe nxp,output-gain property

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 10 lines

Allow configuring the output current to adjust for different hardware designs.

The output gain relates the output current with an external resistor connected to the LED driver. It does not directly correspond to a meaningful physical unit of measurement, so we refer to the datasheet for information on its calculation.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
#5 lore raw

PATCH v5 3/11 leds: pca995x: Fix maximum LED index for 16-channel variants

In reply to Nora Schiffer

The pca995x_chipdef for the 16-channel variants PCA9952 and PCA9955B correctly sets num_leds to 16, but the value was not actually used when checking and iterating over the LEDs during probe.

---
 drivers/leds/leds-pca995x.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
drivers/leds/leds-pca995x.c +3 -3
index fee6216cd1bd0..8b05b3f12cf38 100644
@@ -146,7 +146,7 @@ static int pca995x_probe(struct i2c_client *client)
 		if (ret)
 			return ret;
 
-		if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg])
+		if (reg < 0 || reg >= chipdef->num_leds || led_fwnodes[reg])
 			return -EINVAL;
 
 		led = &chip->leds[reg];
@@ -157,7 +157,7 @@ static int pca995x_probe(struct i2c_client *client)
 		led->ldev.max_brightness = 255;
 	}
 
-	for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) {
+	for (i = 0; i < chipdef->num_leds; i++) {
 		struct led_init_data init_data = {};
 
 		if (!led_fwnodes[i])
@@ -169,7 +169,7 @@ static int pca995x_probe(struct i2c_client *client)
 						     &chip->leds[i].ldev,
 						     &init_data);
 		if (ret < 0) {
-			for (j = i; j < PCA995X_MAX_OUTPUTS; j++)
+			for (j = i; j < chipdef->num_leds; j++)
 				fwnode_handle_put(led_fwnodes[j]);
 			return dev_err_probe(dev, ret,
 					     "Could not register LED %s\n",
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#6 lore raw

Re: [PATCH v5 03/11] leds: pca995x: Fix maximum LED index for 16-channel variants

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 8 lines

The pca995x_chipdef for the 16-channel variants PCA9952 and PCA9955B correctly sets num_leds to 16, but the value was not actually used when checking and iterating over the LEDs during probe.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
#7 lore raw

PATCH v5 4/11 leds: pca995x: Fix fwnode handle leaks in error paths

In reply to Nora Schiffer

Each entry in led_fwnodes needs to be put as long as no LED device has been created for it yet - not just in the creation loop, but also in the first loop that iterates over the child nodes.

By clearing entries in led_fwnodes once they have been used, the same cleanup loop can be used to handle errors in both loops.

Fixes 82c5ada1f9d0 ("leds: pca995x: Fix device child node usage in pca995x_probe()")
---
 drivers/leds/leds-pca995x.c | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)
drivers/leds/leds-pca995x.c +17 -9
index 8b05b3f12cf38..079ce3d8f1e49 100644
@@ -123,7 +123,7 @@ static int pca995x_probe(struct i2c_client *client)
 	const struct pca995x_chipdef *chipdef;
 	struct pca995x_chip *chip;
 	struct pca995x_led *led;
-	int i, j, reg, ret;
+	int i, reg, ret;
 
 	chipdef = device_get_match_data(&client->dev);
 
@@ -144,10 +144,12 @@ static int pca995x_probe(struct i2c_client *client)
 	device_for_each_child_node_scoped(dev, child) {
 		ret = fwnode_property_read_u32(child, "reg", &reg);
 		if (ret)
-			return ret;
+			goto err_put_nodes;
 
-		if (reg < 0 || reg >= chipdef->num_leds || led_fwnodes[reg])
-			return -EINVAL;
+		if (reg < 0 || reg >= chipdef->num_leds || led_fwnodes[reg]) {
+			ret = -EINVAL;
+			goto err_put_nodes;
+		}
 
 		led = &chip->leds[reg];
 		led_fwnodes[reg] = fwnode_handle_get(child);
@@ -169,12 +171,12 @@ static int pca995x_probe(struct i2c_client *client)
 						     &chip->leds[i].ldev,
 						     &init_data);
 		if (ret < 0) {
-			for (j = i; j < chipdef->num_leds; j++)
-				fwnode_handle_put(led_fwnodes[j]);
-			return dev_err_probe(dev, ret,
-					     "Could not register LED %s\n",
-					     chip->leds[i].ldev.name);
+			dev_err_probe(dev, ret, "Could not register LED %s\n",
+				      chip->leds[i].ldev.name);
+			goto err_put_nodes;
 		}
+
+		led_fwnodes[i] = NULL;
 	}
 
 	/* Disable LED all-call address and set normal mode */
@@ -184,6 +186,12 @@ static int pca995x_probe(struct i2c_client *client)
 
 	/* IREF Output current value for all LEDn outputs */
 	return regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG);
+
+err_put_nodes:
+	for (i = 0; i < chipdef->num_leds; i++)
+		fwnode_handle_put(led_fwnodes[i]);
+
+	return ret;
 }
 
 static const struct i2c_device_id pca995x_id[] = {
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#8 lore raw

Re: [PATCH v5 04/11] leds: pca995x: Fix fwnode handle leaks in error paths

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines

Each entry in led_fwnodes needs to be put as long as no LED device has been created for it yet - not just in the creation loop, but also in the first loop that iterates over the child nodes.

By clearing entries in led_fwnodes once they have been used, the same cleanup loop can be used to handle errors in both loops.

Fixes 82c5ada1f9d0 ("leds: pca995x: Fix device child node usage in pca995x_probe()")

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=4
#9 lore raw

PATCH v5 5/11 leds: pca995x: Write global registers before creating LED devices

In reply to Nora Schiffer

The hardware setup should be finished before the devices become visible to userspace.

---
 drivers/leds/leds-pca995x.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)
drivers/leds/leds-pca995x.c +11 -7
index 079ce3d8f1e49..e6122b33451ed 100644
@@ -159,6 +159,16 @@ static int pca995x_probe(struct i2c_client *client)
 		led->ldev.max_brightness = 255;
 	}
 
+	/* Disable LED all-call address and set normal mode */
+	ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG);
+	if (ret)
+		goto err_put_nodes;
+
+	/* IREF Output current value for all LEDn outputs */
+	ret = regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG);
+	if (ret)
+		goto err_put_nodes;
+
 	for (i = 0; i < chipdef->num_leds; i++) {
 		struct led_init_data init_data = {};
 
@@ -179,13 +189,7 @@ static int pca995x_probe(struct i2c_client *client)
 		led_fwnodes[i] = NULL;
 	}
 
-	/* Disable LED all-call address and set normal mode */
-	ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG);
-	if (ret)
-		return ret;
-
-	/* IREF Output current value for all LEDn outputs */
-	return regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG);
+	return 0;
 
 err_put_nodes:
 	for (i = 0; i < chipdef->num_leds; i++)
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#10 lore raw

Re: [PATCH v5 05/11] leds: pca995x: Write global registers before creating LED devices

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 4 lines

The hardware setup should be finished before the devices become visible to userspace.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
#11 lore raw

PATCH v5 6/11 leds: pca995x: Add support for reset GPIO

In reply to Nora Schiffer

The PCA9952/9955B/9956B have a reset pin. Use it to reset the LED driver if provided in the Device Tree.

---
 drivers/leds/leds-pca995x.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)
drivers/leds/leds-pca995x.c +13 -0
index e6122b33451ed..b528b2b4990a7 100644
@@ -8,6 +8,8 @@
  * Copyright 2023 Marek Vasut
  */
 
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/leds.h>
 #include <linux/module.h>
@@ -121,6 +123,7 @@ static int pca995x_probe(struct i2c_client *client)
 	struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 };
 	struct device *dev = &client->dev;
 	const struct pca995x_chipdef *chipdef;
+	struct gpio_desc *reset_gpio;
 	struct pca995x_chip *chip;
 	struct pca995x_led *led;
 	int i, reg, ret;
@@ -130,6 +133,16 @@ static int pca995x_probe(struct i2c_client *client)
 	if (!dev_fwnode(dev))
 		return -ENODEV;
 
+	reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(reset_gpio),
+				     "failed to request reset GPIO\n");
+	if (reset_gpio) {
+		udelay(3);
+		gpiod_set_value_cansleep(reset_gpio, 0);
+		usleep_range(1500, 1600);
+	}
+
 	chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
 	if (!chip)
 		return -ENOMEM;
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#12 lore raw

Re: [PATCH v5 06/11] leds: pca995x: Add support for reset GPIO

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 4 lines

The PCA9952/9955B/9956B have a reset pin. Use it to reset the LED driver if provided in the Device Tree.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
#13 lore raw

PATCH v5 7/11 leds: pca995x: Make output gain configurable

In reply to Nora Schiffer

The output gain linearly configures the LED current in 255 levels; the absolute current depends on both the gain value and the R_ext resistor connected to the LED driver. Make the gain configurable in the Device Tree instead of hardcoding it to 128, allowing to adjust the current for different hardware designs.

---
 drivers/leds/leds-pca995x.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)
drivers/leds/leds-pca995x.c +9 -1
index b528b2b4990a7..df3f7ab2e1f87 100644
@@ -122,6 +122,7 @@ static int pca995x_probe(struct i2c_client *client)
 {
 	struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 };
 	struct device *dev = &client->dev;
+	u32 val, iref = PCA995X_IREFALL_HALF_CFG;
 	const struct pca995x_chipdef *chipdef;
 	struct gpio_desc *reset_gpio;
 	struct pca995x_chip *chip;
@@ -154,6 +155,13 @@ static int pca995x_probe(struct i2c_client *client)
 
 	i2c_set_clientdata(client, chip);
 
+	if (!device_property_read_u32(dev, "nxp,output-gain", &val)) {
+		if (val > PCA995X_IREFALL_FULL_CFG)
+			return dev_err_probe(dev, -EINVAL, "invalid nxp,output-gain\n");
+
+		iref = val;
+	}
+
 	device_for_each_child_node_scoped(dev, child) {
 		ret = fwnode_property_read_u32(child, "reg", &reg);
 		if (ret)
@@ -178,7 +186,7 @@ static int pca995x_probe(struct i2c_client *client)
 		goto err_put_nodes;
 
 	/* IREF Output current value for all LEDn outputs */
-	ret = regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG);
+	ret = regmap_write(chip->regmap, chipdef->irefall, iref);
 	if (ret)
 		goto err_put_nodes;
 
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#14 lore raw

Re: [PATCH v5 07/11] leds: pca995x: Make output gain configurable

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines

The output gain linearly configures the LED current in 255 levels; the absolute current depends on both the gain value and the R_ext resistor connected to the LED driver. Make the gain configurable in the Device Tree instead of hardcoding it to 128, allowing to adjust the current for different hardware designs.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=7
#15 lore raw

PATCH v5 8/11 leds: pca995x: Initialize MODE2 register on probe

In reply to Nora Schiffer

Ensure that the LED driver is in a known state.

Not all of the bits in the MODE2 register have a function - some are just reserved bits that always read 1 - so the values given in the PCA9952/ PCA9955B/PCA9956B datasheets are simply specified as hex values.

---
 drivers/leds/leds-pca995x.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)
drivers/leds/leds-pca995x.c +12 -0
index df3f7ab2e1f87..3d6053625b1c9 100644
@@ -43,24 +43,31 @@ struct pca995x_chipdef {
 	unsigned int num_leds;
 	u8 pwm_base;
 	u8 irefall;
+	u8 mode2_cfg;
 };
 
 static const struct pca995x_chipdef pca9952_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x43,
+	/* Reset default */
+	.mode2_cfg	= 0x05,
 };
 
 static const struct pca995x_chipdef pca9955b_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x08,
 	.irefall	= 0x45,
+	/* Reset default */
+	.mode2_cfg	= 0x01,
 };
 
 static const struct pca995x_chipdef pca9956b_chipdef = {
 	.num_leds	= 24,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x40,
+	/* Reset default */
+	.mode2_cfg	= 0x05,
 };
 
 struct pca995x_led {
@@ -185,6 +192,11 @@ static int pca995x_probe(struct i2c_client *client)
 	if (ret)
 		goto err_put_nodes;
 
+	/* Clear errors on probe */
+	ret = regmap_write(chip->regmap, PCA995X_MODE2, chipdef->mode2_cfg);
+	if (ret)
+		goto err_put_nodes;
+
 	/* IREF Output current value for all LEDn outputs */
 	ret = regmap_write(chip->regmap, chipdef->irefall, iref);
 	if (ret)
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#16 lore raw

Re: [PATCH v5 08/11] leds: pca995x: Initialize MODE2 register on probe

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines

Ensure that the LED driver is in a known state.

Not all of the bits in the MODE2 register have a function - some are just reserved bits that always read 1 - so the values given in the PCA9952/ PCA9955B/PCA9956B datasheets are simply specified as hex values.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=8
#17 lore raw

PATCH v5 9/11 leds: pca995x: Add sysfs files for error reporting

In reply to Nora Schiffer

The PCA9955B/PCA9956B have builtin failure detection. Provide sysfs files for individual LED status (reporting "okay", "short-circuit" or "open-circuit") as well as a global "has_errors" flag. has_errors is sticky and must be cleared by writing "clear" to the sysfs file.

Failure detection on the PCA9952/PCA9955 (without "B") works differently and is currently unsupported.

---
 drivers/leds/leds-pca995x.c | 112 ++++++++++++++++++++++++++++++++++--
 1 file changed, 108 insertions(+), 4 deletions(-)
drivers/leds/leds-pca995x.c +108 -4
index 3d6053625b1c9..359803d0030fb 100644
@@ -8,6 +8,7 @@
  * Copyright 2023 Marek Vasut
  */
 
+#include <linux/bits.h>
 #include <linux/delay.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
@@ -24,6 +25,9 @@
 /* Auto-increment disabled. Normal mode */
 #define PCA995X_MODE1_CFG		0x00
 
+#define PCA995X_MODE2_CLRERR		BIT(4)
+#define PCA995X_MODE2_ERROR		BIT(6)
+
 /* LED select registers determine the source that drives LED outputs */
 #define PCA995X_LED_OFF			0x0
 #define PCA995X_LED_ON			0x1
@@ -37,19 +41,37 @@
 #define PCA995X_IREFALL_FULL_CFG	0xFF
 #define PCA995X_IREFALL_HALF_CFG	(PCA995X_IREFALL_FULL_CFG / 2)
 
+#define PCA995X_EFLAG_UNSUPPORTED	U8_MAX
+
+#define PCA995X_EFLAG_BITS		2
+#define PCA995X_EFLAG_MASK		GENMASK(1, 0)
+
 #define ldev_to_led(c)	container_of(c, struct pca995x_led, ldev)
 
 struct pca995x_chipdef {
 	unsigned int num_leds;
 	u8 pwm_base;
 	u8 irefall;
+	u8 eflag_base;
 	u8 mode2_cfg;
+	const struct attribute_group **attr_groups;
 };
 
+static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf);
+
+static DEVICE_ATTR_RO(status);
+
+static struct attribute *pca995x_led_error_attrs[] = {
+	&dev_attr_status.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(pca995x_led_error);
+
 static const struct pca995x_chipdef pca9952_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x43,
+	.eflag_base	= PCA995X_EFLAG_UNSUPPORTED,
 	/* Reset default */
 	.mode2_cfg	= 0x05,
 };
@@ -58,16 +80,20 @@ static const struct pca995x_chipdef pca9955b_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x08,
 	.irefall	= 0x45,
-	/* Reset default */
-	.mode2_cfg	= 0x01,
+	.eflag_base	= 0x46,
+	/* Reset default + clear errors on probe */
+	.mode2_cfg	= 0x01 | PCA995X_MODE2_CLRERR,
+	.attr_groups	= pca995x_led_error_groups,
 };
 
 static const struct pca995x_chipdef pca9956b_chipdef = {
 	.num_leds	= 24,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x40,
-	/* Reset default */
-	.mode2_cfg	= 0x05,
+	.eflag_base	= 0x41,
+	/* Reset default + clear errors on probe */
+	.mode2_cfg	= 0x05 | PCA995X_MODE2_CLRERR,
+	.attr_groups	= pca995x_led_error_groups,
 };
 
 struct pca995x_led {
@@ -119,6 +145,77 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
 	}
 }
 
+static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct pca995x_led *led = ldev_to_led(led_cdev);
+	struct pca995x_chip *chip = led->chip;
+	const struct pca995x_chipdef *chipdef = chip->chipdef;
+	const char *status = "unknown";
+	unsigned int val;
+	int shift, ret;
+	u8 reg;
+
+	reg = chipdef->eflag_base + (led->led_no / PCA995X_OUTPUTS_PER_REG);
+	shift = PCA995X_EFLAG_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
+
+	ret = regmap_read(chip->regmap, reg, &val);
+	if (ret)
+		return ret;
+
+	switch ((val >> shift) & PCA995X_EFLAG_MASK) {
+	case 0:
+		status = "okay";
+		break;
+	case 1:
+		status = "short-circuit";
+		break;
+	case 2:
+		status = "open-circuit";
+	}
+
+	return sysfs_emit(buf, "%s\n", status);
+}
+
+static ssize_t has_errors_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(chip->regmap, PCA995X_MODE2, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%d\n", !!(val & PCA995X_MODE2_ERROR));
+}
+
+static ssize_t has_errors_store(struct device *dev, struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	if (!sysfs_streq(buf, "clear"))
+		return -EINVAL;
+
+	ret = regmap_update_bits(chip->regmap, PCA995X_MODE2,
+				 PCA995X_MODE2_CLRERR, PCA995X_MODE2_CLRERR);
+
+	return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(has_errors);
+
+static struct attribute *pca995x_error_attrs[] = {
+	&dev_attr_has_errors.attr,
+	NULL,
+};
+
+static const struct attribute_group pca995x_error_group = {
+	.attrs = pca995x_error_attrs,
+};
+
 static const struct regmap_config pca995x_regmap = {
 	.reg_bits = 8,
 	.val_bits = 8,
@@ -185,6 +282,7 @@ static int pca995x_probe(struct i2c_client *client)
 		led->led_no = reg;
 		led->ldev.brightness_set_blocking = pca995x_brightness_set;
 		led->ldev.max_brightness = 255;
+		led->ldev.groups = chipdef->attr_groups;
 	}
 
 	/* Disable LED all-call address and set normal mode */
@@ -202,6 +300,12 @@ static int pca995x_probe(struct i2c_client *client)
 	if (ret)
 		goto err_put_nodes;
 
+	if (chipdef->eflag_base != PCA995X_EFLAG_UNSUPPORTED) {
+		ret = devm_device_add_group(dev, &pca995x_error_group);
+		if (ret)
+			goto err_put_nodes;
+	}
+
 	for (i = 0; i < chipdef->num_leds; i++) {
 		struct led_init_data init_data = {};
 
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#18 lore raw

Re: [PATCH v5 09/11] leds: pca995x: Add sysfs files for error reporting

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines

The PCA9955B/PCA9956B have builtin failure detection. Provide sysfs files for individual LED status (reporting "okay", "short-circuit" or "open-circuit") as well as a global "has_errors" flag. has_errors is sticky and must be cleared by writing "clear" to the sysfs file.

Failure detection on the PCA9952/PCA9955 (without "B") works differently and is currently unsupported.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=9
maintainer
#19 lore raw

Re: [PATCH v5 09/11] leds: pca995x: Add sysfs files for error reporting

In reply to Nora Schiffer
Quoted 46 lines · Nora Schiffer

The PCA9955B/PCA9956B have builtin failure detection. Provide sysfs files for individual LED status (reporting "okay", "short-circuit" or "open-circuit") as well as a global "has_errors" flag. has_errors is sticky and must be cleared by writing "clear" to the sysfs file.

Failure detection on the PCA9952/PCA9955 (without "B") works differently and is currently unsupported.

---
 drivers/leds/leds-pca995x.c | 112 ++++++++++++++++++++++++++++++++++--
 1 file changed, 108 insertions(+), 4 deletions(-)
drivers/leds/leds-pca995x.c +9 -0
index 3d6053625b1c9..359803d0030fb 100644
@@ -8,6 +8,7 @@
  * Copyright 2023 Marek Vasut
  */
 
+#include <linux/bits.h>
 #include <linux/delay.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
@@ -24,6 +25,9 @@
 /* Auto-increment disabled. Normal mode */
 #define PCA995X_MODE1_CFG		0x00
 
+#define PCA995X_MODE2_CLRERR		BIT(4)
+#define PCA995X_MODE2_ERROR		BIT(6)
+
 /* LED select registers determine the source that drives LED outputs */
 #define PCA995X_LED_OFF			0x0
 #define PCA995X_LED_ON			0x1
@@ -37,19 +41,37 @@
 #define PCA995X_IREFALL_FULL_CFG	0xFF
 #define PCA995X_IREFALL_HALF_CFG	(PCA995X_IREFALL_FULL_CFG / 2)
 
+#define PCA995X_EFLAG_UNSUPPORTED	U8_MAX
+
+#define PCA995X_EFLAG_BITS		2
+#define PCA995X_EFLAG_MASK		GENMASK(1, 0)
+
 #define ldev_to_led(c)	container_of(c, struct pca995x_led, ldev)
 

drivers/leds/leds-pca995x.c line 60

Quoted 10 lines
drivers/leds/leds-pca995x.c +3 -0
 struct pca995x_chipdef {
 	unsigned int num_leds;
 	u8 pwm_base;
 	u8 irefall;
+	u8 eflag_base;
 	u8 mode2_cfg;
+	const struct attribute_group **attr_groups;
 };
 
+static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf);

Please avoid forward declaring functions.

Shuffle things around so that it is not required.

drivers/leds/leds-pca995x.c line 68

Quoted 8 lines
drivers/leds/leds-pca995x.c +8 -0
+
+static DEVICE_ATTR_RO(status);
+
+static struct attribute *pca995x_led_error_attrs[] = {
+	&dev_attr_status.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(pca995x_led_error);

Are these documented as per the guidance?

Quoted 140 lines
drivers/leds/leds-pca995x.c +88 -4
+
 static const struct pca995x_chipdef pca9952_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x43,
+	.eflag_base	= PCA995X_EFLAG_UNSUPPORTED,
 	/* Reset default */
 	.mode2_cfg	= 0x05,
 };
@@ -58,16 +80,20 @@ static const struct pca995x_chipdef pca9955b_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x08,
 	.irefall	= 0x45,
-	/* Reset default */
-	.mode2_cfg	= 0x01,
+	.eflag_base	= 0x46,
+	/* Reset default + clear errors on probe */
+	.mode2_cfg	= 0x01 | PCA995X_MODE2_CLRERR,
+	.attr_groups	= pca995x_led_error_groups,
 };
 
 static const struct pca995x_chipdef pca9956b_chipdef = {
 	.num_leds	= 24,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x40,
-	/* Reset default */
-	.mode2_cfg	= 0x05,
+	.eflag_base	= 0x41,
+	/* Reset default + clear errors on probe */
+	.mode2_cfg	= 0x05 | PCA995X_MODE2_CLRERR,
+	.attr_groups	= pca995x_led_error_groups,
 };
 
 struct pca995x_led {
@@ -119,6 +145,77 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
 	}
 }
 
+static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct pca995x_led *led = ldev_to_led(led_cdev);
+	struct pca995x_chip *chip = led->chip;
+	const struct pca995x_chipdef *chipdef = chip->chipdef;
+	const char *status = "unknown";
+	unsigned int val;
+	int shift, ret;
+	u8 reg;
+
+	reg = chipdef->eflag_base + (led->led_no / PCA995X_OUTPUTS_PER_REG);
+	shift = PCA995X_EFLAG_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
+
+	ret = regmap_read(chip->regmap, reg, &val);
+	if (ret)
+		return ret;
+
+	switch ((val >> shift) & PCA995X_EFLAG_MASK) {
+	case 0:
+		status = "okay";
+		break;
+	case 1:
+		status = "short-circuit";
+		break;
+	case 2:
+		status = "open-circuit";
+	}
+
+	return sysfs_emit(buf, "%s\n", status);
+}
+
+static ssize_t has_errors_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(chip->regmap, PCA995X_MODE2, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%d\n", !!(val & PCA995X_MODE2_ERROR));
+}
+
+static ssize_t has_errors_store(struct device *dev, struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	if (!sysfs_streq(buf, "clear"))
+		return -EINVAL;
+
+	ret = regmap_update_bits(chip->regmap, PCA995X_MODE2,
+				 PCA995X_MODE2_CLRERR, PCA995X_MODE2_CLRERR);
+
+	return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(has_errors);
+
+static struct attribute *pca995x_error_attrs[] = {
+	&dev_attr_has_errors.attr,
+	NULL,
+};
+
+static const struct attribute_group pca995x_error_group = {
+	.attrs = pca995x_error_attrs,
+};
+
 static const struct regmap_config pca995x_regmap = {
 	.reg_bits = 8,
 	.val_bits = 8,
@@ -185,6 +282,7 @@ static int pca995x_probe(struct i2c_client *client)
 		led->led_no = reg;
 		led->ldev.brightness_set_blocking = pca995x_brightness_set;
 		led->ldev.max_brightness = 255;
+		led->ldev.groups = chipdef->attr_groups;
 	}
 
 	/* Disable LED all-call address and set normal mode */
@@ -202,6 +300,12 @@ static int pca995x_probe(struct i2c_client *client)
 	if (ret)
 		goto err_put_nodes;
 
+	if (chipdef->eflag_base != PCA995X_EFLAG_UNSUPPORTED) {
+		ret = devm_device_add_group(dev, &pca995x_error_group);
+		if (ret)
+			goto err_put_nodes;
+	}
+
 	for (i = 0; i < chipdef->num_leds; i++) {
 		struct led_init_data init_data = {};
 
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
Signature · 1 line
Lee Jones
#20 lore raw

Re: [PATCH v5 09/11] leds: pca995x: Add sysfs files for error reporting

Quoted 57 lines · Lee Jones
Quoted 55 lines · Nora Schiffer

The PCA9955B/PCA9956B have builtin failure detection. Provide sysfs files for individual LED status (reporting "okay", "short-circuit" or "open-circuit") as well as a global "has_errors" flag. has_errors is sticky and must be cleared by writing "clear" to the sysfs file.

Failure detection on the PCA9952/PCA9955 (without "B") works differently and is currently unsupported.

---
 drivers/leds/leds-pca995x.c | 112 ++++++++++++++++++++++++++++++++++--
 1 file changed, 108 insertions(+), 4 deletions(-)
drivers/leds/leds-pca995x.c +11 -0
index 3d6053625b1c9..359803d0030fb 100644
@@ -8,6 +8,7 @@
  * Copyright 2023 Marek Vasut
  */
 
+#include <linux/bits.h>
 #include <linux/delay.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
@@ -24,6 +25,9 @@
 /* Auto-increment disabled. Normal mode */
 #define PCA995X_MODE1_CFG		0x00
 
+#define PCA995X_MODE2_CLRERR		BIT(4)
+#define PCA995X_MODE2_ERROR		BIT(6)
+
 /* LED select registers determine the source that drives LED outputs */
 #define PCA995X_LED_OFF			0x0
 #define PCA995X_LED_ON			0x1
@@ -37,19 +41,37 @@
 #define PCA995X_IREFALL_FULL_CFG	0xFF
 #define PCA995X_IREFALL_HALF_CFG	(PCA995X_IREFALL_FULL_CFG / 2)
 
+#define PCA995X_EFLAG_UNSUPPORTED	U8_MAX
+
+#define PCA995X_EFLAG_BITS		2
+#define PCA995X_EFLAG_MASK		GENMASK(1, 0)
+
 #define ldev_to_led(c)	container_of(c, struct pca995x_led, ldev)
 
 struct pca995x_chipdef {
 	unsigned int num_leds;
 	u8 pwm_base;
 	u8 irefall;
+	u8 eflag_base;
 	u8 mode2_cfg;
+	const struct attribute_group **attr_groups;
 };
 
Quoted 5 lines

drivers/leds/leds-pca995x.c line 60

Quoted 1 line
drivers/leds/leds-pca995x.c +1 -0
+static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf);

Please avoid forward declaring functions.

Shuffle things around so that it is not required.

I found it a bit ugly to define status_show() above the pca995x_chipdef structs, far away from all other function definitions, but if you prefer it that way, I'll change it.

Quoted 11 lines

drivers/leds/leds-pca995x.c line 68

Quoted 9 lines
drivers/leds/leds-pca995x.c +8 -0
+
+static DEVICE_ATTR_RO(status);
+
+static struct attribute *pca995x_led_error_attrs[] = {
+	&dev_attr_status.attr,
+	NULL,
+};
+ATTRIBUTE_GROUPS(pca995x_led_error);

Are these documented as per the guidance?

Ah, I forgot about that, will fix.

Quoted 141 lines
Quoted 141 lines
drivers/leds/leds-pca995x.c +88 -4
+
 static const struct pca995x_chipdef pca9952_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x43,
+	.eflag_base	= PCA995X_EFLAG_UNSUPPORTED,
 	/* Reset default */
 	.mode2_cfg	= 0x05,
 };
@@ -58,16 +80,20 @@ static const struct pca995x_chipdef pca9955b_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x08,
 	.irefall	= 0x45,
-	/* Reset default */
-	.mode2_cfg	= 0x01,
+	.eflag_base	= 0x46,
+	/* Reset default + clear errors on probe */
+	.mode2_cfg	= 0x01 | PCA995X_MODE2_CLRERR,
+	.attr_groups	= pca995x_led_error_groups,
 };
 
 static const struct pca995x_chipdef pca9956b_chipdef = {
 	.num_leds	= 24,
 	.pwm_base	= 0x0a,
 	.irefall	= 0x40,
-	/* Reset default */
-	.mode2_cfg	= 0x05,
+	.eflag_base	= 0x41,
+	/* Reset default + clear errors on probe */
+	.mode2_cfg	= 0x05 | PCA995X_MODE2_CLRERR,
+	.attr_groups	= pca995x_led_error_groups,
 };
 
 struct pca995x_led {
@@ -119,6 +145,77 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
 	}
 }
 
+static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct pca995x_led *led = ldev_to_led(led_cdev);
+	struct pca995x_chip *chip = led->chip;
+	const struct pca995x_chipdef *chipdef = chip->chipdef;
+	const char *status = "unknown";
+	unsigned int val;
+	int shift, ret;
+	u8 reg;
+
+	reg = chipdef->eflag_base + (led->led_no / PCA995X_OUTPUTS_PER_REG);
+	shift = PCA995X_EFLAG_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
+
+	ret = regmap_read(chip->regmap, reg, &val);
+	if (ret)
+		return ret;
+
+	switch ((val >> shift) & PCA995X_EFLAG_MASK) {
+	case 0:
+		status = "okay";
+		break;
+	case 1:
+		status = "short-circuit";
+		break;
+	case 2:
+		status = "open-circuit";
+	}
+
+	return sysfs_emit(buf, "%s\n", status);
+}
+
+static ssize_t has_errors_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(chip->regmap, PCA995X_MODE2, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%d\n", !!(val & PCA995X_MODE2_ERROR));
+}
+
+static ssize_t has_errors_store(struct device *dev, struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	if (!sysfs_streq(buf, "clear"))
+		return -EINVAL;
+
+	ret = regmap_update_bits(chip->regmap, PCA995X_MODE2,
+				 PCA995X_MODE2_CLRERR, PCA995X_MODE2_CLRERR);
+
+	return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(has_errors);
+
+static struct attribute *pca995x_error_attrs[] = {
+	&dev_attr_has_errors.attr,
+	NULL,
+};
+
+static const struct attribute_group pca995x_error_group = {
+	.attrs = pca995x_error_attrs,
+};
+
 static const struct regmap_config pca995x_regmap = {
 	.reg_bits = 8,
 	.val_bits = 8,
@@ -185,6 +282,7 @@ static int pca995x_probe(struct i2c_client *client)
 		led->led_no = reg;
 		led->ldev.brightness_set_blocking = pca995x_brightness_set;
 		led->ldev.max_brightness = 255;
+		led->ldev.groups = chipdef->attr_groups;
 	}
 
 	/* Disable LED all-call address and set normal mode */
@@ -202,6 +300,12 @@ static int pca995x_probe(struct i2c_client *client)
 	if (ret)
 		goto err_put_nodes;
 
+	if (chipdef->eflag_base != PCA995X_EFLAG_UNSUPPORTED) {
+		ret = devm_device_add_group(dev, &pca995x_error_group);
+		if (ret)
+			goto err_put_nodes;
+	}
+
 	for (i = 0; i < chipdef->num_leds; i++) {
 		struct led_init_data init_data = {};
 
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#21 lore raw

PATCH v5 10/11 leds: pca995x: Do not use full on LED mode

In reply to Nora Schiffer

LEDs in full on mode are unaffected by group dimming. Avoid using it, so group dimming is applied consistently.

This slightly changes the brightness at maximum (from 100% to 255/256 duty cycle), which should be imperceptible; arguably it is also more consistent with the other brightness levels, as it will now scale linearly from 0/256 to 255/256 over the whole range.

---
 drivers/leds/leds-pca995x.c | 33 ++++++++++++++-------------------
 1 file changed, 14 insertions(+), 19 deletions(-)
drivers/leds/leds-pca995x.c +14 -19
index 359803d0030fb..a95193059e747 100644
@@ -121,28 +121,23 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
 	ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG);
 	shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
 
-	switch (brightness) {
-	case LED_FULL:
-		return regmap_update_bits(chip->regmap, ledout_addr,
-					  PCA995X_LDRX_MASK << shift,
-					  PCA995X_LED_ON << shift);
-	case LED_OFF:
+	if (brightness == LED_OFF) {
 		return regmap_update_bits(chip->regmap, ledout_addr,
 					  PCA995X_LDRX_MASK << shift, 0);
-	default:
-		/* Adjust brightness as per user input by changing individual PWM */
-		ret = regmap_write(chip->regmap, pwmout_addr, brightness);
-		if (ret)
-			return ret;
-
-		/*
-		 * Change LDRx configuration to individual brightness via PWM.
-		 * LED will stop blinking if it's doing so.
-		 */
-		return regmap_update_bits(chip->regmap, ledout_addr,
-					  PCA995X_LDRX_MASK << shift,
-					  PCA995X_LED_PWM_MODE << shift);
 	}
+
+	/* Adjust brightness as per user input by changing individual PWM */
+	ret = regmap_write(chip->regmap, pwmout_addr, brightness);
+	if (ret)
+		return ret;
+
+	/*
+	 * Change LDRx configuration to individual brightness via PWM.
+	 * LED will stop blinking if it's doing so.
+	 */
+	return regmap_update_bits(chip->regmap, ledout_addr,
+				  PCA995X_LDRX_MASK << shift,
+				  PCA995X_LED_PWM_MODE << shift);
 }
 
 static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#22 lore raw

Re: [PATCH v5 10/11] leds: pca995x: Do not use full on LED mode

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 9 lines

LEDs in full on mode are unaffected by group dimming. Avoid using it, so group dimming is applied consistently.

This slightly changes the brightness at maximum (from 100% to 255/256 duty cycle), which should be imperceptible; arguably it is also more consistent with the other brightness levels, as it will now scale linearly from 0/256 to 255/256 over the whole range.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=10
#23 lore raw

PATCH v5 11/11 leds: pca995x: Add support for group brightness control

In reply to Nora Schiffer

When LEDs are set to PWM mode with group control enabled, their brightness can control using a global "group PWM" setting, modulating the individual LEDs' brightness with a second PWM running at a different frequency. This lowers the minimum duty cycle from 1/256 to 1/65536 (averaged over the modulated signal). Group brightness control is particularly useful to adjust for different levels of ambient light.

For simplicity, group PWM mode is always enabled, with the reset default of 255 as group brightness. This reduces the effective duty cycle by 1/256 at all individual brightness levels (the individual PWM signals are modulated with the 255/256 duty cycle group PWM), which should be imperceptible.

---
 drivers/leds/leds-pca995x.c | 57 +++++++++++++++++++++++++++++++++++--
 1 file changed, 55 insertions(+), 2 deletions(-)
drivers/leds/leds-pca995x.c +55 -2
index a95193059e747..302d64d4215fa 100644
@@ -32,6 +32,7 @@
 #define PCA995X_LED_OFF			0x0
 #define PCA995X_LED_ON			0x1
 #define PCA995X_LED_PWM_MODE		0x2
+#define PCA995X_LED_PWM_MODE_GRP	0x3
 #define PCA995X_LDRX_MASK		0x3
 #define PCA995X_LDRX_BITS		2
 
@@ -51,6 +52,7 @@
 struct pca995x_chipdef {
 	unsigned int num_leds;
 	u8 pwm_base;
+	u8 grppwm;
 	u8 irefall;
 	u8 eflag_base;
 	u8 mode2_cfg;
@@ -70,6 +72,7 @@ ATTRIBUTE_GROUPS(pca995x_led_error);
 static const struct pca995x_chipdef pca9952_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x0a,
+	.grppwm		= 0x08,
 	.irefall	= 0x43,
 	.eflag_base	= PCA995X_EFLAG_UNSUPPORTED,
 	/* Reset default */
@@ -79,6 +82,7 @@ static const struct pca995x_chipdef pca9952_chipdef = {
 static const struct pca995x_chipdef pca9955b_chipdef = {
 	.num_leds	= 16,
 	.pwm_base	= 0x08,
+	.grppwm		= 0x06,
 	.irefall	= 0x45,
 	.eflag_base	= 0x46,
 	/* Reset default + clear errors on probe */
@@ -89,6 +93,7 @@ static const struct pca995x_chipdef pca9955b_chipdef = {
 static const struct pca995x_chipdef pca9956b_chipdef = {
 	.num_leds	= 24,
 	.pwm_base	= 0x0a,
+	.grppwm		= 0x08,
 	.irefall	= 0x40,
 	.eflag_base	= 0x41,
 	/* Reset default + clear errors on probe */
@@ -133,13 +138,52 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
 
 	/*
 	 * Change LDRx configuration to individual brightness via PWM.
-	 * LED will stop blinking if it's doing so.
 	 */
 	return regmap_update_bits(chip->regmap, ledout_addr,
 				  PCA995X_LDRX_MASK << shift,
-				  PCA995X_LED_PWM_MODE << shift);
+				  PCA995X_LED_PWM_MODE_GRP << shift);
 }
 
+static ssize_t group_brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(chip->regmap, chip->chipdef->grppwm, &val);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%u\n", val);
+}
+
+static ssize_t group_brightness_store(struct device *dev, struct device_attribute *attr,
+				       const char *buf, size_t count)
+{
+	struct pca995x_chip *chip = dev_get_drvdata(dev);
+	int ret;
+	u8 val;
+
+	ret = kstrtou8(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(chip->regmap, chip->chipdef->grppwm, val);
+
+	return ret ?: count;
+}
+
+static DEVICE_ATTR_RW(group_brightness);
+
+static struct attribute *pca995x_attrs[] = {
+	&dev_attr_group_brightness.attr,
+	NULL,
+};
+
+static const struct attribute_group pca995x_group = {
+	.attrs = pca995x_attrs,
+};
+
 static ssize_t status_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
 	struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -290,11 +334,20 @@ static int pca995x_probe(struct i2c_client *client)
 	if (ret)
 		goto err_put_nodes;
 
+	/* Full group brightness */
+	ret = regmap_write(chip->regmap, chipdef->grppwm, U8_MAX);
+	if (ret)
+		goto err_put_nodes;
+
 	/* IREF Output current value for all LEDn outputs */
 	ret = regmap_write(chip->regmap, chipdef->irefall, iref);
 	if (ret)
 		goto err_put_nodes;
 
+	ret = devm_device_add_group(dev, &pca995x_group);
+	if (ret)
+		goto err_put_nodes;
+
 	if (chipdef->eflag_base != PCA995X_EFLAG_UNSUPPORTED) {
 		ret = devm_device_add_group(dev, &pca995x_error_group);
 		if (ret)
Signature · 4 lines
TQ-Systems GmbH | Mühlstraße 2, Gut Delling | 82229 Seefeld, Germany
Amtsgericht München, HRB 105018
Geschäftsführer: Detlef Schneider, Rüdiger Stahl, Stefan Schneider
https://www.tq-group.com/
#24 lore raw

Re: [PATCH v5 11/11] leds: pca995x: Add support for group brightness control

Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines

When LEDs are set to PWM mode with group control enabled, their brightness can control using a global "group PWM" setting, modulating the individual LEDs' brightness with a second PWM running at a different frequency. This lowers the minimum duty cycle from 1/256 to 1/65536 (averaged over the modulated signal). Group brightness control is particularly useful to adjust for different levels of ambient light.

Quoted 7 lines

For simplicity, group PWM mode is always enabled, with the reset default of 255 as group brightness. This reduces the effective duty cycle by 1/256 at all individual brightness levels (the individual PWM signals are modulated with the 255/256 duty cycle group PWM), which should be imperceptible.

Sashiko has reviewed this patch and found no issues. It looks great!

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=11