]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
pwm: pca9685: Use bulk write to atomicially update registers
authorUwe Kleine-König <u.kleine-koenig@baylibre.com>
Tue, 29 Jul 2025 10:36:01 +0000 (12:36 +0200)
committerUwe Kleine-König <ukleinek@kernel.org>
Mon, 15 Sep 2025 09:39:46 +0000 (11:39 +0200)
The output of a PWM channel is configured by four register values. Write
them in a single i2c transaction to ensure glitch free updates.

Signed-off-by: Uwe Kleine-König <u.kleine-koenig@baylibre.com>
Link: https://lore.kernel.org/r/bfa8c0267c9ec059d0d77f146998d564654c75ca.1753784092.git.u.kleine-koenig@baylibre.com
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>
drivers/pwm/pwm-pca9685.c

index de5fe86b4f3351f9c28434fe49ac8e98c150bc5c..b22c6da20ebc3b04c92b3685279ae3857e5ad6fa 100644 (file)
@@ -61,6 +61,8 @@
 #define MODE1_SUB2             BIT(2)
 #define MODE1_SUB1             BIT(3)
 #define MODE1_SLEEP            BIT(4)
+#define MODE1_AI               BIT(5)
+
 #define MODE2_INVRT            BIT(4)
 #define MODE2_OUTDRV           BIT(2)
 
@@ -131,6 +133,19 @@ static int pca9685_write_reg(struct pwm_chip *chip, unsigned int reg, unsigned i
        return err;
 }
 
+static int pca9685_write_4reg(struct pwm_chip *chip, unsigned int reg, u8 val[4])
+{
+       struct pca9685 *pca = to_pca(chip);
+       struct device *dev = pwmchip_parent(chip);
+       int err;
+
+       err = regmap_bulk_write(pca->regmap, reg, val, 4);
+       if (err)
+               dev_err(dev, "regmap_write to register 0x%x failed: %pe\n", reg, ERR_PTR(err));
+
+       return err;
+}
+
 /* Helper function to set the duty cycle ratio to duty/4096 (e.g. duty=2048 -> 50%) */
 static void pca9685_pwm_set_duty(struct pwm_chip *chip, int channel, unsigned int duty)
 {
@@ -143,12 +158,10 @@ static void pca9685_pwm_set_duty(struct pwm_chip *chip, int channel, unsigned in
                return;
        } else if (duty >= PCA9685_COUNTER_RANGE) {
                /* Set the full ON bit and clear the full OFF bit */
-               pca9685_write_reg(chip, REG_ON_H(channel), LED_FULL);
-               pca9685_write_reg(chip, REG_OFF_H(channel), 0);
+               pca9685_write_4reg(chip, REG_ON_L(channel), (u8[4]){ 0, LED_FULL, 0, 0 });
                return;
        }
 
-
        if (pwm->state.usage_power && channel < PCA9685_MAXCHAN) {
                /*
                 * If usage_power is set, the pca9685 driver will phase shift
@@ -163,12 +176,9 @@ static void pca9685_pwm_set_duty(struct pwm_chip *chip, int channel, unsigned in
 
        off = (on + duty) % PCA9685_COUNTER_RANGE;
 
-       /* Set ON time (clears full ON bit) */
-       pca9685_write_reg(chip, REG_ON_L(channel), on & 0xff);
-       pca9685_write_reg(chip, REG_ON_H(channel), (on >> 8) & 0xf);
-       /* Set OFF time (clears full OFF bit) */
-       pca9685_write_reg(chip, REG_OFF_L(channel), off & 0xff);
-       pca9685_write_reg(chip, REG_OFF_H(channel), (off >> 8) & 0xf);
+       /* implicitly clear full ON and full OFF bit */
+       pca9685_write_4reg(chip, REG_ON_L(channel),
+                          (u8[4]){ on & 0xff, (on >> 8) & 0xf, off & 0xff, (off >> 8) & 0xf });
 }
 
 static unsigned int pca9685_pwm_get_duty(struct pwm_chip *chip, int channel)
@@ -543,9 +553,8 @@ static int pca9685_pwm_probe(struct i2c_client *client)
 
        mutex_init(&pca->lock);
 
-       ret = pca9685_read_reg(chip, PCA9685_MODE2, &reg);
-       if (ret)
-               return ret;
+       /* clear MODE2_OCH */
+       reg = 0;
 
        if (device_property_read_bool(&client->dev, "invert"))
                reg |= MODE2_INVRT;
@@ -561,16 +570,19 @@ static int pca9685_pwm_probe(struct i2c_client *client)
        if (ret)
                return ret;
 
-       /* Disable all LED ALLCALL and SUBx addresses to avoid bus collisions */
+       /*
+        * Disable all LED ALLCALL and SUBx addresses to avoid bus collisions,
+        * enable Auto-Increment.
+        */
        pca9685_read_reg(chip, PCA9685_MODE1, &reg);
        reg &= ~(MODE1_ALLCALL | MODE1_SUB1 | MODE1_SUB2 | MODE1_SUB3);
+       reg |= MODE1_AI;
        pca9685_write_reg(chip, PCA9685_MODE1, reg);
 
        /* Reset OFF/ON registers to POR default */
-       pca9685_write_reg(chip, PCA9685_ALL_LED_OFF_L, 0);
-       pca9685_write_reg(chip, PCA9685_ALL_LED_OFF_H, LED_FULL);
-       pca9685_write_reg(chip, PCA9685_ALL_LED_ON_L, 0);
-       pca9685_write_reg(chip, PCA9685_ALL_LED_ON_H, LED_FULL);
+       ret = pca9685_write_4reg(chip, PCA9685_ALL_LED_ON_L, (u8[]){ 0, LED_FULL, 0, LED_FULL });
+       if (ret < 0)
+               return dev_err_probe(&client->dev, ret, "Failed to reset ON/OFF registers\n");
 
        chip->ops = &pca9685_pwm_ops;