leds: pca995x: Minor fixes; reset, output-gain, error reporting and group brightness support
Subsystems (2)
v3 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:
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 (10):
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: 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 | 244 +++++++++++++++--- 2 files changed, 223 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/
PATCH v3 1/10 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
@@ -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/
[email protected]
AI reviewRe: [PATCH v3 01/10] 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.
Reviewed-by Krzysztof Kozlowski <[email protected]>Signed-off-by Nora Schiffer <[email protected]>
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
PATCH v3 2/10 dt-bindings: leds: pca995x: Describe nxp,output-gain property
In reply to Nora SchifferAllow 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
@@ -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/
[email protected]
AI reviewRe: [PATCH v3 02/10] 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.
Reviewed-by Krzysztof Kozlowski <[email protected]>Signed-off-by Nora Schiffer <[email protected]>
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
PATCH v3 3/10 leds: pca995x: Fix maximum LED index for 16-channel variants
In reply to Nora SchifferThe 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
@@ -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/
[email protected]
AI reviewRe: [PATCH v3 03/10] leds: pca995x: Fix maximum LED index for 16-channel variants
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 7 lines
The
pca995x_chipdeffor the 16-channel variants PCA9952 and PCA9955B correctly setsnum_ledsto 16, but the value was not actually used when checking and iterating over the LEDs during probe.Fixes 68d6520d2e76 ("leds: leds-pca995x: Add support for NXP PCA9956B")Signed-off-by Nora Schiffer <[email protected]>
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
PATCH v3 4/10 leds: pca995x: Fix fwnode handle leaks in error paths
In reply to Nora SchifferEach 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.
--- drivers/leds/leds-pca995x.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)
drivers/leds/leds-pca995x.c +17 -9
@@ -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" , ®);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/
[email protected]
AI reviewRe: [PATCH v3 04/10] 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_fwnodesneeds 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_fwnodesonce 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()")Signed-off-by Nora Schiffer <[email protected]>
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
Lee Jones
maintainerRe: [PATCH v3 04/10] leds: pca995x: Fix fwnode handle leaks in error paths
In reply to Nora Schiffer--- checkpatch.pl: clean (0 issues) ---
Quoted 47 lines · Nora Schiffer
Each entry in
led_fwnodesneeds 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_fwnodesonce 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()")Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)drivers/leds/leds-pca995x.c +6
-5@@ -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" , ®);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++)
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 179
Quoted 10 lines
drivers/leds/leds-pca995x.c +5
-4-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 ;
Shouldn't we put this before removing it?
Quoted 23 lines
drivers/leds/leds-pca995x.c +6
-0}/* 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/
Signature · 1 line
Lee Jones
Re: [PATCH v3 04/10] leds: pca995x: Fix fwnode handle leaks in error paths
Quoted 55 lines · Lee Jones
--- checkpatch.pl: clean (0 issues) ---
Quoted 51 lines · Nora Schiffer
Each entry in
led_fwnodesneeds 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_fwnodesonce 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()")Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-)drivers/leds/leds-pca995x.c +6
-9@@ -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" , ®);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);
Quoted 8 lines
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 179
Quoted 6 lines
drivers/leds/leds-pca995x.c +5
-0+dev_err_probe (dev, ret,"Could not register LED %s\n" , + chip->leds[i].ldev.name); + goto err_put_nodes; } + + led_fwnodes[i] =NULL ;Shouldn't we put this before removing it?
The handle is only consumed when the LED has been registered successfully. On error (ret < 0), led_fwnodes[i] must not have been unset yet to avoid the leak.
Quoted 25 lines
Quoted 24 lines
drivers/leds/leds-pca995x.c +6
-0}/* 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/
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/
PATCH v3 5/10 leds: pca995x: Write global registers before creating LED devices
In reply to Nora SchifferThe 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
@@ -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/
[email protected]
AI reviewRe: [PATCH v3 05/10] 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.
Signed-off-by Nora Schiffer <[email protected]>
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
PATCH v3 6/10 leds: pca995x: Add support for reset GPIO
In reply to Nora SchifferThe 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
@@ -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/
[email protected]
AI reviewRe: [PATCH v3 06/10] 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.
Signed-off-by Nora Schiffer <[email protected]>
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
Lee Jones
maintainerRe: [PATCH v3 06/10] leds: pca995x: Add support for reset GPIO
In reply to Nora SchifferQuoted 30 lines · Nora Schiffer
The PCA9952/9955B/9956B have a reset pin. Use it to reset the LED driver if provided in the Device Tree.
Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)drivers/leds/leds-pca995x.c +3
-0@@ -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;
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 141
Quoted 9 lines
drivers/leds/leds-pca995x.c +6
-0@@ -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 );
It's strange to see delays _before_ actions.
What are you delaying for exactly?
Quoted 13 lines
drivers/leds/leds-pca995x.c +4
-0+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/
Signature · 1 line
Lee Jones
Re: [PATCH v3 06/10] leds: pca995x: Add support for reset GPIO
Quoted 35 lines · Lee Jones
Quoted 33 lines · Nora Schiffer
The PCA9952/9955B/9956B have a reset pin. Use it to reset the LED driver if provided in the Device Tree.
Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)drivers/leds/leds-pca995x.c +3
-0@@ -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 ;
Quoted 8 lines
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 141
Quoted 6 lines
drivers/leds/leds-pca995x.c +6
-0+ 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 );It's strange to see delays _before_ actions.
devm_gpiod_get*() with GPIOD_OUT_HIGH asserts the reset GPIO if it was in a different state before. We keep the chip in reset for 3us, de-assert the reset, and then wait for ~1.5ms until the chip is back up. This is a common pattern seen in various drivers.
Quoted 17 lines
What are you delaying for exactly?
Quoted 13 lines
drivers/leds/leds-pca995x.c +4
-0+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/
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/
PATCH v3 7/10 leds: pca995x: Make output gain configurable
In reply to Nora SchifferThe 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 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-)
drivers/leds/leds-pca995x.c +7 -1
@@ -127,6 +127,7 @@ static int pca995x_probe(struct i2c_client *client)struct pca995x_chip *chip;struct pca995x_led *led;int i, reg, ret; +u32 iref; chipdef =device_get_match_data (&client->dev);
@@ -154,6 +155,11 @@ static int pca995x_probe(struct i2c_client *client)i2c_set_clientdata (client, chip); +if (device_property_read_u32 (dev,"nxp,output-gain" , &iref)) + iref =PCA995X_IREFALL_HALF_CFG ; +else if (iref >PCA995X_IREFALL_FULL_CFG ) +return dev_err_probe (dev, -EINVAL ,"invalid nxp,output-gain\n" ); +device_for_each_child_node_scoped (dev, child) { ret =fwnode_property_read_u32 (child,"reg" , ®);if (ret)
@@ -178,7 +184,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/
[email protected]
AI reviewRe: [PATCH v3 07/10] 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_extresistor 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.Signed-off-by Nora Schiffer <[email protected]>
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
PATCH v3 8/10 leds: pca995x: Add sysfs files for error reporting
In reply to Nora SchifferThe PCA995x has 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.
--- drivers/leds/leds-pca995x.c | 112 +++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-)
drivers/leds/leds-pca995x.c +111 -1
@@ -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,12 @@/* Auto-increment disabled. Normal mode */ #define PCA995X_MODE1_CFG 0x00 +#define PCA995X_MODE2_CLRERR BIT(4) +#define PCA995X_MODE2_ERROR BIT(6) + +/* Clear errors on probe, group brightness control, linear adjustment */ +#define PCA995X_MODE2_CFG PCA995X_MODE2_CLRERR +/* LED select registers determine the source that drives LED outputs */ #define PCA995X_LED_OFF 0x0#define PCA995X_LED_ON 0x1
@@ -37,30 +44,37 @@#define PCA995X_IREFALL_FULL_CFG 0xFF#define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2) +#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; };static const struct pca995x_chipdef pca9952_chipdef = { .num_leds =16 , .pwm_base =0x0a , .irefall =0x43 , + .eflag_base =0x44 , };static const struct pca995x_chipdef pca9955b_chipdef = { .num_leds =16 , .pwm_base =0x08 , .irefall =0x45 , + .eflag_base =0x46 , };static const struct pca995x_chipdef pca9956b_chipdef = { .num_leds =24 , .pwm_base =0x0a , .irefall =0x40 , + .eflag_base =0x41 , };struct pca995x_led {
@@ -112,6 +126,83 @@ 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 DEVICE_ATTR_RO (status); + +static struct attribute *pca995x_led_attrs[] = { + &dev_attr_status.attr, +NULL , +}; +ATTRIBUTE_GROUPS (pca995x_led); + +static ssize_t has_errors_show (struct device *dev,struct device_attribute *attr,char *buf) +{ +struct pca995x_chip *chip =i2c_get_clientdata (to_i2c_client (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 =i2c_get_clientdata (to_i2c_client (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_attrs[] = { + &dev_attr_has_errors.attr, +NULL , +}; +ATTRIBUTE_GROUPS (pca995x); +static const struct regmap_config pca995x_regmap = { .reg_bits =8 , .val_bits =8 ,
@@ -176,6 +267,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 = pca995x_led_groups; }/* Disable LED all-call address and set normal mode */
@@ -183,11 +275,20 @@ 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 ,PCA995X_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) goto err_put_nodes; + ret =device_add_groups (dev, pca995x_groups); +if (ret) + goto err_put_nodes; +for (i =0 ; i < chipdef->num_leds; i++) {struct led_init_data init_data = {};
@@ -202,7 +303,7 @@ static int pca995x_probe(struct i2c_client *client)if (ret <0 ) {dev_err_probe (dev, ret,"Could not register LED %s\n" , chip->leds[i].ldev.name); - goto err_put_nodes; + goto err_remove_groups; } led_fwnodes[i] =NULL ;
@@ -210,6 +311,9 @@ static int pca995x_probe(struct i2c_client *client)return 0 ; +err_remove_groups: +device_remove_groups (dev, pca995x_groups); + err_put_nodes:for (i =0 ; i < chipdef->num_leds; i++)fwnode_handle_put (led_fwnodes[i]);
@@ -217,6 +321,11 @@ static int pca995x_probe(struct i2c_client *client)return ret; } +static void pca995x_remove (struct i2c_client *client) +{ +device_remove_groups (&client->dev, pca995x_groups); +} +static const struct i2c_device_id pca995x_id[] = { { .name ="pca9952" , .driver_data = (kernel_ulong_t)&pca9952_chipdef }, { .name ="pca9955b" , .driver_data = (kernel_ulong_t)&pca9955b_chipdef },
@@ -239,6 +348,7 @@ static struct i2c_driver pca995x_driver = { .of_match_table = pca995x_of_match, }, .probe = pca995x_probe, + .remove = pca995x_remove, .id_table = pca995x_id, };module_i2c_driver (pca995x_driver);
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/
[email protected]
AI reviewRe: [PATCH v3 08/10] leds: pca995x: Add sysfs files for error reporting
Sashiko has reviewed this patch and found no issues. It looks great!
Quoted 6 lines
The PCA995x has 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_errorsis sticky and must be cleared by writing "clear" to the sysfs file.Signed-off-by Nora Schiffer <[email protected]>
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
Lee Jones
maintainerRe: [PATCH v3 08/10] leds: pca995x: Add sysfs files for error reporting
In reply to Nora SchifferQuoted 110 lines · Nora Schiffer
The PCA995x has 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_errorsis sticky and must be cleared by writing "clear" to the sysfs file.Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 112 +++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-)drivers/leds/leds-pca995x.c +47
-0@@ -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,12 @@/* Auto-increment disabled. Normal mode */ #define PCA995X_MODE1_CFG 0x00 +#define PCA995X_MODE2_CLRERR BIT(4) +#define PCA995X_MODE2_ERROR BIT(6) + +/* Clear errors on probe, group brightness control, linear adjustment */ +#define PCA995X_MODE2_CFG PCA995X_MODE2_CLRERR +/* LED select registers determine the source that drives LED outputs */ #define PCA995X_LED_OFF 0x0#define PCA995X_LED_ON 0x1@@ -37,30 +44,37 @@#define PCA995X_IREFALL_FULL_CFG 0xFF#define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2) +#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; };static const struct pca995x_chipdef pca9952_chipdef = { .num_leds =16 , .pwm_base =0x0a , .irefall =0x43 , + .eflag_base =0x44 , };static const struct pca995x_chipdef pca9955b_chipdef = { .num_leds =16 , .pwm_base =0x08 , .irefall =0x45 , + .eflag_base =0x46 , };static const struct pca995x_chipdef pca9956b_chipdef = { .num_leds =24 , .pwm_base =0x0a , .irefall =0x40 , + .eflag_base =0x41 , };struct pca995x_led {@@ -112,6 +126,83 @@ 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 DEVICE_ATTR_RO (status);
drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 171
Quoted 10 lines
drivers/leds/leds-pca995x.c +10
-0+ +static struct attribute *pca995x_led_attrs[] = { + &dev_attr_status.attr, +NULL , +}; +ATTRIBUTE_GROUPS (pca995x_led); + +static ssize_t has_errors_show (struct device *dev,struct device_attribute *attr,char *buf) +{ +struct pca995x_chip *chip =i2c_get_clientdata (to_i2c_client (dev));
Why not 'dev_get_drvdata(dev)' instead of bouncing through the i2c_client?
drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 179
Quoted 8 lines
drivers/leds/leds-pca995x.c +8
-0+unsigned int val; +int ret; + + ret =regmap_read (chip->regmap,PCA995X_MODE2 , &val); +if (ret) +return ret; + +
Stray blank line.
drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 186
Quoted 7 lines
drivers/leds/leds-pca995x.c +7
-0+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 =i2c_get_clientdata (to_i2c_client (dev));
As above.
Quoted 35 lines
drivers/leds/leds-pca995x.c +21
-0+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_attrs[] = { + &dev_attr_has_errors.attr, +NULL , +}; +ATTRIBUTE_GROUPS (pca995x); +static const struct regmap_config pca995x_regmap = { .reg_bits =8 , .val_bits =8 ,@@ -176,6 +267,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 = pca995x_led_groups; }/* Disable LED all-call address and set normal mode */ @@ -183,11 +275,20 @@ static int pca995x_probe(struct i2c_client *client)if (ret) goto err_put_nodes; +/* Clear errors on probe */
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 288
Quoted 10 lines
drivers/leds/leds-pca995x.c +5
-0+ ret =regmap_write (chip->regmap,PCA995X_MODE2 ,PCA995X_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) goto err_put_nodes; + ret =device_add_groups (dev, pca995x_groups);
Why not devm_* and remove the clean-up?
Quoted 16 lines
drivers/leds/leds-pca995x.c +4
-1+if (ret) + goto err_put_nodes; +for (i =0 ; i < chipdef->num_leds; i++) {struct led_init_data init_data = {};@@ -202,7 +303,7 @@ static int pca995x_probe(struct i2c_client *client)if (ret <0 ) {dev_err_probe (dev, ret,"Could not register LED %s\n" , chip->leds[i].ldev.name); - goto err_put_nodes; + goto err_remove_groups; } led_fwnodes[i] =NULL ;
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 316
Quoted 6 lines
drivers/leds/leds-pca995x.c +3
-0@@ -210,6 +311,9 @@ static int pca995x_probe(struct i2c_client *client)return 0 ; +err_remove_groups: +device_remove_groups (dev, pca995x_groups); +
You can remove this if you move to devm_*.
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 328
Quoted 12 lines
drivers/leds/leds-pca995x.c +5
-0err_put_nodes:for (i =0 ; i < chipdef->num_leds; i++)fwnode_handle_put (led_fwnodes[i]);@@ -217,6 +321,11 @@ static int pca995x_probe(struct i2c_client *client)return ret; } +static void pca995x_remove (struct i2c_client *client) +{ +device_remove_groups (&client->dev, pca995x_groups); +} +
As above.
Quoted 18 lines
drivers/leds/leds-pca995x.c +1
-0static const struct i2c_device_id pca995x_id[] = { { .name ="pca9952" , .driver_data = (kernel_ulong_t)&pca9952_chipdef }, { .name ="pca9955b" , .driver_data = (kernel_ulong_t)&pca9955b_chipdef },@@ -239,6 +348,7 @@ static struct i2c_driver pca995x_driver = { .of_match_table = pca995x_of_match, }, .probe = pca995x_probe, + .remove = pca995x_remove, .id_table = pca995x_id, };module_i2c_driver (pca995x_driver);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
Re: [PATCH v3 08/10] leds: pca995x: Add sysfs files for error reporting
Quoted 190 lines · Lee Jones
Quoted 110 lines · Nora Schiffer
The PCA995x has 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_errorsis sticky and must be cleared by writing "clear" to the sysfs file.Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 112 +++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-)drivers/leds/leds-pca995x.c +47
-0@@ -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,12 @@/* Auto-increment disabled. Normal mode */ #define PCA995X_MODE1_CFG 0x00 +#define PCA995X_MODE2_CLRERR BIT(4) +#define PCA995X_MODE2_ERROR BIT(6) + +/* Clear errors on probe, group brightness control, linear adjustment */ +#define PCA995X_MODE2_CFG PCA995X_MODE2_CLRERR +/* LED select registers determine the source that drives LED outputs */ #define PCA995X_LED_OFF 0x0#define PCA995X_LED_ON 0x1@@ -37,30 +44,37 @@#define PCA995X_IREFALL_FULL_CFG 0xFF#define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2) +#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; };static const struct pca995x_chipdef pca9952_chipdef = { .num_leds =16 , .pwm_base =0x0a , .irefall =0x43 , + .eflag_base =0x44 , };static const struct pca995x_chipdef pca9955b_chipdef = { .num_leds =16 , .pwm_base =0x08 , .irefall =0x45 , + .eflag_base =0x46 , };static const struct pca995x_chipdef pca9956b_chipdef = { .num_leds =24 , .pwm_base =0x0a , .irefall =0x40 , + .eflag_base =0x41 , };struct pca995x_led {@@ -112,6 +126,83 @@ 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 DEVICE_ATTR_RO (status);drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 171
Quoted 10 lines
drivers/leds/leds-pca995x.c +10
-0+ +static struct attribute *pca995x_led_attrs[] = { + &dev_attr_status.attr, +NULL , +}; +ATTRIBUTE_GROUPS (pca995x_led); + +static ssize_t has_errors_show (struct device *dev,struct device_attribute *attr,char *buf) +{ +struct pca995x_chip *chip =i2c_get_clientdata (to_i2c_client (dev));Why not '
dev_get_drvdata(dev)' instead of bouncing through thei2c_client?drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 179
Quoted 8 lines
drivers/leds/leds-pca995x.c +8
-0+unsigned int val; +int ret; + + ret =regmap_read (chip->regmap,PCA995X_MODE2 , &val); +if (ret) +return ret; + +Stray blank line.
drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 186
Quoted 7 lines
drivers/leds/leds-pca995x.c +7
-0+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 =i2c_get_clientdata (to_i2c_client (dev));As above.
Quoted 44 lines
drivers/leds/leds-pca995x.c +25
-0+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_attrs[] = { + &dev_attr_has_errors.attr, +NULL , +}; +ATTRIBUTE_GROUPS (pca995x); +static const struct regmap_config pca995x_regmap = { .reg_bits =8 , .val_bits =8 ,@@ -176,6 +267,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 = pca995x_led_groups; }/* Disable LED all-call address and set normal mode */ @@ -183,11 +275,20 @@ 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 ,PCA995X_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) goto err_put_nodes;
Quoted 3 lines
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 288
Quoted 1 line
drivers/leds/leds-pca995x.c +1
-0+ ret =device_add_groups (dev, pca995x_groups);Why not devm_* and remove the clean-up?
Ahh, the AI had suggested this too, but it was referring to a non-exisitent API. I didn't know the function was called devm_device_add_group() - found it now, will fix in the next revision.
Quoted 60 lines
Quoted 17 lines
drivers/leds/leds-pca995x.c +4
-1+if (ret) + goto err_put_nodes; +for (i =0 ; i < chipdef->num_leds; i++) {struct led_init_data init_data = {};@@ -202,7 +303,7 @@ static int pca995x_probe(struct i2c_client *client)if (ret <0 ) {dev_err_probe (dev, ret,"Could not register LED %s\n" , chip->leds[i].ldev.name); - goto err_put_nodes; + goto err_remove_groups; } led_fwnodes[i] =NULL ;drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 316
Quoted 6 lines
drivers/leds/leds-pca995x.c +3
-0@@ -210,6 +311,9 @@ static int pca995x_probe(struct i2c_client *client)return 0 ; +err_remove_groups: +device_remove_groups (dev, pca995x_groups); +You can remove this if you move to devm_*.
drivers/leds/leds-pca995x.c static int pca995x_probe(struct i2c_client *client) · line 328
Quoted 12 lines
drivers/leds/leds-pca995x.c +5
-0err_put_nodes:for (i =0 ; i < chipdef->num_leds; i++)fwnode_handle_put (led_fwnodes[i]);@@ -217,6 +321,11 @@ static int pca995x_probe(struct i2c_client *client)return ret; } +static void pca995x_remove (struct i2c_client *client) +{ +device_remove_groups (&client->dev, pca995x_groups); +} +As above.
Quoted 18 lines
drivers/leds/leds-pca995x.c +1
-0static const struct i2c_device_id pca995x_id[] = { { .name ="pca9952" , .driver_data = (kernel_ulong_t)&pca9952_chipdef }, { .name ="pca9955b" , .driver_data = (kernel_ulong_t)&pca9955b_chipdef },@@ -239,6 +348,7 @@ static struct i2c_driver pca995x_driver = { .of_match_table = pca995x_of_match, }, .probe = pca995x_probe, + .remove = pca995x_remove, .id_table = pca995x_id, };module_i2c_driver (pca995x_driver);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/
PATCH v3 9/10 leds: pca995x: Do not use full on LED mode
In reply to Nora SchifferLEDs 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
@@ -102,28 +102,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/
[email protected]
AI reviewRe: [PATCH v3 09/10] 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.
Signed-off-by Nora Schiffer <[email protected]>
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
PATCH v3 10/10 leds: pca995x: Add support for group brightness control
In reply to Nora SchifferWhen 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 | 46 ++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-)
drivers/leds/leds-pca995x.c +43 -3
@@ -35,6 +35,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
@@ -52,6 +53,7 @@struct pca995x_chipdef {unsigned int num_leds;u8 pwm_base; +u8 grppwm;u8 irefall;u8 eflag_base; };
@@ -59,6 +61,7 @@ struct pca995x_chipdef {static const struct pca995x_chipdef pca9952_chipdef = { .num_leds =16 , .pwm_base =0x0a , + .grppwm =0x08 , .irefall =0x43 , .eflag_base =0x44 , };
@@ -66,6 +69,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 , };
@@ -73,6 +77,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 , };
@@ -114,11 +119,10 @@ 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 status_show (struct device *dev,struct device_attribute *attr,char *buf)
@@ -190,10 +194,41 @@ static ssize_t has_errors_store(struct device *dev, struct device_attribute *attreturn ret ?: count; } +static ssize_t group_brightness_show (struct device *dev,struct device_attribute *attr,char *buf) +{ +struct pca995x_chip *chip =i2c_get_clientdata (to_i2c_client (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 =i2c_get_clientdata (to_i2c_client (dev)); +u8 val; +int ret; + + 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 (has_errors); +static DEVICE_ATTR_RW (group_brightness);static struct attribute *pca995x_attrs[] = { &dev_attr_has_errors.attr, + &dev_attr_group_brightness.attr,NULL , };ATTRIBUTE_GROUPS (pca995x);
@@ -270,11 +305,16 @@ static int pca995x_probe(struct i2c_client *client)if (ret) goto err_put_nodes; -/* Clear errors on probe */ +/* Clear errors on probe, use GRPPWM register for group brightness control */ ret =regmap_write (chip->regmap,PCA995X_MODE2 ,PCA995X_MODE2_CFG );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)
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/
[email protected]
AI reviewRe: [PATCH v3 10/10] 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.
Signed-off-by Nora Schiffer <[email protected]>
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
Lee Jones
maintainerRe: [PATCH v3 10/10] leds: pca995x: Add support for group brightness control
In reply to Nora Schiffer--- checkpatch.pl: clean (0 issues) ---
Quoted 63 lines · 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.
Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 46 ++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-)drivers/leds/leds-pca995x.c +5
-0@@ -35,6 +35,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@@ -52,6 +53,7 @@struct pca995x_chipdef {unsigned int num_leds;u8 pwm_base; +u8 grppwm;u8 irefall;u8 eflag_base; };@@ -59,6 +61,7 @@ struct pca995x_chipdef {static const struct pca995x_chipdef pca9952_chipdef = { .num_leds =16 , .pwm_base =0x0a , + .grppwm =0x08 , .irefall =0x43 , .eflag_base =0x44 , };@@ -66,6 +69,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 , };@@ -73,6 +77,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 , };
drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 125
Quoted 9 lines
drivers/leds/leds-pca995x.c +1
-2@@ -114,11 +119,10 @@ 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);
If we use group PWM mode to scale brightness, what happens when 'brightness' is 'LED_FULL'? The 'switch' statement above sets 'PCA995X_LED_ON', which typically bypasses PWM entirely. Won't this cause an LED set to 255 to ignore the group brightness and be fully on, while an LED at 254 is scaled down?
drivers/leds/leds-pca995x.c static ssize_t has_errors_store(struct device *dev, struct device_attribute *att · line 199
Quoted 10 lines
drivers/leds/leds-pca995x.c +3
-0}static ssize_t status_show (struct device *dev,struct device_attribute *attr,char *buf)@@ -190,10 +194,41 @@ static ssize_t has_errors_store(struct device *dev, struct device_attribute *attreturn ret ?: count; } +static ssize_t group_brightness_show (struct device *dev,struct device_attribute *attr,char *buf) +{ +struct pca995x_chip *chip =i2c_get_clientdata (to_i2c_client (dev));
'dev_get_drvdata(dev)' again?
Quoted 60 lines
drivers/leds/leds-pca995x.c +34
-1+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 =i2c_get_clientdata (to_i2c_client (dev)); +u8 val; +int ret; + + 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 (has_errors); +static DEVICE_ATTR_RW (group_brightness);static struct attribute *pca995x_attrs[] = { &dev_attr_has_errors.attr, + &dev_attr_group_brightness.attr,NULL , };ATTRIBUTE_GROUPS (pca995x);@@ -270,11 +305,16 @@ static int pca995x_probe(struct i2c_client *client)if (ret) goto err_put_nodes; -/* Clear errors on probe */ +/* Clear errors on probe, use GRPPWM register for group brightness control */ ret =regmap_write (chip->regmap,PCA995X_MODE2 ,PCA995X_MODE2_CFG );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)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
Re: [PATCH v3 10/10] leds: pca995x: Add support for group brightness control
Quoted 68 lines · Lee Jones
--- checkpatch.pl: clean (0 issues) ---
Quoted 64 lines · 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.
Signed-off-by Nora Schiffer <[email protected]>--- drivers/leds/leds-pca995x.c | 46 ++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-)drivers/leds/leds-pca995x.c +5
-0@@ -35,6 +35,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@@ -52,6 +53,7 @@struct pca995x_chipdef {unsigned int num_leds;u8 pwm_base; +u8 grppwm;u8 irefall;u8 eflag_base; };@@ -59,6 +61,7 @@ struct pca995x_chipdef {static const struct pca995x_chipdef pca9952_chipdef = { .num_leds =16 , .pwm_base =0x0a , + .grppwm =0x08 , .irefall =0x43 , .eflag_base =0x44 , };@@ -66,6 +69,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 , };@@ -73,6 +77,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 , };@@ -114,11 +119,10 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
Quoted 13 lines
drivers/leds/leds-pca995x.c static int pca995x_brightness_set(struct led_classdev *led_cdev, · line 125
Quoted 8 lines
drivers/leds/leds-pca995x.c +1
-2/* * 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);If we use group PWM mode to scale brightness, what happens when 'brightness' is '
LED_FULL'? The 'switch' statement above sets 'PCA995X_LED_ON', which typically bypasses PWM entirely. Won't this cause an LED set to 255 to ignore the group brightness and be fully on, while an LED at 254 is scaled down?
Patch 9/10 removes the use of PCA995X_LED_ON to avoid this issue, using PCA995X_LED_PWM_MODE_GRP for all brightness values 1..255 instead.
Quoted 75 lines
drivers/leds/leds-pca995x.c static ssize_t has_errors_store(struct device *dev, struct device_attribute *att · line 199
Quoted 11 lines
drivers/leds/leds-pca995x.c +3
-0}static ssize_t status_show (struct device *dev,struct device_attribute *attr,char *buf)@@ -190,10 +194,41 @@ static ssize_t has_errors_store(struct device *dev, struct device_attribute *attreturn ret ?: count; } +static ssize_t group_brightness_show (struct device *dev,struct device_attribute *attr,char *buf) +{ +struct pca995x_chip *chip =i2c_get_clientdata (to_i2c_client (dev));'
dev_get_drvdata(dev)' again?Quoted 60 lines
drivers/leds/leds-pca995x.c +34
-1+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 =i2c_get_clientdata (to_i2c_client (dev)); +u8 val; +int ret; + + 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 (has_errors); +static DEVICE_ATTR_RW (group_brightness);static struct attribute *pca995x_attrs[] = { &dev_attr_has_errors.attr, + &dev_attr_group_brightness.attr,NULL , };ATTRIBUTE_GROUPS (pca995x);@@ -270,11 +305,16 @@ static int pca995x_probe(struct i2c_client *client)if (ret) goto err_put_nodes; -/* Clear errors on probe */ +/* Clear errors on probe, use GRPPWM register for group brightness control */ ret =regmap_write (chip->regmap,PCA995X_MODE2 ,PCA995X_MODE2_CFG );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)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/