On Mon, Jul 15, 2013 at 4:18 PM, Mark A. Greer <mgreer@xxxxxxxxxxxxxxx> wrote: > From: "Mark A. Greer" <mgreer@xxxxxxxxxxxxxxx> > > Add hardware blink support to the pca9633 driver. > > NOTE: This patch violates the leds infrastructure > since the hardware only supports blinking all LEDs > with the same delay_on/delay_off rates. That is, > only the LEDs that are set to blink will actually > blink but all LEDs that are set to blink will blink > in identical fashion. The delay_on/delay_off values > of the last LED that is set to blink will be used > for all of the blinking LEDs. > Sorry for the delay, I'm basically OK for this RFC. And could you please put some NOTE in the code as a comment as well as the patch description. After that, I will put this patch on my devel branch. Thanks, -Bryan > Signed-off-by: Mark A. Greer <mgreer@xxxxxxxxxxxxxxx> > --- > > Based on linux-leds@xxxxxxxxxxxxxxx devel. > > Sending this as an RFC because of the issue described > in the "NOTE:" section above. > > I doubt this is acceptable because of the issue but if > there is something I can do to make it acceptable, I'd > be happy to make the necessary changes. > > drivers/leds/leds-pca9633.c | 115 ++++++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 111 insertions(+), 4 deletions(-) > > diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c > index 90935e4..6cc5568 100644 > --- a/drivers/leds/leds-pca9633.c > +++ b/drivers/leds/leds-pca9633.c > @@ -31,30 +31,44 @@ > #define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ > #define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ > > +#define PCA9633_MODE2_DMBLNK 0x20 /* Enable blinking */ > + > #define PCA9633_MODE1 0x00 > #define PCA9633_MODE2 0x01 > #define PCA9633_PWM_BASE 0x02 > +#define PCA9633_GRPPWM 0x06 > +#define PCA9633_GRPFREQ 0x07 > #define PCA9633_LEDOUT 0x08 > > +/* Total blink period in milliseconds */ > +#define PCA9632_BLINK_PERIOD_MIN 42 > +#define PCA9632_BLINK_PERIOD_MAX 10667 > + > static const struct i2c_device_id pca9633_id[] = { > { "pca9633", 0 }, > { } > }; > MODULE_DEVICE_TABLE(i2c, pca9633_id); > > +enum pca9633_cmd { > + BRIGHTNESS_SET, > + BLINK_SET, > +}; > + > struct pca9633_led { > struct i2c_client *client; > struct work_struct work; > enum led_brightness brightness; > struct led_classdev led_cdev; > int led_num; /* 0 .. 3 potentially */ > + enum pca9633_cmd cmd; > char name[32]; > + u8 gdc; > + u8 gfrq; > }; > > -static void pca9633_led_work(struct work_struct *work) > +static void pca9633_brightness_work(struct pca9633_led *pca9633) > { > - struct pca9633_led *pca9633 = container_of(work, > - struct pca9633_led, work); > u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); > int shift = 2 * pca9633->led_num; > u8 mask = 0x3 << shift; > @@ -78,6 +92,43 @@ static void pca9633_led_work(struct work_struct *work) > } > } > > +static void pca9633_blink_work(struct pca9633_led *pca9633) > +{ > + u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); > + u8 mode2 = i2c_smbus_read_byte_data(pca9633->client, PCA9633_MODE2); > + int shift = 2 * pca9633->led_num; > + u8 mask = 0x3 << shift; > + > + i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPPWM, > + pca9633->gdc); > + > + i2c_smbus_write_byte_data(pca9633->client, PCA9633_GRPFREQ, > + pca9633->gfrq); > + > + if (!(mode2 & PCA9633_MODE2_DMBLNK)) > + i2c_smbus_write_byte_data(pca9633->client, PCA9633_MODE2, > + mode2 | PCA9633_MODE2_DMBLNK); > + > + if ((ledout & mask) != (PCA9633_LED_GRP_PWM << shift)) > + i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, > + (ledout & ~mask) | (PCA9633_LED_GRP_PWM << shift)); > +} > + > +static void pca9633_work(struct work_struct *work) > +{ > + struct pca9633_led *pca9633 = container_of(work, > + struct pca9633_led, work); > + > + switch (pca9633->cmd) { > + case BRIGHTNESS_SET: > + pca9633_brightness_work(pca9633); > + break; > + case BLINK_SET: > + pca9633_blink_work(pca9633); > + break; > + }; > +} > + > static void pca9633_led_set(struct led_classdev *led_cdev, > enum led_brightness value) > { > @@ -85,6 +136,7 @@ static void pca9633_led_set(struct led_classdev *led_cdev, > > pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); > > + pca9633->cmd = BRIGHTNESS_SET; > pca9633->brightness = value; > > /* > @@ -94,6 +146,60 @@ static void pca9633_led_set(struct led_classdev *led_cdev, > schedule_work(&pca9633->work); > } > > +static int pca9633_blink_set(struct led_classdev *led_cdev, > + unsigned long *delay_on, unsigned long *delay_off) > +{ > + struct pca9633_led *pca9633; > + unsigned long time_on, time_off, period; > + u8 gdc, gfrq; > + > + pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); > + > + time_on = *delay_on; > + time_off = *delay_off; > + > + /* If both zero, pick reasonable defaults of 500ms each */ > + if (!time_on && !time_off) { > + time_on = 500; > + time_off = 500; > + } > + > + period = time_on + time_off; > + > + if ((period < PCA9632_BLINK_PERIOD_MIN) || > + (period > PCA9632_BLINK_PERIOD_MAX)) > + return -EINVAL; > + > + /* > + * From manual: duty cycle = (GDC / 256) -> > + * (time_on / period) = (GDC / 256) -> > + * GDC = ((time_on * 256) / period) > + */ > + gdc = (time_on * 256) / period; > + > + /* > + * From manual: period = ((GFRQ + 1) / 24) in seconds. > + * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> > + * GFRQ = ((period * 24 / 1000) - 1) > + */ > + gfrq = (period * 24 / 1000) - 1; > + > + pca9633->cmd = BLINK_SET; > + pca9633->gdc = gdc; > + pca9633->gfrq = gfrq; > + > + /* > + * Must use workqueue for the actual I/O since I2C operations > + * can sleep. > + */ > + schedule_work(&pca9633->work); > + > + *delay_on = time_on; > + *delay_off = time_off; > + > + return 0; > +} > + > #if IS_ENABLED(CONFIG_OF) > static struct pca9633_platform_data * > pca9633_dt_init(struct i2c_client *client) > @@ -205,8 +311,9 @@ static int pca9633_probe(struct i2c_client *client, > > pca9633[i].led_cdev.name = pca9633[i].name; > pca9633[i].led_cdev.brightness_set = pca9633_led_set; > + pca9633[i].led_cdev.blink_set = pca9633_blink_set; > > - INIT_WORK(&pca9633[i].work, pca9633_led_work); > + INIT_WORK(&pca9633[i].work, pca9633_work); > > err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); > if (err < 0) > -- > 1.7.12 > -- To unsubscribe from this list: send the line "unsubscribe linux-leds" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html