[PATCH 1/1] leds: support for Philips PCA9531 8-bit led dimmer.

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

 



From: Rodolfo Giometti <giometti at linux.it>

Signed-off-by: Rodolfo Giometti <giometti at linux.it>
---
 drivers/leds/Kconfig        |    7 +
 drivers/leds/Makefile       |    1 +
 drivers/leds/leds-pca9531.c |  325 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/pca9531.h |   25 ++++
 4 files changed, 358 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-pca9531.c
 create mode 100644 include/linux/i2c/pca9531.h

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 9556262..52fca4c 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -163,6 +163,13 @@ config LEDS_PCA955X
 	  LED driver chips accessed via the I2C bus.  Supported
 	  devices include PCA9550, PCA9551, PCA9552, and PCA9553.
 
+config LEDS_PCA9531
+	tristate "LED Support using PCA9531 8-bit led dimmer"
+	depends on LEDS_CLASS && I2C
+	help
+	  This option enables led support by using Philips PCA9531
+	  8-bit led dimmer.
+
 comment "LED Triggers"
 
 config LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index ff7982b..4901575 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_LEDS_CM_X270)              += leds-cm-x270.o
 obj-$(CONFIG_LEDS_CLEVO_MAIL)		+= leds-clevo-mail.o
 obj-$(CONFIG_LEDS_HP6XX)		+= leds-hp6xx.o
 obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
+obj-$(CONFIG_LEDS_PCA9531)		+= leds-pca9531.o
 obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 
 # LED Triggers
