* [PATCH] leds: pca9532: Propagate I2C errors
@ 2026-11-14 14:19 Daniil Demakov
2026-11-14 14:32 ` sashiko-bot
2026-11-14 17:24 ` [PATCH v2] " Daniil Demakov
0 siblings, 2 replies; 5+ messages in thread
From: Daniil Demakov @ 2026-11-14 14:19 UTC (permalink / raw)
To: linux-leds
Cc: lee, pavel, linusw, riku.voipio, brgl, griffin, linux-kernel,
linux-gpio, Daniil Demakov
Check return values from SMBus read and write operations and
propagate errors through LED, PWM, blink and GPIO operations.
Also check register writes during device initialization and
report failures instead of continuing with an incorrectly
configured device.
Signed-off-by: Daniil Demakov <t3127430@gmail.com>
---
drivers/leds/leds-pca9532.c | 185 +++++++++++++++++++++++++++---------
1 file changed, 139 insertions(+), 46 deletions(-)
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index c8085427696f..335795520490 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -148,32 +148,66 @@ static int pca9532_setpwm(struct i2c_client *client, int pwm)
{
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
+ int ret;
+
+ if (pwm < 0 || pwm > 1)
+ return -EINVAL;
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, pwm),
+ ret = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, pwm),
data->pwm[pwm]);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, pwm),
+
+ if (ret < 0) {
+ dev_warn(&client->dev, "Failed to write PWM register: %d\n", ret);
+ goto out;
+ }
+
+ ret = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, pwm),
data->psc[pwm]);
+
+ if (ret < 0) {
+ dev_warn(&client->dev, "Failed to write PSC register: %d\n", ret);
+ goto out;
+ }
+
+out:
mutex_unlock(&data->update_lock);
- return 0;
+ return ret;
}
/* Set LED routing */
-static void pca9532_setled(struct pca9532_led *led)
+static int pca9532_setled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
- char reg;
-
+ int reg;
+ int ret;
mutex_lock(&data->update_lock);
+
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
+ if (reg < 0) {
+ ret = reg;
+ dev_warn(&client->dev, "Failed to read LED register: %d\n", ret);
+ goto out;
+ }
+
/* zero led bits */
- reg = reg & ~LED_MASK(led->id);
+ reg &= ~LED_MASK(led->id);
+
/* set the new value */
- reg = reg | (led->state << LED_SHIFT(led->id));
- i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
+ reg |= led->state << LED_SHIFT(led->id);
+
+ ret = i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
+
+ if (ret < 0) {
+ dev_warn(&client->dev, "Failed to write LED register: %d\n", ret);
+ goto out;
+ }
+
+out:
mutex_unlock(&data->update_lock);
+ return ret;
}
static int pca9532_set_brightness(struct led_classdev *led_cdev,
@@ -194,9 +228,13 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev,
if (err)
return err;
}
- if (led->state == PCA9532_PWM0)
- pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
- pca9532_setled(led);
+
+ if (led->state == PCA9532_PWM0) {
+ err = pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
+ if (err < 0)
+ return err;
+ }
+ err = pca9532_setled(led);
return err;
}
@@ -205,6 +243,7 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
{
struct pca9532_data *data = i2c_get_clientdata(led->client);
unsigned int psc;
+ unsigned long period;
int i;
/* Look for others LEDs that already use PWM1 */
@@ -223,15 +262,28 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
}
}
- psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
- if (psc > U8_MAX) {
- /* Blink period too long to be handled by hardware */
+ if (delay_on > ULONG_MAX - delay_off)
+ return -EINVAL;
+
+ period = delay_on + delay_off;
+
+ if (!period)
+ return -EINVAL;
+
+ if (period > ((U8_MAX + 1) * 1000) /
+ PCA9532_PWM_PERIOD_DIV) {
+ dev_warn(&led->client->dev,
+ "Blink period too long: %lu ms on, %lu ms off\n",
+ delay_on, delay_off);
return -EINVAL;
}
+ psc = (period * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
+
led->state = PCA9532_PWM1;
data->psc[PCA9532_PWM_ID_1] = psc;
- data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
+ data->pwm[PCA9532_PWM_ID_1] =
+ (delay_on * PCA9532_PWM_DUTY_DIV) / period;
return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
}
@@ -244,8 +296,10 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
struct pca9532_data *data = i2c_get_clientdata(client);
int err;
- if (!data->hw_blink)
+ if (!data->hw_blink) {
+ dev_warn(&client->dev, "Blinking not supported on this device\n");
return -EINVAL;
+ }
if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */
@@ -254,19 +308,18 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
}
err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
- if (err)
+ if (err) {
+ dev_warn(&client->dev, "Failed to set blink: %d\n", err);
return err;
+ }
- pca9532_setled(led);
-
- return 0;
+ return pca9532_setled(led);
}
static int pca9532_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
struct pca9532_data *data = input_get_drvdata(dev);
-
if (!(type == EV_SND && (code == SND_BELL || code == SND_TONE)))
return -1;
@@ -286,27 +339,34 @@ static void pca9532_input_work(struct work_struct *work)
struct pca9532_data *data =
container_of(work, struct pca9532_data, work);
u8 maxleds = data->chip_info->num_leds;
+ int ret;
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
+ ret = i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
data->pwm[PCA9532_PWM_ID_1]);
+ if (ret < 0)
+ dev_warn(&data->client->dev, "Failed to write PWM register: %d\n", ret);
+
mutex_unlock(&data->update_lock);
}
-static enum pca9532_state pca9532_getled(struct pca9532_led *led)
+static int pca9532_getled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
int reg;
- enum pca9532_state ret = PCA9532_OFF;
+ int ret;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
- if (reg >= 0)
+
+ if (reg >= 0) {
ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
- else
- dev_warn(&client->dev, "failed to read LED register: %d\n", reg);
+ } else {
+ ret = reg;
+ dev_warn(&client->dev, "Failed to read LED register: %d\n", ret);
+ }
mutex_unlock(&data->update_lock);
return ret;
}
@@ -334,27 +394,25 @@ static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
else
led->state = PCA9532_ON;
- pca9532_setled(led);
-
- return 0;
+ return pca9532_setled(led);
}
static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
struct pca9532_data *data = gpiochip_get_data(gc);
- unsigned char reg;
+ int reg;
reg = i2c_smbus_read_byte_data(data->client, PCA9532_REG_INPUT(offset));
+ if (reg < 0)
+ return reg;
+
return !!(reg & (1 << (offset % 8)));
}
static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
- /* To use as input ensure pin is not driven */
- pca9532_gpio_set_value(gc, offset, 1);
-
- return 0;
+ return pca9532_gpio_set_value(gc, offset, 1);
}
static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
@@ -394,40 +452,57 @@ static int pca9532_configure(struct i2c_client *client,
struct pca9532_data *data, struct pca9532_platform_data *pdata)
{
int i, err = 0;
+#ifdef CONFIG_LEDS_PCA9532_GPIO
int gpios = 0;
+#endif
u8 maxleds = data->chip_info->num_leds;
- for (i = 0; i < 2; i++) {
+ for (i = 0; i < 2; i++) {
data->pwm[i] = pdata->pwm[i];
data->psc[i] = pdata->psc[i];
- err = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i),
+
+ err = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PWM(maxleds, i),
data->pwm[i]);
if (err < 0)
return err;
- err = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
+
+ err = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PSC(maxleds, i),
data->psc[i]);
if (err < 0)
return err;
}
data->hw_blink = true;
+
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
+
led->client = client;
led->id = i;
led->type = pled->type;
+
switch (led->type) {
case PCA9532_TYPE_NONE:
break;
case PCA9532_TYPE_GPIO:
+#ifdef CONFIG_LEDS_PCA9532_GPIO
gpios++;
+#endif
break;
case PCA9532_TYPE_LED:
- if (pled->state == PCA9532_KEEP)
- led->state = pca9532_getled(led);
- else
+ if (pled->state == PCA9532_KEEP) {
+ err = pca9532_getled(led);
+ if (err < 0)
+ goto exit;
+
+ led->state = err;
+ } else {
led->state = pled->state;
+ }
+
led->name = pled->name;
led->ldev.name = led->name;
led->ldev.default_trigger = pled->default_trigger;
@@ -435,6 +510,7 @@ static int pca9532_configure(struct i2c_client *client,
led->ldev.brightness_set_blocking =
pca9532_set_brightness;
led->ldev.blink_set = pca9532_set_blink;
+
err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) {
dev_err(&client->dev,
@@ -442,19 +518,35 @@ static int pca9532_configure(struct i2c_client *client,
led->name);
goto exit;
}
- pca9532_setled(led);
+
+ err = pca9532_setled(led);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't set LED %s state\n",
+ led->name);
+ led_classdev_unregister(&led->ldev);
+ goto exit;
+ }
break;
case PCA9532_TYPE_N2100_BEEP:
/* PWM1 is reserved for beeper so blink will not use hardware */
data->hw_blink = false;
BUG_ON(data->idev);
+
led->state = PCA9532_PWM1;
- pca9532_setled(led);
+ err = pca9532_setled(led);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't set beeper state\n");
+ goto exit;
+ }
+
data->idev = devm_input_allocate_device(&client->dev);
if (data->idev == NULL) {
err = -ENOMEM;
goto exit;
}
+
data->idev->name = pled->name;
data->idev->phys = "i2c/pca9532";
data->idev->id.bustype = BUS_HOST;
@@ -467,6 +559,7 @@ static int pca9532_configure(struct i2c_client *client,
data->idev->event = pca9532_event;
input_set_drvdata(data->idev, data);
INIT_WORK(&data->work, pca9532_input_work);
+
err = input_register_device(data->idev);
if (err) {
cancel_work_sync(&data->work);
@@ -498,8 +591,8 @@ static int pca9532_configure(struct i2c_client *client,
dev_warn(&client->dev, "could not add gpiochip\n");
} else {
dev_info(&client->dev, "gpios %i...%i\n",
- data->gpio.base, data->gpio.base +
- data->gpio.ngpio - 1);
+ data->gpio.base, data->gpio.base +
+ data->gpio.ngpio - 1);
}
}
#endif
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH] leds: pca9532: Propagate I2C errors
2026-11-14 14:19 [PATCH] leds: pca9532: Propagate I2C errors Daniil Demakov
@ 2026-11-14 14:32 ` sashiko-bot
2026-11-14 17:24 ` [PATCH v2] " Daniil Demakov
1 sibling, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-14 14:32 UTC (permalink / raw)
To: Daniil Demakov, linux-leds; +Cc: linux-gpio, lee
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [Medium] leds: pca9532: unrestored led->state on I2C failure breaks
blink fallback
- [Low] leds: pca9532: dev_warn() log spam on software blink fallback in
pca9532_set_blink()
--
commit 85607316f9a97fea0c814965c666330e54cd0fdc
Author: Daniil Demakov <t3127430@gmail.com>
leds: pca9532: Propagate I2C errors
This commit checks return values from SMBus read and write calls in the
pca9532 driver and propagates errors through LED, PWM, blink, GPIO, and
device initialization paths.
> diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
> index c8085427696ff..3357955204903 100644
> --- a/drivers/leds/leds-pca9532.c
> +++ b/drivers/leds/leds-pca9532.c
[ ... ]
> @@ -223,15 +262,28 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
> }
> }
>
> - psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
> - if (psc > U8_MAX) {
> - /* Blink period too long to be handled by hardware */
> + if (delay_on > ULONG_MAX - delay_off)
> + return -EINVAL;
> +
> + period = delay_on + delay_off;
> +
> + if (!period)
> + return -EINVAL;
> +
> + if (period > ((U8_MAX + 1) * 1000) /
> + PCA9532_PWM_PERIOD_DIV) {
> + dev_warn(&led->client->dev,
> + "Blink period too long: %lu ms on, %lu ms off\n",
> + delay_on, delay_off);
> return -EINVAL;
> }
>
> + psc = (period * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
> +
> led->state = PCA9532_PWM1;
> data->psc[PCA9532_PWM_ID_1] = psc;
> - data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
> + data->pwm[PCA9532_PWM_ID_1] =
> + (delay_on * PCA9532_PWM_DUTY_DIV) / period;
>
> return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
> }
[Severity: Medium]
Does propagating an error from pca9532_setpwm() or pca9532_setled() leave
led->state stuck at PCA9532_PWM1 when hardware blink setup fails?
When pca9532_set_blink() returns an error to led_blink_setup(), the LED core
falls back to software blinking via led_set_software_blink(). Because
pca9532_set_brightness() ignores non-zero brightness updates whenever
led->state == PCA9532_PWM1, software blinking can never turn the LED on, and
pca9532_update_hw_blink() will also treat PWM1 as busy for other LEDs on the
chip.
pca9532_set_brightness() and pca9532_gpio_set_value() similarly overwrite
led->state before pca9532_calcpwm(), pca9532_setpwm(), and pca9532_setled()
succeed, leaving software state out of sync with the hardware registers on
I2C errors.
> @@ -244,8 +296,10 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
> struct pca9532_data *data = i2c_get_clientdata(client);
> int err;
>
> - if (!data->hw_blink)
> + if (!data->hw_blink) {
> + dev_warn(&client->dev, "Blinking not supported on this device\n");
> return -EINVAL;
> + }
>
> if (*delay_on == 0 && *delay_off == 0) {
> /* led subsystem ask us for a blink rate */
> @@ -254,19 +308,18 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
> }
>
> err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
> - if (err)
> + if (err) {
> + dev_warn(&client->dev, "Failed to set blink: %d\n", err);
> return err;
> + }
>
> - pca9532_setled(led);
> -
> - return 0;
> + return pca9532_setled(led);
> }
[Severity: Low]
Should these dev_warn() calls in pca9532_update_hw_blink() and
pca9532_set_blink() be dropped?
In led_blink_setup(), a non-zero return from blink_set() is the normal way to
decline hardware offload and fall back to software blinking. Warning when
!data->hw_blink, when the period exceeds the hardware limit, or whenever
pca9532_update_hw_blink() returns -EINVAL floods the kernel log on normal
software-blink fallback (and duplicates the warning already printed by
pca9532_setpwm() on I2C failure), reintroducing the log noise removed by
2aad93b6de0d ("leds: pca9532: Remove irrelevant blink configuration error
message").
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114141926.92737-1-t3127430@gmail.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2] leds: pca9532: Propagate I2C errors
2026-11-14 14:19 [PATCH] leds: pca9532: Propagate I2C errors Daniil Demakov
2026-11-14 14:32 ` sashiko-bot
@ 2026-11-14 17:24 ` Daniil Demakov
2026-11-14 17:40 ` sashiko-bot
2026-11-14 18:14 ` [PATCH v3] " Daniil Demakov
1 sibling, 2 replies; 5+ messages in thread
From: Daniil Demakov @ 2026-11-14 17:24 UTC (permalink / raw)
To: linux-leds; +Cc: lee, pavel, linusw, Daniil Demakov
Check return values from SMBus read and write operations and
propagate errors through LED, PWM, blink and GPIO operations.
Also check register writes during device initialization and
report failures instead of continuing with an incorrectly
configured device.
Restore the previous LED state when I2C operations fail to
avoid inconsistent software state.
Avoid warnings for unsupported or invalid hardware blink
configurations, since the LED subsystem can fall back to
software blinking.
Signed-off-by: Daniil Demakov <t3127430@gmail.com>
---
drivers/leds/leds-pca9532.c | 216 +++++++++++++++++++++++++++---------
1 file changed, 165 insertions(+), 51 deletions(-)
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index c8085427696f..0856c0665707 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -148,39 +148,75 @@ static int pca9532_setpwm(struct i2c_client *client, int pwm)
{
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
+ int ret;
+
+ if (pwm < 0 || pwm > 1)
+ return -EINVAL;
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, pwm),
- data->pwm[pwm]);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, pwm),
- data->psc[pwm]);
+ret = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PWM(maxleds, pwm),
+ data->pwm[pwm]);
+ if (ret < 0) {
+ dev_warn(&client->dev, "Failed to write PWM register: %d\n", ret);
+ goto out;
+ }
+
+ret = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PSC(maxleds, pwm),
+ data->psc[pwm]);
+
+ if (ret < 0) {
+ dev_warn(&client->dev, "Failed to write PSC register: %d\n", ret);
+ goto out;
+ }
+
+out:
mutex_unlock(&data->update_lock);
- return 0;
+ return ret;
}
/* Set LED routing */
-static void pca9532_setled(struct pca9532_led *led)
+static int pca9532_setled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
- char reg;
-
+ int reg;
+ int ret;
mutex_lock(&data->update_lock);
+
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
+ if (reg < 0) {
+ ret = reg;
+ dev_warn(&client->dev, "Failed to read LED register: %d\n", ret);
+ goto out;
+ }
+
/* zero led bits */
- reg = reg & ~LED_MASK(led->id);
+ reg &= ~LED_MASK(led->id);
+
/* set the new value */
- reg = reg | (led->state << LED_SHIFT(led->id));
- i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
+ reg |= led->state << LED_SHIFT(led->id);
+
+ ret = i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
+
+ if (ret < 0) {
+ dev_warn(&client->dev, "Failed to write LED register: %d\n", ret);
+ goto out;
+ }
+
+out:
mutex_unlock(&data->update_lock);
+ return ret;
}
static int pca9532_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
- int err = 0;
struct pca9532_led *led = ldev_to_led(led_cdev);
+ int old_state = led->state;
+ int err = 0;
if (value == LED_OFF) {
led->state = PCA9532_OFF;
@@ -190,13 +226,26 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev,
led->state = PCA9532_ON;
} else {
led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
- err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value);
+ err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0,
+ 0, value);
if (err)
- return err;
+ goto restore_state;
+ }
+
+ if (led->state == PCA9532_PWM0) {
+ err = pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
+ if (err < 0)
+ goto restore_state;
}
- if (led->state == PCA9532_PWM0)
- pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
- pca9532_setled(led);
+
+ err = pca9532_setled(led);
+ if (err < 0)
+ goto restore_state;
+
+ return 0;
+
+restore_state:
+ led->state = old_state;
return err;
}
@@ -205,7 +254,8 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
{
struct pca9532_data *data = i2c_get_clientdata(led->client);
unsigned int psc;
- int i;
+ unsigned long period;
+ int i, ret;
/* Look for others LEDs that already use PWM1 */
for (i = 0; i < data->chip_info->num_leds; i++) {
@@ -223,17 +273,32 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
}
}
- psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
- if (psc > U8_MAX) {
- /* Blink period too long to be handled by hardware */
+ if (delay_on > ULONG_MAX - delay_off)
+ return -EINVAL;
+
+ period = delay_on + delay_off;
+
+ if (!period)
+ return -EINVAL;
+
+ if (period > ((U8_MAX + 1) * 1000) /
+ PCA9532_PWM_PERIOD_DIV) {
return -EINVAL;
}
- led->state = PCA9532_PWM1;
+ psc = (period * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
+
data->psc[PCA9532_PWM_ID_1] = psc;
- data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
+ data->pwm[PCA9532_PWM_ID_1] =
+ (delay_on * PCA9532_PWM_DUTY_DIV) / period;
+
+ ret = pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
+ if (ret < 0)
+ return ret;
- return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
+ led->state = PCA9532_PWM1;
+
+ return 0;
}
static int pca9532_set_blink(struct led_classdev *led_cdev,
@@ -242,6 +307,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
struct pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
+ int old_state = led->state;
int err;
if (!data->hw_blink)
@@ -257,16 +323,17 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
if (err)
return err;
- pca9532_setled(led);
+ err = pca9532_setled(led);
+ if (err < 0)
+ led->state = old_state;
- return 0;
+ return err;
}
static int pca9532_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
struct pca9532_data *data = input_get_drvdata(dev);
-
if (!(type == EV_SND && (code == SND_BELL || code == SND_TONE)))
return -1;
@@ -286,27 +353,35 @@ static void pca9532_input_work(struct work_struct *work)
struct pca9532_data *data =
container_of(work, struct pca9532_data, work);
u8 maxleds = data->chip_info->num_leds;
+ int ret;
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
- data->pwm[PCA9532_PWM_ID_1]);
+ret = i2c_smbus_write_byte_data(data->client,
+ PCA9532_REG_PWM(maxleds, 1),
+ data->pwm[PCA9532_PWM_ID_1]);
+ if (ret < 0)
+ dev_warn(&data->client->dev, "Failed to write PWM register: %d\n", ret);
+
mutex_unlock(&data->update_lock);
}
-static enum pca9532_state pca9532_getled(struct pca9532_led *led)
+static int pca9532_getled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
int reg;
- enum pca9532_state ret = PCA9532_OFF;
+ int ret;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
- if (reg >= 0)
+
+ if (reg >= 0) {
ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
- else
- dev_warn(&client->dev, "failed to read LED register: %d\n", reg);
+ } else {
+ ret = reg;
+ dev_warn(&client->dev, "Failed to read LED register: %d\n", ret);
+ }
mutex_unlock(&data->update_lock);
return ret;
}
@@ -328,33 +403,37 @@ static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
{
struct pca9532_data *data = gpiochip_get_data(gc);
struct pca9532_led *led = &data->leds[offset];
+ int old_state = led->state;
+ int ret;
if (val)
led->state = PCA9532_OFF;
else
led->state = PCA9532_ON;
- pca9532_setled(led);
+ ret = pca9532_setled(led);
+ if (ret < 0)
+ led->state = old_state;
- return 0;
+ return ret;
}
static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
struct pca9532_data *data = gpiochip_get_data(gc);
- unsigned char reg;
+ int reg;
reg = i2c_smbus_read_byte_data(data->client, PCA9532_REG_INPUT(offset));
+ if (reg < 0)
+ return reg;
+
return !!(reg & (1 << (offset % 8)));
}
static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
- /* To use as input ensure pin is not driven */
- pca9532_gpio_set_value(gc, offset, 1);
-
- return 0;
+ return pca9532_gpio_set_value(gc, offset, 1);
}
static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
@@ -394,40 +473,57 @@ static int pca9532_configure(struct i2c_client *client,
struct pca9532_data *data, struct pca9532_platform_data *pdata)
{
int i, err = 0;
+#ifdef CONFIG_LEDS_PCA9532_GPIO
int gpios = 0;
+#endif
u8 maxleds = data->chip_info->num_leds;
- for (i = 0; i < 2; i++) {
+ for (i = 0; i < 2; i++) {
data->pwm[i] = pdata->pwm[i];
data->psc[i] = pdata->psc[i];
- err = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i),
+
+ err = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PWM(maxleds, i),
data->pwm[i]);
if (err < 0)
return err;
- err = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
+
+ err = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PSC(maxleds, i),
data->psc[i]);
if (err < 0)
return err;
}
data->hw_blink = true;
+
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
+
led->client = client;
led->id = i;
led->type = pled->type;
+
switch (led->type) {
case PCA9532_TYPE_NONE:
break;
case PCA9532_TYPE_GPIO:
+#ifdef CONFIG_LEDS_PCA9532_GPIO
gpios++;
+#endif
break;
case PCA9532_TYPE_LED:
- if (pled->state == PCA9532_KEEP)
- led->state = pca9532_getled(led);
- else
+ if (pled->state == PCA9532_KEEP) {
+ err = pca9532_getled(led);
+ if (err < 0)
+ goto exit;
+
+ led->state = err;
+ } else {
led->state = pled->state;
+ }
+
led->name = pled->name;
led->ldev.name = led->name;
led->ldev.default_trigger = pled->default_trigger;
@@ -435,6 +531,7 @@ static int pca9532_configure(struct i2c_client *client,
led->ldev.brightness_set_blocking =
pca9532_set_brightness;
led->ldev.blink_set = pca9532_set_blink;
+
err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) {
dev_err(&client->dev,
@@ -442,19 +539,35 @@ static int pca9532_configure(struct i2c_client *client,
led->name);
goto exit;
}
- pca9532_setled(led);
+
+ err = pca9532_setled(led);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't set LED %s state\n",
+ led->name);
+ led_classdev_unregister(&led->ldev);
+ goto exit;
+ }
break;
case PCA9532_TYPE_N2100_BEEP:
/* PWM1 is reserved for beeper so blink will not use hardware */
data->hw_blink = false;
BUG_ON(data->idev);
+
led->state = PCA9532_PWM1;
- pca9532_setled(led);
+ err = pca9532_setled(led);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't set beeper state\n");
+ goto exit;
+ }
+
data->idev = devm_input_allocate_device(&client->dev);
if (data->idev == NULL) {
err = -ENOMEM;
goto exit;
}
+
data->idev->name = pled->name;
data->idev->phys = "i2c/pca9532";
data->idev->id.bustype = BUS_HOST;
@@ -467,6 +580,7 @@ static int pca9532_configure(struct i2c_client *client,
data->idev->event = pca9532_event;
input_set_drvdata(data->idev, data);
INIT_WORK(&data->work, pca9532_input_work);
+
err = input_register_device(data->idev);
if (err) {
cancel_work_sync(&data->work);
@@ -498,8 +612,8 @@ static int pca9532_configure(struct i2c_client *client,
dev_warn(&client->dev, "could not add gpiochip\n");
} else {
dev_info(&client->dev, "gpios %i...%i\n",
- data->gpio.base, data->gpio.base +
- data->gpio.ngpio - 1);
+ data->gpio.base, data->gpio.base +
+ data->gpio.ngpio - 1);
}
}
#endif
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2] leds: pca9532: Propagate I2C errors
2026-11-14 17:24 ` [PATCH v2] " Daniil Demakov
@ 2026-11-14 17:40 ` sashiko-bot
2026-11-14 18:14 ` [PATCH v3] " Daniil Demakov
1 sibling, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-14 17:40 UTC (permalink / raw)
To: Daniil Demakov, linux-leds; +Cc: lee
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] leds: pca9532: u8 duty-cycle truncation in
pca9532_update_hw_blink()
- [Medium] leds: pca9532: unlocked state updates and rollbacks outside
update_lock
- [Low] leds: pca9532: LED registered before hardware init in
pca9532_configure()
--
commit 97438085718f3f15d211a12a646ef74e8799f047
Author: Daniil Demakov <t3127430@gmail.com>
leds: pca9532: Propagate I2C errors
This commit checks SMBus read and write return values across LED, PWM,
blink, GPIO, and initialization paths, and restores the previous LED state
when I2C transfers fail.
> diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
> index c8085427696ff..0856c06657071 100644
> --- a/drivers/leds/leds-pca9532.c
> +++ b/drivers/leds/leds-pca9532.c
[ ... ]
> @@ -223,17 +273,32 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
> }
> }
>
> - psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
> - if (psc > U8_MAX) {
> - /* Blink period too long to be handled by hardware */
> + if (delay_on > ULONG_MAX - delay_off)
> + return -EINVAL;
> +
> + period = delay_on + delay_off;
> +
> + if (!period)
> + return -EINVAL;
> +
> + if (period > ((U8_MAX + 1) * 1000) /
> + PCA9532_PWM_PERIOD_DIV) {
> return -EINVAL;
> }
>
> - led->state = PCA9532_PWM1;
> + psc = (period * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
> +
> data->psc[PCA9532_PWM_ID_1] = psc;
> - data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
> + data->pwm[PCA9532_PWM_ID_1] =
> + (delay_on * PCA9532_PWM_DUTY_DIV) / period;
[Severity: Medium]
Does this truncate the duty cycle to 0 in u8 data->pwm[PCA9532_PWM_ID_1]
when delay_off is 0?
pca9532_set_blink() already replaces (0, 0) with (500, 500) and overflow
is rejected above, so period is never 0 here and the !period check is
unreachable. When a caller such as delay_off_store() sets delay_off to 0
with a non-zero delay_on, period equals delay_on and the quotient is
PCA9532_PWM_DUTY_DIV (256), which wraps to 0 in u8 and turns the LED off
instead of returning -EINVAL so led_set_software_blink() can handle a
zero delay. Conversely, when delay_on is 0 and delay_off > 0, this
programs a 0% duty cycle and places the LED into PCA9532_PWM1, causing
subsequent non-zero pca9532_set_brightness() calls to return early at
led->state == PCA9532_PWM1.
Checking if (!delay_on || !delay_off) instead of if (!period) would reject
both cases for software fallback.
> +
> + ret = pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
> + if (ret < 0)
> + return ret;
>
> - return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
> + led->state = PCA9532_PWM1;
> +
> + return 0;
> }
>
> static int pca9532_set_blink(struct led_classdev *led_cdev,
> @@ -242,6 +307,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
> struct pca9532_led *led = ldev_to_led(led_cdev);
> struct i2c_client *client = led->client;
> struct pca9532_data *data = i2c_get_clientdata(client);
> + int old_state = led->state;
> int err;
>
> if (!data->hw_blink)
> @@ -257,16 +323,17 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
> if (err)
> return err;
>
> - pca9532_setled(led);
> + err = pca9532_setled(led);
> + if (err < 0)
> + led->state = old_state;
>
> - return 0;
> + return err;
> }
[Severity: Medium]
Can updating and rolling back led->state and data->pwm[]/data->psc[]
outside data->update_lock race across concurrent brightness, blink, and
GPIO calls?
data->update_lock is only held inside the leaf helpers pca9532_setpwm()
and pca9532_setled(), while pca9532_set_brightness(),
pca9532_update_hw_blink(), pca9532_set_blink(), and
pca9532_gpio_set_value() read old_state, mutate led->state and shared PWM
parameters, sleep on I2C transfers, and write back old_state without the
lock:
CPU0 CPU1
pca9532_set_brightness(led0)
old_state = led0->state
led0->state = PCA9532_PWM0
pca9532_setpwm()
pca9532_gpio_set_value(led0)
old_state = PCA9532_PWM0
led0->state = PCA9532_ON
pca9532_setled(led0)
pca9532_setled(led0) -> fails
led0->state = old_state <-- clobbers CPU1's state
Moving led->state = PCA9532_PWM1 to after the sleeping pca9532_setpwm()
call in pca9532_update_hw_blink() also allows two concurrent
pca9532_set_blink() calls on different LEDs with conflicting delays to
both pass the other->state == PCA9532_PWM1 check and overwrite
data->psc[PCA9532_PWM_ID_1] and data->pwm[PCA9532_PWM_ID_1]. In addition,
if pca9532_setled() fails in pca9532_set_brightness() or
pca9532_set_blink(), restoring only led->state leaves data->pwm[],
data->psc[], and the hardware PWM/PSC registers modified.
[ ... ]
> @@ -435,6 +531,7 @@ static int pca9532_configure(struct i2c_client *client,
> led->ldev.brightness_set_blocking =
> pca9532_set_brightness;
> led->ldev.blink_set = pca9532_set_blink;
> +
> err = led_classdev_register(&client->dev, &led->ldev);
> if (err < 0) {
> dev_err(&client->dev,
> @@ -442,19 +539,35 @@ static int pca9532_configure(struct i2c_client *client,
> led->name);
> goto exit;
> }
> - pca9532_setled(led);
> +
> + err = pca9532_setled(led);
> + if (err < 0) {
> + dev_err(&client->dev,
> + "couldn't set LED %s state\n",
> + led->name);
> + led_classdev_unregister(&led->ldev);
> + goto exit;
> + }
[Severity: Low]
Should pca9532_setled() be called before led_classdev_register(), as
PCA9532_TYPE_N2100_BEEP does below?
led_classdev_register() exposes sysfs attributes and activates
default_trigger before pca9532_setled() writes the initial state, and if
pca9532_setled() fails, led_classdev_unregister() calls
led_set_brightness(LED_OFF) and flushes set_brightness_work, issuing a
second pca9532_setled() transfer on the failing I2C device.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114172431.11075-1-t3127430@gmail.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3] leds: pca9532: Propagate I2C errors
2026-11-14 17:24 ` [PATCH v2] " Daniil Demakov
2026-11-14 17:40 ` sashiko-bot
@ 2026-11-14 18:14 ` Daniil Demakov
1 sibling, 0 replies; 5+ messages in thread
From: Daniil Demakov @ 2026-11-14 18:14 UTC (permalink / raw)
To: linux-leds
Cc: riku.voipio, lee, pavel, griffin, linusw, brgl, linux-gpio,
linux-kernel, Daniil Demakov
Check return values from SMBus read and write operations and
propagate errors through LED, PWM, blink and GPIO operations.
Restore previous LED and PWM state when hardware updates fail.
Protect shared LED and PWM state with the update mutex.
Check register writes during device initialization and register
LED class devices only after successfully configuring the hardware.
Signed-off-by: Daniil Demakov <t3127430@gmail.com>
---
drivers/leds/leds-pca9532.c | 313 ++++++++++++++++++++++++++++--------
1 file changed, 250 insertions(+), 63 deletions(-)
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index c8085427696f..eb9020e77b46 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -144,68 +144,147 @@ static int pca9532_calcpwm(struct i2c_client *client, int pwm, int blink,
return 0;
}
-static int pca9532_setpwm(struct i2c_client *client, int pwm)
+static int __pca9532_setpwm(struct i2c_client *client, int pwm)
{
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
+ int ret;
- mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, pwm),
- data->pwm[pwm]);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, pwm),
- data->psc[pwm]);
- mutex_unlock(&data->update_lock);
- return 0;
-}
+ if (pwm < 0 || pwm > 1)
+ return -EINVAL;
+
+ ret = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PWM(maxleds, pwm),
+ data->pwm[pwm]);
+ if (ret < 0) {
+ dev_warn(&client->dev,
+ "Failed to write PWM register: %d\n", ret);
+ return ret;
+ }
+ ret = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PSC(maxleds, pwm),
+ data->psc[pwm]);
+ if (ret < 0)
+ dev_warn(&client->dev,
+ "Failed to write PSC register: %d\n", ret);
+
+ return ret;
+}
/* Set LED routing */
-static void pca9532_setled(struct pca9532_led *led)
+static int __pca9532_setled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
- char reg;
+ int reg;
+ int ret;
- mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
+ if (reg < 0) {
+ ret = reg;
+ dev_warn(&client->dev, "Failed to read LED register: %d\n", ret);
+ return ret;
+ }
+
/* zero led bits */
- reg = reg & ~LED_MASK(led->id);
+ reg &= ~LED_MASK(led->id);
+
/* set the new value */
- reg = reg | (led->state << LED_SHIFT(led->id));
- i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
+ reg |= led->state << LED_SHIFT(led->id);
+
+ ret = i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
+
+ if (ret < 0)
+ dev_warn(&client->dev, "Failed to write LED register: %d\n", ret);
+
+ return ret;
+}
+
+static int pca9532_setled(struct pca9532_led *led)
+{
+ struct pca9532_data *data = i2c_get_clientdata(led->client);
+ int ret;
+
+ mutex_lock(&data->update_lock);
+ ret = __pca9532_setled(led);
mutex_unlock(&data->update_lock);
+
+ return ret;
}
static int pca9532_set_brightness(struct led_classdev *led_cdev,
- enum led_brightness value)
+ enum led_brightness value)
{
- int err = 0;
struct pca9532_led *led = ldev_to_led(led_cdev);
+ struct pca9532_data *data = i2c_get_clientdata(led->client);
+ int old_state;
+ u8 old_pwm;
+ u8 old_psc;
+ bool pwm_updated = false;
+ int restore_err;
+ int err = 0;
+
+ mutex_lock(&data->update_lock);
+ old_state = led->state;
+ old_pwm = data->pwm[PCA9532_PWM_ID_0];
+ old_psc = data->psc[PCA9532_PWM_ID_0];
if (value == LED_OFF) {
led->state = PCA9532_OFF;
} else if (led->state == PCA9532_PWM1) {
- return 0; /* Non-zero brightness shall not stop HW blinking */
+ goto out;
} else if (value == LED_FULL) {
led->state = PCA9532_ON;
} else {
- led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
- err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value);
+ led->state = PCA9532_PWM0;
+ err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0,
+ 0, value);
if (err)
- return err;
+ goto restore_state;
+ }
+
+ if (led->state == PCA9532_PWM0) {
+ pwm_updated = true;
+ err = __pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
+ if (err < 0)
+ goto restore_state;
+ }
+
+ err = __pca9532_setled(led);
+ if (err < 0)
+ goto restore_state;
+
+ goto out;
+
+restore_state:
+ led->state = old_state;
+ data->pwm[PCA9532_PWM_ID_0] = old_pwm;
+ data->psc[PCA9532_PWM_ID_0] = old_psc;
+
+ if (pwm_updated) {
+ restore_err = __pca9532_setpwm(led->client,
+ PCA9532_PWM_ID_0);
+ if (restore_err < 0)
+ dev_warn(&led->client->dev,
+ "Failed to restore PWM settings: %d\n",
+ restore_err);
}
- if (led->state == PCA9532_PWM0)
- pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
- pca9532_setled(led);
+out:
+ mutex_unlock(&data->update_lock);
return err;
}
static int pca9532_update_hw_blink(struct pca9532_led *led,
- unsigned long delay_on, unsigned long delay_off)
+ unsigned long delay_on,
+ unsigned long delay_off)
{
struct pca9532_data *data = i2c_get_clientdata(led->client);
unsigned int psc;
- int i;
+ unsigned long period;
+ u8 old_pwm = data->pwm[PCA9532_PWM_ID_1];
+ u8 old_psc = data->psc[PCA9532_PWM_ID_1];
+ int i, ret;
/* Look for others LEDs that already use PWM1 */
for (i = 0; i < data->chip_info->num_leds; i++) {
@@ -223,29 +302,64 @@ static int pca9532_update_hw_blink(struct pca9532_led *led,
}
}
- psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
- if (psc > U8_MAX) {
- /* Blink period too long to be handled by hardware */
+ if (delay_on > ULONG_MAX - delay_off)
+ return -EINVAL;
+
+ period = delay_on + delay_off;
+
+ if (!delay_on || !delay_off)
return -EINVAL;
+
+ if (period > ((U8_MAX + 1) * 1000) /
+ PCA9532_PWM_PERIOD_DIV)
+ return -EINVAL;
+
+ psc = (period * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
+
+ data->psc[PCA9532_PWM_ID_1] = psc;
+ data->pwm[PCA9532_PWM_ID_1] =
+ (delay_on * PCA9532_PWM_DUTY_DIV) / period;
+
+ ret = __pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
+ if (ret < 0) {
+ data->pwm[PCA9532_PWM_ID_1] = old_pwm;
+ data->psc[PCA9532_PWM_ID_1] = old_psc;
+
+ if (__pca9532_setpwm(data->client, PCA9532_PWM_ID_1) < 0)
+ dev_warn(&data->client->dev,
+ "Failed to restore PWM settings\n");
+
+ return ret;
}
led->state = PCA9532_PWM1;
- data->psc[PCA9532_PWM_ID_1] = psc;
- data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
- return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
+ return 0;
}
static int pca9532_set_blink(struct led_classdev *led_cdev,
- unsigned long *delay_on, unsigned long *delay_off)
+ unsigned long *delay_on,
+ unsigned long *delay_off)
{
struct pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
+ int old_state;
+ int restore_err;
+ u8 old_pwm;
+ u8 old_psc;
int err;
- if (!data->hw_blink)
- return -EINVAL;
+ mutex_lock(&data->update_lock);
+
+ old_state = led->state;
+ old_pwm = data->pwm[PCA9532_PWM_ID_1];
+ old_psc = data->psc[PCA9532_PWM_ID_1];
+
+ if (!data->hw_blink) {
+ err = -EINVAL;
+ goto out;
+ }
if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */
@@ -255,15 +369,32 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
if (err)
- return err;
+ goto restore_state;
+
+ err = __pca9532_setled(led);
+ if (err < 0) {
+ led->state = old_state;
+ data->pwm[PCA9532_PWM_ID_1] = old_pwm;
+ data->psc[PCA9532_PWM_ID_1] = old_psc;
+
+ restore_err = __pca9532_setpwm(client, PCA9532_PWM_ID_1);
+ if (restore_err < 0)
+ dev_warn(&client->dev,
+ "Failed to restore PWM settings: %d\n",
+ restore_err);
+ }
- pca9532_setled(led);
+ goto out;
- return 0;
+restore_state:
+ led->state = old_state;
+out:
+ mutex_unlock(&data->update_lock);
+ return err;
}
static int pca9532_event(struct input_dev *dev, unsigned int type,
- unsigned int code, int value)
+ unsigned int code, int value)
{
struct pca9532_data *data = input_get_drvdata(dev);
@@ -271,11 +402,15 @@ static int pca9532_event(struct input_dev *dev, unsigned int type,
return -1;
/* XXX: allow different kind of beeps with psc/pwm modifications */
+ mutex_lock(&data->update_lock);
+
if (value > 1 && value < 32767)
data->pwm[PCA9532_PWM_ID_1] = 127;
else
data->pwm[PCA9532_PWM_ID_1] = 0;
+ mutex_unlock(&data->update_lock);
+
schedule_work(&data->work);
return 0;
@@ -286,27 +421,36 @@ static void pca9532_input_work(struct work_struct *work)
struct pca9532_data *data =
container_of(work, struct pca9532_data, work);
u8 maxleds = data->chip_info->num_leds;
+ int ret;
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
- data->pwm[PCA9532_PWM_ID_1]);
+ ret = i2c_smbus_write_byte_data(data->client,
+ PCA9532_REG_PWM(maxleds, 1),
+ data->pwm[PCA9532_PWM_ID_1]);
+ if (ret < 0)
+ dev_warn(&data->client->dev,
+ "Failed to write PWM register: %d\n", ret);
+
mutex_unlock(&data->update_lock);
}
-static enum pca9532_state pca9532_getled(struct pca9532_led *led)
+static int pca9532_getled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
u8 maxleds = data->chip_info->num_leds;
int reg;
- enum pca9532_state ret = PCA9532_OFF;
+ int ret;
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
- if (reg >= 0)
+
+ if (reg >= 0) {
ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
- else
- dev_warn(&client->dev, "failed to read LED register: %d\n", reg);
+ } else {
+ ret = reg;
+ dev_warn(&client->dev, "Failed to read LED register: %d\n", ret);
+ }
mutex_unlock(&data->update_lock);
return ret;
}
@@ -328,33 +472,42 @@ static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
{
struct pca9532_data *data = gpiochip_get_data(gc);
struct pca9532_led *led = &data->leds[offset];
+ int old_state;
+ int ret;
+
+ mutex_lock(&data->update_lock);
+ old_state = led->state;
if (val)
led->state = PCA9532_OFF;
else
led->state = PCA9532_ON;
- pca9532_setled(led);
+ ret = __pca9532_setled(led);
+ if (ret < 0)
+ led->state = old_state;
- return 0;
+ mutex_unlock(&data->update_lock);
+
+ return ret;
}
static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
struct pca9532_data *data = gpiochip_get_data(gc);
- unsigned char reg;
+ int reg;
reg = i2c_smbus_read_byte_data(data->client, PCA9532_REG_INPUT(offset));
+ if (reg < 0)
+ return reg;
+
return !!(reg & (1 << (offset % 8)));
}
static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
- /* To use as input ensure pin is not driven */
- pca9532_gpio_set_value(gc, offset, 1);
-
- return 0;
+ return pca9532_gpio_set_value(gc, offset, 1);
}
static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
@@ -394,40 +547,57 @@ static int pca9532_configure(struct i2c_client *client,
struct pca9532_data *data, struct pca9532_platform_data *pdata)
{
int i, err = 0;
+#ifdef CONFIG_LEDS_PCA9532_GPIO
int gpios = 0;
+#endif
u8 maxleds = data->chip_info->num_leds;
- for (i = 0; i < 2; i++) {
+ for (i = 0; i < 2; i++) {
data->pwm[i] = pdata->pwm[i];
data->psc[i] = pdata->psc[i];
- err = i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i),
+
+ err = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PWM(maxleds, i),
data->pwm[i]);
if (err < 0)
return err;
- err = i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
+
+ err = i2c_smbus_write_byte_data(client,
+ PCA9532_REG_PSC(maxleds, i),
data->psc[i]);
if (err < 0)
return err;
}
data->hw_blink = true;
+
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
+
led->client = client;
led->id = i;
led->type = pled->type;
+
switch (led->type) {
case PCA9532_TYPE_NONE:
break;
case PCA9532_TYPE_GPIO:
+#ifdef CONFIG_LEDS_PCA9532_GPIO
gpios++;
+#endif
break;
case PCA9532_TYPE_LED:
- if (pled->state == PCA9532_KEEP)
- led->state = pca9532_getled(led);
- else
+ if (pled->state == PCA9532_KEEP) {
+ err = pca9532_getled(led);
+ if (err < 0)
+ goto exit;
+
+ led->state = err;
+ } else {
led->state = pled->state;
+ }
+
led->name = pled->name;
led->ldev.name = led->name;
led->ldev.default_trigger = pled->default_trigger;
@@ -435,6 +605,15 @@ static int pca9532_configure(struct i2c_client *client,
led->ldev.brightness_set_blocking =
pca9532_set_brightness;
led->ldev.blink_set = pca9532_set_blink;
+
+ err = pca9532_setled(led);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't set LED %s state\n",
+ led->name);
+ goto exit;
+ }
+
err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) {
dev_err(&client->dev,
@@ -442,19 +621,26 @@ static int pca9532_configure(struct i2c_client *client,
led->name);
goto exit;
}
- pca9532_setled(led);
break;
case PCA9532_TYPE_N2100_BEEP:
/* PWM1 is reserved for beeper so blink will not use hardware */
data->hw_blink = false;
BUG_ON(data->idev);
+
led->state = PCA9532_PWM1;
- pca9532_setled(led);
+ err = pca9532_setled(led);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't set beeper state\n");
+ goto exit;
+ }
+
data->idev = devm_input_allocate_device(&client->dev);
if (data->idev == NULL) {
err = -ENOMEM;
goto exit;
}
+
data->idev->name = pled->name;
data->idev->phys = "i2c/pca9532";
data->idev->id.bustype = BUS_HOST;
@@ -467,6 +653,7 @@ static int pca9532_configure(struct i2c_client *client,
data->idev->event = pca9532_event;
input_set_drvdata(data->idev, data);
INIT_WORK(&data->work, pca9532_input_work);
+
err = input_register_device(data->idev);
if (err) {
cancel_work_sync(&data->work);
@@ -498,8 +685,8 @@ static int pca9532_configure(struct i2c_client *client,
dev_warn(&client->dev, "could not add gpiochip\n");
} else {
dev_info(&client->dev, "gpios %i...%i\n",
- data->gpio.base, data->gpio.base +
- data->gpio.ngpio - 1);
+ data->gpio.base, data->gpio.base +
+ data->gpio.ngpio - 1);
}
}
#endif
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-14 18:15 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 14:19 [PATCH] leds: pca9532: Propagate I2C errors Daniil Demakov
2026-11-14 14:32 ` sashiko-bot
2026-11-14 17:24 ` [PATCH v2] " Daniil Demakov
2026-11-14 17:40 ` sashiko-bot
2026-11-14 18:14 ` [PATCH v3] " Daniil Demakov
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.