leds: pca9532: Use PWM1 for hardware blinking
authorBastien Curutchet <bastien.curutchet@bootlin.com>
Mon, 17 Jun 2024 14:39:08 +0000 (16:39 +0200)
committerLee Jones <lee@kernel.org>
Wed, 26 Jun 2024 16:04:36 +0000 (17:04 +0100)
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>
drivers/leds/leds-pca9532.c

index b6e5f48bffe72cd7b67f14bed211bc759222ede3..244ae3ff79b505514ed022ce824a772c4d0955be 100644 (file)
@@ -29,6 +29,9 @@
 #define LED_SHIFT(led)         (LED_NUM(led) * 2)
 #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)
 
 struct pca9532_chip_info {
@@ -194,29 +197,59 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev,
        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,
        unsigned long *delay_on, unsigned long *delay_off)
 {
        struct pca9532_led *led = ldev_to_led(led_cdev);
-       struct i2c_client *client = led->client;
-       int psc;
-       int err = 0;
+       int err;
 
        if (*delay_on == 0 && *delay_off == 0) {
                /* led subsystem ask us for a blink rate */
                *delay_on = 1000;
                *delay_off = 1000;
        }
-       if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6)
-               return -EINVAL;
 
-       /* Thecus specific: only use PSC/PWM 0 */
-       psc = (*delay_on * 152-1)/1000;
-       err = pca9532_calcpwm(client, PCA9532_PWM_ID_0, psc, led_cdev->brightness);
+       err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
        if (err)
                return err;
-       if (led->state == PCA9532_PWM0)
-               pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
+
        pca9532_setled(led);
 
        return 0;