Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8465b018 authored by Mark A. Greer's avatar Mark A. Greer Committed by Bryan Wu
Browse files

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: default avatarMark A. Greer <mgreer@animalcreek.com>
Reported-by: default avatarFengguang Wu <fengguang.wu@intel.com>
Signed-off-by: default avatarBryan Wu <cooloney@gmail.com>
parent 33b3a561
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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.

+132 −4
Original line number Diff line number Diff line
@@ -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)
+6 −0
Original line number Diff line number Diff line
@@ -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*/