summaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/leds-pca9633.c136
1 files changed, 132 insertions, 4 deletions
diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c
index 90935e465c4d..77b3f24b7207 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)