leds: pca9633: Add hardware blink support
authorMark A. Greer <mgreer@animalcreek.com>
Thu, 25 Jul 2013 17:16:41 +0000 (10:16 -0700)
committerBryan Wu <cooloney@gmail.com>
Tue, 27 Aug 2013 00:22:10 +0000 (17:22 -0700)
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.

(fengguang.wu@intel.com: Removes unneeded semicolon.)

Signed-off-by: Mark A. Greer <mgreer@animalcreek.com>
Reported-by: Fengguang Wu <fengguang.wu@intel.com>
Signed-off-by: Bryan Wu <cooloney@gmail.com>
Documentation/devicetree/bindings/leds/pca9633.txt
drivers/leds/leds-pca9633.c
include/linux/platform_data/leds-pca9633.h

index 8140512c0ac8cc224636294e18841d1b01e60c7a..6d9e1a9e7e9f9c95f68345a0a2865c69a8d76fe1 100644 (file)
@@ -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.
 
index 90935e465c4d0b2c98a31cb14ddbe9536a39b1d0..77b3f24b7207132da95c77cb9c1f22085a7ec04f 100644 (file)
  *
  * 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>
 #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)
index c5bf29b6fa7f8c1eddafb23508bb6030509dd7ae..3c1037a81d34e18650628752a709dea3efd122eb 100644 (file)
@@ -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*/
This page took 0.030083 seconds and 5 git commands to generate.