From: Daniil Demakov <t3127430@gmail.com>
To: linux-leds@vger.kernel.org
Cc: lee@kernel.org, pavel@kernel.org, linusw@kernel.org,
Daniil Demakov <t3127430@gmail.com>
Subject: [PATCH v2] leds: pca9532: Propagate I2C errors
Date: Sat, 14 Nov 2026 17:24:31 +0000 [thread overview]
Message-ID: <20261114172431.11075-1-t3127430@gmail.com> (raw)
In-Reply-To: <20261114141926.92737-1-t3127430@gmail.com>
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
next prev parent reply other threads:[~2026-11-14 17:25 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
2026-11-14 17:40 ` [PATCH v2] " sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261114172431.11075-1-t3127430@gmail.com \
--to=t3127430@gmail.com \
--cc=lee@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-leds@vger.kernel.org \
--cc=pavel@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.