leds: pca9633: Add hardware blink support
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>
This commit is contained in:
parent
33b3a561f4
commit
8465b01827
|
@ -5,6 +5,7 @@ Required properties:
|
||||||
|
|
||||||
Optional properties:
|
Optional properties:
|
||||||
- nxp,totem-pole : use totem pole (push-pull) instead of default open-drain
|
- 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.
|
Each led is represented as a sub-node of the nxp,pca9633 device.
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,15 @@
|
||||||
*
|
*
|
||||||
* LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62)
|
* 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>
|
#include <linux/module.h>
|
||||||
|
@ -31,30 +40,44 @@
|
||||||
#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */
|
#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */
|
||||||
#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */
|
#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */
|
||||||
|
|
||||||
|
#define PCA9633_MODE2_DMBLNK 0x20 /* Enable blinking */
|
||||||
|
|
||||||
#define PCA9633_MODE1 0x00
|
#define PCA9633_MODE1 0x00
|
||||||
#define PCA9633_MODE2 0x01
|
#define PCA9633_MODE2 0x01
|
||||||
#define PCA9633_PWM_BASE 0x02
|
#define PCA9633_PWM_BASE 0x02
|
||||||
|
#define PCA9633_GRPPWM 0x06
|
||||||
|
#define PCA9633_GRPFREQ 0x07
|
||||||
#define PCA9633_LEDOUT 0x08
|
#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[] = {
|
static const struct i2c_device_id pca9633_id[] = {
|
||||||
{ "pca9633", 0 },
|
{ "pca9633", 0 },
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(i2c, pca9633_id);
|
MODULE_DEVICE_TABLE(i2c, pca9633_id);
|
||||||
|
|
||||||
|
enum pca9633_cmd {
|
||||||
|
BRIGHTNESS_SET,
|
||||||
|
BLINK_SET,
|
||||||
|
};
|
||||||
|
|
||||||
struct pca9633_led {
|
struct pca9633_led {
|
||||||
struct i2c_client *client;
|
struct i2c_client *client;
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
enum led_brightness brightness;
|
enum led_brightness brightness;
|
||||||
struct led_classdev led_cdev;
|
struct led_classdev led_cdev;
|
||||||
int led_num; /* 0 .. 3 potentially */
|
int led_num; /* 0 .. 3 potentially */
|
||||||
|
enum pca9633_cmd cmd;
|
||||||
char name[32];
|
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);
|
u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT);
|
||||||
int shift = 2 * pca9633->led_num;
|
int shift = 2 * pca9633->led_num;
|
||||||
u8 mask = 0x3 << shift;
|
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,
|
static void pca9633_led_set(struct led_classdev *led_cdev,
|
||||||
enum led_brightness value)
|
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 = container_of(led_cdev, struct pca9633_led, led_cdev);
|
||||||
|
|
||||||
|
pca9633->cmd = BRIGHTNESS_SET;
|
||||||
pca9633->brightness = value;
|
pca9633->brightness = value;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -94,6 +155,64 @@ static void pca9633_led_set(struct led_classdev *led_cdev,
|
||||||
schedule_work(&pca9633->work);
|
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)
|
#if IS_ENABLED(CONFIG_OF)
|
||||||
static struct pca9633_platform_data *
|
static struct pca9633_platform_data *
|
||||||
pca9633_dt_init(struct i2c_client *client)
|
pca9633_dt_init(struct i2c_client *client)
|
||||||
|
@ -140,6 +259,12 @@ pca9633_dt_init(struct i2c_client *client)
|
||||||
else
|
else
|
||||||
pdata->outdrv = PCA9633_OPEN_DRAIN;
|
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;
|
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.name = pca9633[i].name;
|
||||||
pca9633[i].led_cdev.brightness_set = pca9633_led_set;
|
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);
|
err = led_classdev_register(&client->dev, &pca9633[i].led_cdev);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
|
|
|
@ -27,9 +27,15 @@ enum pca9633_outdrv {
|
||||||
PCA9633_TOTEM_POLE, /* aka push-pull */
|
PCA9633_TOTEM_POLE, /* aka push-pull */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum pca9633_blink_type {
|
||||||
|
PCA9633_SW_BLINK,
|
||||||
|
PCA9633_HW_BLINK,
|
||||||
|
};
|
||||||
|
|
||||||
struct pca9633_platform_data {
|
struct pca9633_platform_data {
|
||||||
struct led_platform_data leds;
|
struct led_platform_data leds;
|
||||||
enum pca9633_outdrv outdrv;
|
enum pca9633_outdrv outdrv;
|
||||||
|
enum pca9633_blink_type blink_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* __LINUX_PCA9633_H*/
|
#endif /* __LINUX_PCA9633_H*/
|
||||||
|
|
Loading…
Reference in New Issue