Re: [PATCH v3] leds: pca9633: Add hardware blink support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux OMAP]     [Linux MIPS]     [ECOS]     [Asterisk Internet PBX]     [Linux API]

  Powered by Linux