Support blinking using the PCA955x chip. Use PWM0 for blinking instead of LED_HALF brightness. Since there is only one frequency and brightness register for any blinking LED, all blinked LEDs on the chip will have the same frequency and brightness. Signed-off-by: Eddie James <eajames@xxxxxxxxxxxxx> --- drivers/leds/leds-pca955x.c | 175 ++++++++++++++++++++++++------------ 1 file changed, 120 insertions(+), 55 deletions(-) diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 81aaf21212d7..aeddc64e8ecf 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -74,6 +74,7 @@ struct pca955x_chipdef { int bits; u8 slv_addr; /* 7-bit slave address mask */ int slv_addr_shift; /* Number of bits to ignore */ + int blink_div; /* PSC divider */ }; static struct pca955x_chipdef pca955x_chipdefs[] = { @@ -81,26 +82,31 @@ static struct pca955x_chipdef pca955x_chipdefs[] = { .bits = 2, .slv_addr = /* 110000x */ 0x60, .slv_addr_shift = 1, + .blink_div = 44, }, [pca9551] = { .bits = 8, .slv_addr = /* 1100xxx */ 0x60, .slv_addr_shift = 3, + .blink_div = 38, }, [pca9552] = { .bits = 16, .slv_addr = /* 1100xxx */ 0x60, .slv_addr_shift = 3, + .blink_div = 44, }, [ibm_pca9552] = { .bits = 16, .slv_addr = /* 0110xxx */ 0x30, .slv_addr_shift = 3, + .blink_div = 44, }, [pca9553] = { .bits = 4, .slv_addr = /* 110001x */ 0x62, .slv_addr_shift = 1, + .blink_div = 44, }, }; @@ -163,7 +169,7 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state) /* * Write to frequency prescaler register, used to program the - * period of the PWM output. period = (PSCx + 1) / 38 + * period of the PWM output. period = (PSCx + 1) / <38 or 44, chip dependent> */ static int pca955x_write_psc(struct i2c_client *client, int n, u8 val) { @@ -273,13 +279,16 @@ static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev) ret = LED_OFF; break; case PCA955X_LS_BLINK0: - ret = LED_HALF; + ret = pca955x_read_pwm(pca955x->client, 0, &pwm); + if (ret) + return ret; + ret = 256 - pwm; break; case PCA955X_LS_BLINK1: ret = pca955x_read_pwm(pca955x->client, 1, &pwm); if (ret) return ret; - ret = 255 - pwm; + ret = 256 - pwm; break; } @@ -308,32 +317,98 @@ static int pca955x_led_set(struct led_classdev *led_cdev, if (ret) goto out; - switch (value) { - case LED_FULL: - ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); - break; - case LED_OFF: + if (value == LED_OFF) { ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF); - break; - case LED_HALF: + } else { + u8 tls = (ls >> (ls_led << 1)) & 0x3; + + if (tls == PCA955X_LS_BLINK0) { + ret = pca955x_write_pwm(pca955x->client, 0, + 256 - value); + goto out; + } else { + if (value == LED_FULL) { + ls = pca955x_ledsel(ls, ls_led, + PCA955X_LS_LED_ON); + } else { + /* + * Use PWM1 for all other values. This has the + * unwanted side effect of making all LEDs on + * the chip share the same brightness level if + * set to a value other than OFF or FULL. But, + * this is probably better than just turning + * off for all other values. + */ + ret = pca955x_write_pwm(pca955x->client, 1, + 256 - value); + if (ret || tls == PCA955X_LS_BLINK1) + goto out; + ls = pca955x_ledsel(ls, ls_led, + PCA955X_LS_BLINK1); + } + } + } + + ret = pca955x_write_ls(pca955x->client, chip_ls, ls); + +out: + mutex_unlock(&pca955x->lock); + + return ret; +} + +static int pca955x_led_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + int chip_ls; + int ls_led; + int ret; + u8 ls; + struct pca955x_led *pca955x_led = container_of(led_cdev, + struct pca955x_led, + led_cdev); + struct pca955x *pca955x = pca955x_led->pca955x; + unsigned long p = *delay_on + *delay_off; + + /* 1 Hz default */ + if (!p) + p = 1000; + + p *= (unsigned long)pca955x->chipdef->blink_div; + p /= 1000; + p -= 1; + + chip_ls = pca955x_led->led_num / 4; + ls_led = pca955x_led->led_num % 4; + + mutex_lock(&pca955x->lock); + + ret = pca955x_read_ls(pca955x->client, chip_ls, &ls); + if (ret) + goto out; + + /* + * All blinking leds on the PCA955x chip will use the same period and + * brightness. + */ + ret = pca955x_write_psc(pca955x->client, 0, (u8)p); + if (ret) + goto out; + + if (((ls >> (ls_led << 1)) & 0x3) != PCA955X_LS_BLINK0) { ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0); - break; - default: - /* - * Use PWM1 for all other values. This has the unwanted - * side effect of making all LEDs on the chip share the - * same brightness level if set to a value other than - * OFF, HALF, or FULL. But, this is probably better than - * just turning off for all other values. - */ - ret = pca955x_write_pwm(pca955x->client, 1, 255 - value); + ret = pca955x_write_ls(pca955x->client, chip_ls, ls); if (ret) goto out; - ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1); - break; } - ret = pca955x_write_ls(pca955x->client, chip_ls, ls); + p += 1; + p *= 1000; + p /= (unsigned long)pca955x->chipdef->blink_div; + p /= 2; + + *delay_on = p; + *delay_off = p; out: mutex_unlock(&pca955x->lock); @@ -495,7 +570,6 @@ static int pca955x_probe(struct i2c_client *client) int i, err; struct pca955x_platform_data *pdata; bool set_default_label = false; - bool keep_pwm = false; char default_label[8]; enum pca955x_type chip_type; const void *md = device_get_match_data(&client->dev); @@ -577,6 +651,7 @@ static int pca955x_probe(struct i2c_client *client) led = &pca955x_led->led_cdev; led->brightness_set_blocking = pca955x_led_set; led->brightness_get = pca955x_led_get; + led->blink_set = pca955x_led_blink; if (pdata->leds[i].default_state == LEDS_GPIO_DEFSTATE_OFF) { @@ -585,9 +660,28 @@ static int pca955x_probe(struct i2c_client *client) return err; } else if (pdata->leds[i].default_state == LEDS_GPIO_DEFSTATE_ON) { - err = pca955x_led_set(led, LED_FULL); + /* + * handle this case specially in order to turn + * off blinking, which pca955x_led_set won't do + */ + u8 ls; + int chip_ls = i / 4; + int ls_led = i % 4; + + err = pca955x_read_ls(pca955x->client, chip_ls, + &ls); if (err) return err; + + if (((ls >> (ls_led << 1)) & 0x3) != + PCA955X_LS_LED_ON) { + ls = pca955x_ledsel(ls, ls_led, + PCA955X_LS_LED_ON); + err = pca955x_write_ls(pca955x->client, + chip_ls, ls); + if (err) + return err; + } } init_data.fwnode = pdata->leds[i].fwnode; @@ -616,39 +710,10 @@ static int pca955x_probe(struct i2c_client *client) return err; set_bit(i, &pca955x->active_pins); - - /* - * For default-state == "keep", let the core update the - * brightness from the hardware, then check the - * brightness to see if it's using PWM1. If so, PWM1 - * should not be written below. - */ - if (pdata->leds[i].default_state == - LEDS_GPIO_DEFSTATE_KEEP) { - if (led->brightness != LED_FULL && - led->brightness != LED_OFF && - led->brightness != LED_HALF) - keep_pwm = true; - } } } - /* PWM0 is used for half brightness or 50% duty cycle */ - err = pca955x_write_pwm(client, 0, 255 - LED_HALF); - if (err) - return err; - - if (!keep_pwm) { - /* PWM1 is used for variable brightness, default to OFF */ - err = pca955x_write_pwm(client, 1, 0); - if (err) - return err; - } - - /* Set to fast frequency so we do not see flashing */ - err = pca955x_write_psc(client, 0, 0); - if (err) - return err; + /* Set PWM1 to fast frequency so we do not see flashing */ err = pca955x_write_psc(client, 1, 0); if (err) return err; -- 2.27.0