[PATCH v2] leds: pca9633: Add hardware blink support

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

 



From: "Mark A. Greer" <mgreer@xxxxxxxxxxxxxxx>

Add hardware blink support to the pca9633 driver.

NOTE: This patch 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.

Signed-off-by: Mark A. Greer <mgreer@xxxxxxxxxxxxxxx>
---
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

 drivers/leds/leds-pca9633.c | 121 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 117 insertions(+), 4 deletions(-)

diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c
index 90935e4..de74bb8 100644
--- a/drivers/leds/leds-pca9633.c
+++ b/drivers/leds/leds-pca9633.c
@@ -11,6 +11,12 @@
  *
  * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62)
  *
+ * Note that the blinking functionality 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.
  */
 
 #include <linux/module.h>
@@ -31,30 +37,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 +98,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 +142,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 +152,60 @@ 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 < PCA9632_BLINK_PERIOD_MIN) ||
+	    (period > PCA9632_BLINK_PERIOD_MAX))
+		return -EINVAL;
+
+	/*
+	 * 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)
@@ -205,8 +317,9 @@ 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;
+		pca9633[i].led_cdev.blink_set = pca9633_blink_set;
 
-		INIT_WORK(&pca9633[i].work, pca9633_led_work);
+		INIT_WORK(&pca9633[i].work, pca9633_work);
 
 		err = led_classdev_register(&client->dev, &pca9633[i].led_cdev);
 		if (err < 0)
-- 
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