Rodolfo Giometti wrote: > From: Rodolfo Giometti <giometti at linux.it> > The functionality of pca9531 seems to be a simplified pca9532, perhaps it would be better to fold both into the same driver? > 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; > You don't really need a long for that.. > + > + 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 > These definitions are not really needed. > + > +#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 */ >