On Thu, Jul 25, 2013 at 10:16 AM, Mark A. Greer <mgreer@xxxxxxxxxxxxxxx> wrote: > From: "Mark A. Greer" <mgreer@xxxxxxxxxxxxxxx> > > Add hardware blinking support to the pca9633 driver. > > NOTE: Hardware blinking violates the leds infrastructure > driver interface 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. If the hardware doesn't > support the requested blinking pattern, a default of > 500ms on and off will be used. > > Hardware blinking is disabled by default but can be enabled > by setting the 'blink_type' member in the platform_data > struct to 'PCA9633_HW_BLINK' or by adding the 'nxp,hw-blink' > property to the DTS. > > Signed-off-by: Mark A. Greer <mgreer@xxxxxxxxxxxxxxx> Looks good to me, I merged it into my tree. Thanks, -Bryan > --- > Changes from v2: > - Hardware blinking now optional via platform data and DTS. > - Commit description and source comments extendd to describe > how to turn hardware blinking on. > > Changes from v1: > - Minor commit description edit. > - Added a copy of the NOTE section from commit description > to the comments at the beginning of the source file. > > Applies to linux-leds/devel > > Documentation/devicetree/bindings/leds/pca9633.txt | 1 + > drivers/leds/leds-pca9633.c | 136 ++++++++++++++++++++- > include/linux/platform_data/leds-pca9633.h | 6 + > 3 files changed, 139 insertions(+), 4 deletions(-) > > diff --git a/Documentation/devicetree/bindings/leds/pca9633.txt b/Documentation/devicetree/bindings/leds/pca9633.txt > index 8140512..6d9e1a9 100644 > --- a/Documentation/devicetree/bindings/leds/pca9633.txt > +++ b/Documentation/devicetree/bindings/leds/pca9633.txt > @@ -5,6 +5,7 @@ Required properties: > > Optional properties: > - nxp,totem-pole : use totem pole (push-pull) instead of default open-drain > +- nxp,hw-blink : use hardware blinking instead of software blinking > > Each led is represented as a sub-node of the nxp,pca9633 device. > > diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c > index 90935e4..7a75707 100644 > --- a/drivers/leds/leds-pca9633.c > +++ b/drivers/leds/leds-pca9633.c > @@ -11,6 +11,15 @@ > * > * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) > * > + * Note that hardware blinking violates the leds infrastructure driver > + * interface 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. > + * Hardware blinking is disabled by default but can be enabled by setting > + * the 'blink_type' member in the platform_data struct to 'PCA9633_HW_BLINK' > + * or by adding the 'nxp,hw-blink' property to the DTS. > */ > > #include <linux/module.h> > @@ -31,30 +40,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 +101,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 +145,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 +155,64 @@ 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 not supported by hardware, default to someting sane. */ > + if ((period < PCA9632_BLINK_PERIOD_MIN) || > + (period > PCA9632_BLINK_PERIOD_MAX)) { > + time_on = 500; > + time_off = 500; > + period = time_on + time_off; > + } > + > + /* > + * 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) > @@ -140,6 +259,12 @@ pca9633_dt_init(struct i2c_client *client) > else > pdata->outdrv = PCA9633_OPEN_DRAIN; > > + /* default to software blinking unless hardware blinking is specified */ > + if (of_property_read_bool(np, "nxp,hw-blink")) > + pdata->blink_type = PCA9633_HW_BLINK; > + else > + pdata->blink_type = PCA9633_SW_BLINK; > + > return pdata; > } > > @@ -206,7 +331,10 @@ 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; > > - INIT_WORK(&pca9633[i].work, pca9633_led_work); > + if (pdata && pdata->blink_type == PCA9633_HW_BLINK) > + pca9633[i].led_cdev.blink_set = pca9633_blink_set; > + > + INIT_WORK(&pca9633[i].work, pca9633_work); > > err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); > if (err < 0) > diff --git a/include/linux/platform_data/leds-pca9633.h b/include/linux/platform_data/leds-pca9633.h > index c5bf29b..3c1037a 100644 > --- a/include/linux/platform_data/leds-pca9633.h > +++ b/include/linux/platform_data/leds-pca9633.h > @@ -27,9 +27,15 @@ enum pca9633_outdrv { > PCA9633_TOTEM_POLE, /* aka push-pull */ > }; > > +enum pca9633_blink_type { > + PCA9633_SW_BLINK, > + PCA9633_HW_BLINK, > +}; > + > struct pca9633_platform_data { > struct led_platform_data leds; > enum pca9633_outdrv outdrv; > + enum pca9633_blink_type blink_type; > }; > > #endif /* __LINUX_PCA9633_H*/ > -- > 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