[PATCH 7/7] leds: pca995x: add support for group brightness control

From: Nora Schiffer

Date: Wed Sep 02 2026 - 09:16:41 EST


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 <nora.schiffer@xxxxxxxxxxxxxxx>
---
drivers/leds/leds-pca995x.c | 39 +++++++++++++++++++++++++++++++++++--
1 file changed, 37 insertions(+), 2 deletions(-)

diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c
index 62f2764661b9d..d65c33edc83ab 100644
--- a/drivers/leds/leds-pca995x.c
+++ b/drivers/leds/leds-pca995x.c
@@ -31,6 +31,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

@@ -48,6 +49,7 @@
struct pca995x_chipdef {
unsigned int num_leds;
u8 pwm_base;
+ u8 grppwm;
u8 irefall;
u8 eflag_base;
};
@@ -55,6 +57,7 @@ struct pca995x_chipdef {
static const struct pca995x_chipdef pca9952_chipdef = {
.num_leds = 16,
.pwm_base = 0x0a,
+ .grppwm = 0x08,
.irefall = 0x43,
.eflag_base = 0x44,
};
@@ -62,6 +65,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,
};
@@ -69,6 +73,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,
};
@@ -110,11 +115,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)
@@ -186,10 +190,41 @@ static ssize_t has_errors_store(struct device *dev, struct device_attribute *att
return 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);
--
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/