diff --git a/drivers/leds/leds-pca9531.c b/drivers/leds/leds-pca9531.c
new file mode 100644
index 0000000..8cad2aa
--- /dev/null
+++ b/drivers/leds/leds-pca9531.c
@@ -0,0 +1,325 @@
+/*
+ *  pca9531.c - driver for 8-bit I2C led dimmer
+ *
+ *  Copyright (C) 2008 Rodolfo Giometti <giometti at linux.it>
+ *  Copyright (C) 2008 Eurotech S.p.A. <info at eurotech.it>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/leds.h>
+
+#include <linux/i2c/pca9531.h>
+
+#define DRIVER_VERSION		"0.40.0"
+
+/*
+ * Defines
+ */
+
+#define INPUT		0x00
+#define PWM0		0x02
+#define PWM1		0x04
+#define LS0		0x05
+#define LS1		0x06
+#define    LED_OFF	   0
+#define    LED_ON	   1
+#define    LED_PWM0	   2
+#define    LED_PWM1	   3
+#define    LED_MASK	   3
+
+/*
+ * Structs
+ */
+
+struct pca9531_data;
+struct pca9531_led {
+	struct pca9531_data *data;	/* back link */
+	struct led_classdev dev;
+	int new_level;
+
+	unsigned long pwm:2;
+	unsigned long inverted:1;
+
+	struct work_struct work;
+};
+
+struct pca9531_data {
+	struct i2c_client *client;
+	struct mutex update_lock;
+
+	struct pca9531_led led[8];
+};
+
+/*
+ * Management functions
+ */
+
+static int write_led_brightness(struct i2c_client *client, int id,
+				int pwm, unsigned int brightness)
+{
+	struct pca9531_data *data = i2c_get_clientdata(client);
+	u8 addr = id <= 3 ? LS0 : LS1;
+	u8 tmp;
+	int ret;
+
+	if (brightness > 255 ||
+		(pwm != PCA9531_PWM0 && pwm != PCA9531_PWM1 &&
+			pwm != PCA9531_ONOFF))
+		return -EINVAL;
+
+	mutex_lock(&data->update_lock);
+
+	/* Set brightness intensity.
+	 *
+	 * Please, note that if you (the driver user) define two or more
+	 * leds sharing one PWM unit, then all that leds will change
+	 * their intensity when you change the brightness intensity!
+	 * So, when configuring the drive, you should define at most one
+	 * led for each PWM unit.
+	 * However you (the driver user) are a real-programmer so you
+	 * shouldn't do such (evil) thing... ;)
+	 */
+	if (brightness && pwm != PCA9531_ONOFF) {
+		ret = i2c_smbus_write_byte_data(client,
+				pwm == PCA9531_PWM0 ? PWM0 : PWM1, brightness);
+		if (ret < 0)
+			goto exit;
+	}
+
+	/* Enable/disable led */
+	ret = i2c_smbus_read_byte_data(client, addr);
+	if (ret < 0)
+		goto exit;
+
+	ret &= ~(LED_MASK << ((id % 4) * 2));
+	tmp = LED_OFF;
+	if (brightness)
+		switch (pwm) {
+		case PCA9531_ONOFF:
+			tmp = LED_ON;
+			break;
+		case PCA9531_PWM0:
+			tmp = LED_PWM0;
+			break;
+		case PCA9531_PWM1:
+			tmp = LED_PWM1;
+			break;
+		}
+	ret |= tmp << ((id % 4) * 2);
+
+	ret = i2c_smbus_write_byte_data(client, addr, ret);
+	if (ret < 0)
+		goto exit;
+
+	ret = 0;
+exit:
+	mutex_unlock(&data->update_lock);
+
+	return ret;
+}
+
+/*
+ * Led support
+ */
+
+static void pca9531_led_set_work(struct work_struct *p)
+{
+	struct pca9531_led *led = container_of(p, struct pca9531_led, work);
+	struct pca9531_data *data = led->data;
+	struct i2c_client *client = data->client;
+	int id = led - &data->led[0];
+	int ret;
+
+	ret = write_led_brightness(client, id, led->pwm,
+					led->inverted ?
+						255 - led->new_level :
+						led->new_level);
+	if (ret)
+		dev_warn(&client->dev, "unable to set brightness for led%d\n",
+					id);
+}
+
+static void pca9531_led_set(struct led_classdev *p, enum led_brightness value)
+{
+	struct pca9531_led *led = container_of(p, struct pca9531_led, dev);
+
+	led->new_level = value;
+	schedule_work(&led->work);
+}
+
+/*
+ * Device init function
+ */
+
+static int pca9531_init_client(struct i2c_client *client,
+				struct pca9531_platform_data *pdata)
+{
+	int i, ret;
+
+	/* Set the platform defaults */
+	for (i = PCA9531_LED0; i <= PCA9531_LED7; i++) {
+		if (pdata[i].name)
+			ret = write_led_brightness(client, i, pdata[i].pwm,
+					pdata[i].inverted ?
+						255 - pdata[i].brightness :
+						pdata[i].brightness);
+		else
+			ret = write_led_brightness(client, i, PCA9531_ONOFF, 0);
+		if (ret)
+			dev_warn(&client->dev,
+				"unable to init LED%d brightness to %d\n", i,
+				pdata[i].brightness);
+	}
+
+	return 0;
+}
+
+/*
+ * I2C init/probing/exit functions
+ */
+
+static struct pca9531_platform_data defs[8];	/* all values are set to '0' */
+
+static struct i2c_driver pca9531_driver;
+static int __devinit pca9531_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+	struct pca9531_data *data;
+	struct pca9531_platform_data *pdata;
+	int i, ret = 0;
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE
+				     | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) {
+		ret = -EIO;
+		goto exit;
+	}
+
+	data = kzalloc(sizeof(struct pca9531_data), GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+	data->client = client;
+	i2c_set_clientdata(client, data);
+
+	/* Check platform data */
+	pdata = client->dev.platform_data;
+	if (!pdata)
+		pdata = defs;
+	for (i = PCA9531_LED0; i <= PCA9531_LED7; i++)
+		if (pdata[i].name) {
+			dev_dbg(&client->dev, "led%d:\n", i);
+			dev_dbg(&client->dev, "\tname %s\n", pdata[i].name);
+			dev_dbg(&client->dev, "\tdefault brightness %d\n",
+				pdata[i].brightness);
+			dev_dbg(&client->dev, "\tinverted brightness %d\n",
+				pdata[i].inverted);
+#ifdef CONFIG_LEDS_TRIGGERS
+			dev_dbg(&client->dev, "\ttrigger %s\n",
+				pdata[i].trigger);
+#endif
+			dev_dbg(&client->dev, "\tpwm %d\n", pdata[i].pwm);
+		}
+
+	mutex_init(&data->update_lock);
+
+	/* Initialize the PCA9531 chip */
+	ret = pca9531_init_client(client, pdata);
+	if (ret)
+		goto exit_kfree;
+
+	/* Register the led devices */
+	for (i = PCA9531_LED0; i <= PCA9531_LED7; i++)
+		if (pdata[i].name) {
+			data->led[i].data = data;
+			data->led[i].pwm = pdata[i].pwm;
+			data->led[i].dev.name = pdata[i].name;
+			data->led[i].inverted = pdata[i].inverted;
+			data->led[i].dev.brightness = pdata[i].brightness;
+			data->led[i].dev.brightness_set = pca9531_led_set;
+#ifdef CONFIG_LEDS_TRIGGERS
+			data->led[i].dev.default_trigger = pdata[i].trigger;
+#endif
+			INIT_WORK(&data->led[i].work, pca9531_led_set_work);
+			ret = led_classdev_register(&client->dev,
+							&data->led[i].dev);
+			if (ret < 0)
+				dev_warn(&client->dev, "unable to register "
+						"led%d into the system\n", i);
+		}
+
+	dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION);
+
+	return 0;
+
+exit_kfree:
+	kfree(data);
+exit:
+	return ret;
+}
+
+static int __devexit pca9531_remove(struct i2c_client *client)
+{
+	struct pca9531_data *data = i2c_get_clientdata(client);
+	int i;
+
+	for (i = PCA9531_LED1; i <= PCA9531_LED7; i++)
+		led_classdev_unregister(&data->led[i].dev);
+
+	kfree(i2c_get_clientdata(client));
+
+	return 0;
+}
+
+static const struct i2c_device_id pca9531_id[] = {
+	{ "pca9531", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, pca9531_id);
+
+static struct i2c_driver pca9531_driver = {
+	.driver = {
+		.name	= "pca9531",
+		.owner	= THIS_MODULE,
+	},
+	.probe	= pca9531_probe,
+	.remove	= __devexit_p(pca9531_remove),
+	.id_table = pca9531_id,
+};
+
+static int __init pca9531_init(void)
+{
+	return i2c_add_driver(&pca9531_driver);
+}
+
+static void __exit pca9531_exit(void)
+{
+	i2c_del_driver(&pca9531_driver);
+}
+
+MODULE_AUTHOR("Rodolfo Giometti <giometti at linux.it>");
+MODULE_DESCRIPTION("PCA9531 driver for 8-bit I2C led dimmer"
+			"dual LDO");
+MODULE_LICENSE("GPL");
+
+module_init(pca9531_init);
+module_exit(pca9531_exit);
diff --git a/include/linux/i2c/pca9531.h b/include/linux/i2c/pca9531.h
new file mode 100644
index 0000000..c00201d
--- /dev/null
+++ b/include/linux/i2c/pca9531.h
@@ -0,0 +1,25 @@
+#ifndef __LINUX_PCA9531_H
+#define __LINUX_PCA9531_H
+
+#define PCA9531_LED0	0
+#define PCA9531_LED1	1
+#define PCA9531_LED2	2
+#define PCA9531_LED3	3
+#define PCA9531_LED4	4
+#define PCA9531_LED5	5
+#define PCA9531_LED6	6
+#define PCA9531_LED7	7
+
+#define PCA9531_ONOFF	0
+#define PCA9531_PWM0	1
+#define PCA9531_PWM1	2
+
+struct pca9531_platform_data {
+	char *name;
+	char *trigger;			/* see available led triggers */
+	unsigned int brightness;	/* use values in [0:31] */
+	unsigned int inverted:1;	/* inverted polarity led */
+	unsigned int pwm:2;		/* 0 = pwm0, 1 = pwm1, 2 = none */
+};
+
+#endif /* __LINUX_PCA9531_H */
-- 
1.5.4.3





[Index of Archives]     [Linux Kernel]     [Linux Hardware Monitoring]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux