pwm: pca9685: Use bulk write to atomicially update registers

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>
This commit is contained in:
Uwe Kleine-König
2025-07-29 12:36:01 +02:00
committed by Uwe Kleine-König
parent ca478d8a4b
commit de58556132

View 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;