1

leds: pca9532: Use PWM1 for hardware blinking

PSC0/PWM0 are used by all LEDs for brightness and blinking. This causes
conflicts when you set a brightness of a new LED while an other one is
already using PWM0 for blinking.

Use PSC1/PWM1 for blinking.
Check that no other LED is already blinking with a different PSC1/PWM1
configuration to avoid conflict.

Signed-off-by: Bastien Curutchet <bastien.curutchet@bootlin.com>
Link: https://lore.kernel.org/r/20240617143910.154546-3-bastien.curutchet@bootlin.com
Signed-off-by: Lee Jones <lee@kernel.org>
This commit is contained in:
Bastien Curutchet 2024-06-17 16:39:08 +02:00 committed by Lee Jones
parent 0e69c9062b
commit 48ca7f302c

View File

@ -29,6 +29,9 @@
#define LED_SHIFT(led) (LED_NUM(led) * 2) #define LED_SHIFT(led) (LED_NUM(led) * 2)
#define LED_MASK(led) (0x3 << LED_SHIFT(led)) #define LED_MASK(led) (0x3 << LED_SHIFT(led))
#define PCA9532_PWM_PERIOD_DIV 152
#define PCA9532_PWM_DUTY_DIV 256
#define ldev_to_led(c) container_of(c, struct pca9532_led, ldev) #define ldev_to_led(c) container_of(c, struct pca9532_led, ldev)
struct pca9532_chip_info { struct pca9532_chip_info {
@ -194,29 +197,59 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev,
return err; return err;
} }
static int pca9532_update_hw_blink(struct pca9532_led *led,
unsigned long delay_on, unsigned long delay_off)
{
struct pca9532_data *data = i2c_get_clientdata(led->client);
unsigned int psc;
int i;
/* Look for others LEDs that already use PWM1 */
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *other = &data->leds[i];
if (other == led)
continue;
if (other->state == PCA9532_PWM1) {
if (other->ldev.blink_delay_on != delay_on ||
other->ldev.blink_delay_off != delay_off) {
dev_err(&led->client->dev,
"HW can handle only one blink configuration at a time\n");
return -EINVAL;
}
}
}
psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
if (psc > U8_MAX) {
dev_err(&led->client->dev, "Blink period too long to be handled by hardware\n");
return -EINVAL;
}
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);
}
static int pca9532_set_blink(struct led_classdev *led_cdev, 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 pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client; int err;
int psc;
int err = 0;
if (*delay_on == 0 && *delay_off == 0) { if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */ /* led subsystem ask us for a blink rate */
*delay_on = 1000; *delay_on = 1000;
*delay_off = 1000; *delay_off = 1000;
} }
if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6)
return -EINVAL;
/* Thecus specific: only use PSC/PWM 0 */ err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
psc = (*delay_on * 152-1)/1000;
err = pca9532_calcpwm(client, PCA9532_PWM_ID_0, psc, led_cdev->brightness);
if (err) if (err)
return err; return err;
if (led->state == PCA9532_PWM0)
pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
pca9532_setled(led); pca9532_setled(led);
return 0; return 0;