From f6b1f49300a24c329f5d1049031f711c6268be95 Mon Sep 17 00:00:00 2001 From: Matt Hsu Date: Thu, 18 Sep 2008 11:14:50 +0100 Subject: [PATCH] gta03-pca9632.patch - pca9632 is a LED driver which will be adopted in gta03. Signed-off-by: Matt Hsu --- drivers/i2c/chips/Kconfig | 9 + drivers/i2c/chips/Makefile | 1 + drivers/i2c/chips/pca9632.c | 551 +++++++++++++++++++++++++++++++++++++++++++ drivers/i2c/chips/pca9632.h | 24 ++ include/linux/i2c-id.h | 1 + 5 files changed, 586 insertions(+), 0 deletions(-) create mode 100644 drivers/i2c/chips/pca9632.c create mode 100644 drivers/i2c/chips/pca9632.h diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 1d6d36f..9af3c62 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -193,4 +193,13 @@ config SENSORS_TSL256X This driver can also be built as a module. If so, the module will be called tsl256x. +config PCA9632 + tristate "Philips/NXP PCA9632 low power LED driver" + depends on I2C + help + If you say yes here you get support for the Philips/NXP PCA9632 + LED driver. + + This driver can also be built as a module. If so, the module + will be called pca9632. endmenu diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 4b6ba04..67cd1e5 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_TPS65010) += tps65010.o obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o obj-$(CONFIG_SENSORS_TSL256X) += tsl256x.o +obj-$(CONFIG_PCA9632) += pca9632.o ifeq ($(CONFIG_I2C_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/i2c/chips/pca9632.c b/drivers/i2c/chips/pca9632.c new file mode 100644 index 0000000..deda6a1 --- /dev/null +++ b/drivers/i2c/chips/pca9632.c @@ -0,0 +1,551 @@ +/* + * Philips/NXP PCA9632 low power LED driver. + * Copyright (C) 2008 Matt Hsu + * + * low_level implementation are based on pcf50606 driver + * + * 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; version 2 of the License. + * + * TODO: + * - attach ledclass?? + * - add platform data + * + */ + +#include +#include +#include +#include + +#include "pca9632.h" + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x62, I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(pca9632); + +enum pca9632_pwr_state { + PCA9632_NORMAL, + PCA9632_SLEEP, +}; + +enum pca9632_led_output { + PCA9632_OFF, + PCA9632_ON, + PCA9632_CTRL_BY_PWM, + PCA9632_CTRL_BY_PWM_GRPPWM, +}; + +static const char *led_output_name[] = { + [PCA9632_OFF] = "off", + [PCA9632_ON] = "fully-on", + [PCA9632_CTRL_BY_PWM] = "ctrl-by-pwm", + [PCA9632_CTRL_BY_PWM_GRPPWM] = "ctrl-by-pwm-grppwm", +}; + +struct pca9632_data { + struct i2c_client client; + struct mutex lock; +}; + +static struct i2c_driver pca9632_driver; +static struct platform_device *pca9632_pdev; + +static int pca9632_attach_adapter(struct i2c_adapter *adapter); +static int pca9632_detach_client(struct i2c_client *client); + +static int __reg_write(struct pca9632_data *pca, u_int8_t reg, u_int8_t val) +{ + return i2c_smbus_write_byte_data(&pca->client, reg, val); +} + +static int reg_write(struct pca9632_data *pca, u_int8_t reg, u_int8_t val) +{ + int ret; + + mutex_lock(&pca->lock); + ret = __reg_write(pca, reg, val); + mutex_unlock(&pca->lock); + + return ret; +} + +static int32_t __reg_read(struct pca9632_data *pca, u_int8_t reg) +{ + int32_t ret; + + ret = i2c_smbus_read_byte_data(&pca->client, reg); + + return ret; +} + +static u_int8_t reg_read(struct pca9632_data *pca, u_int8_t reg) +{ + int32_t ret; + + mutex_lock(&pca->lock); + ret = __reg_read(pca, reg); + mutex_unlock(&pca->lock); + + return ret & 0xff; +} + +static int reg_set_bit_mask(struct pca9632_data *pca, + u_int8_t reg, u_int8_t mask, u_int8_t val) +{ + int ret; + u_int8_t tmp; + + val &= mask; + + mutex_lock(&pca->lock); + + tmp = __reg_read(pca, reg); + tmp &= ~mask; + tmp |= val; + ret = __reg_write(pca, reg, tmp); + + mutex_unlock(&pca->lock); + + return ret; +} + +static inline int calc_dc(uint8_t idc) +{ + return (idc * 100) / 256; +} + +/* + * Software reset + */ +static int software_rst(struct i2c_adapter *adapter) +{ + u8 buf[] = { 0xa5, 0x5a }; + + struct i2c_msg msg[] = { + { + .addr = 0x3, + .flags = 0, + .buf = &buf, + .len = sizeof(buf) + } + }; + + return i2c_transfer(adapter, msg, 1); +} + +/* + * Group dmblnk control + */ +static void config_group_dmblnk(struct pca9632_data *pca, int group_dmblnk_mode) +{ + reg_set_bit_mask(pca, PCA9632_REG_MODE2, 0x20, + group_dmblnk_mode << PCA9632_DMBLNK_SHIFT); +} + +static int get_group_dmblnk(struct pca9632_data *pca) +{ + return reg_read(pca, PCA9632_REG_MODE2) >> PCA9632_DMBLNK_SHIFT; +} + +static ssize_t show_group_dmblnk(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + + if (get_group_dmblnk(pca)) + return sprintf(buf, "blinking\n"); + else + return sprintf(buf, "dimming\n"); +} + +static ssize_t set_group_dmblnk(struct device *dev, struct device_attribute + *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + unsigned int mode = simple_strtoul(buf, NULL, 10); + + if (mode) + dev_info(&pca->client.dev, "blinking\n"); + else + dev_info(&pca->client.dev, "dimming\n"); + + config_group_dmblnk(pca, mode); + + return count; +} + +static DEVICE_ATTR(group_dmblnk, S_IRUGO | S_IWUSR, show_group_dmblnk, + set_group_dmblnk); + +static int reg_id_by_name(const char *name) +{ + int reg_id = -1; + + if (!strncmp(name, "led0", 4)) + reg_id = PCA9632_REG_PWM0; + else if (!strncmp(name, "led1", 4)) + reg_id = PCA9632_REG_PWM1; + else if (!strncmp(name, "led2", 4)) + reg_id = PCA9632_REG_PWM2; + else if (!strncmp(name, "led3", 4)) + reg_id = PCA9632_REG_PWM3; + + return reg_id; +} + +static int get_led_output(struct pca9632_data *pca, int ldrx) +{ + u_int8_t led_state; + + ldrx = ldrx - 2; + led_state = reg_read(pca, PCA9632_REG_LEDOUT); + led_state = (led_state >> (2 * ldrx)) & 0x03; + + return led_state; +} + +static void config_led_output(struct pca9632_data *pca, int ldrx, + enum pca9632_led_output led_output) +{ + u_int8_t mask; + int tmp; + + ldrx = ldrx - 2; + mask = 0x03 << (2 * ldrx); + tmp = reg_set_bit_mask(pca, PCA9632_REG_LEDOUT, + mask, led_output << (2 * ldrx)); +} + +/* + * Individual brightness control + */ +static ssize_t show_brightness(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + int ldrx; + + ldrx = reg_id_by_name(attr->attr.name); + + switch (get_led_output(pca, ldrx)) { + + case PCA9632_OFF: + case PCA9632_ON: + return sprintf(buf, "%s", + led_output_name[get_led_output(pca, ldrx)]); + + case PCA9632_CTRL_BY_PWM: + return sprintf(buf, "%d%% \n", calc_dc(reg_read(pca, ldrx))); + + case PCA9632_CTRL_BY_PWM_GRPPWM: + /* check group dmblnk */ + if (get_group_dmblnk(pca)) + return sprintf(buf, "%d%% \n", + calc_dc(reg_read(pca, ldrx))); + return sprintf(buf, "%d%% \n", + calc_dc((reg_read(pca, ldrx) & 0xfc))); + default: + break; + } + + return sprintf(buf, "invalid argument\n"); +} + +static ssize_t set_brightness(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + unsigned int pwm = simple_strtoul(buf, NULL, 10); + int ldrx; + + ldrx = reg_id_by_name(attr->attr.name); + reg_set_bit_mask(pca, ldrx, 0xff, pwm); + + return count; +} + +static +DEVICE_ATTR(led0_pwm, S_IRUGO | S_IWUSR, show_brightness, set_brightness); +static +DEVICE_ATTR(led1_pwm, S_IRUGO | S_IWUSR, show_brightness, set_brightness); +static +DEVICE_ATTR(led2_pwm, S_IRUGO | S_IWUSR, show_brightness, set_brightness); +static +DEVICE_ATTR(led3_pwm, S_IRUGO | S_IWUSR, show_brightness, set_brightness); + +/* + * Group frequency control + */ +static ssize_t show_group_freq(struct device *dev, struct device_attribute + *attr, char *buf) +{ + uint32_t period; + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + + period = ((reg_read(pca, PCA9632_REG_GRPFREQ) + 1) * 1000) / 24; + + return sprintf(buf, "%d ms\n", period); +} + +static ssize_t set_group_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + + unsigned int freq = simple_strtoul(buf, NULL, 10); + reg_write(pca, PCA9632_REG_GRPFREQ, freq); + return count; +} + +static +DEVICE_ATTR(group_freq, S_IRUGO | S_IWUSR, show_group_freq, set_group_freq); + +/* + * Group duty cycle tonrol* + */ +static ssize_t show_group_dc(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + + if (get_group_dmblnk(pca)) { + + if (reg_read(pca, PCA9632_REG_GRPFREQ) <= 0x03) + return sprintf(buf, "%d%% \n", + calc_dc(reg_read(pca, PCA9632_REG_GRPPWM) & 0xfc)); + + return sprintf(buf, "%d%% \n", calc_dc(reg_read(pca, + PCA9632_REG_GRPPWM))); + } + + return sprintf(buf, "%d%% \n", calc_dc(reg_read(pca, + PCA9632_REG_GRPPWM) & 0xf0)); +} + +static ssize_t set_group_dc(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + + unsigned int dc = simple_strtoul(buf, NULL, 10); + + reg_set_bit_mask(pca, PCA9632_REG_GRPPWM, 0xff, dc); + + return count; +} + +static DEVICE_ATTR(group_dc, S_IRUGO | S_IWUSR, show_group_dc, set_group_dc); + +/* + * LED driver output + */ +static ssize_t show_led_output(struct device *dev, struct device_attribute + *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + int ldrx; + + ldrx = reg_id_by_name(attr->attr.name); + + return sprintf(buf, "%s \n", + led_output_name[get_led_output(pca, ldrx)]); + +} +static ssize_t set_led_output(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pca9632_data *pca = i2c_get_clientdata(client); + enum pca9632_led_output led_output; + int ldrx; + + led_output = simple_strtoul(buf, NULL, 10); + ldrx = reg_id_by_name(attr->attr.name); + config_led_output(pca, ldrx, led_output); + + return count; +} + +static +DEVICE_ATTR(led0_output, S_IRUGO | S_IWUSR, show_led_output, set_led_output); +static +DEVICE_ATTR(led1_output, S_IRUGO | S_IWUSR, show_led_output, set_led_output); +static +DEVICE_ATTR(led2_output, S_IRUGO | S_IWUSR, show_led_output, set_led_output); +static +DEVICE_ATTR(led3_output, S_IRUGO | S_IWUSR, show_led_output, set_led_output); + +static struct attribute *pca_sysfs_entries[] = { + &dev_attr_group_dmblnk.attr, + &dev_attr_led0_pwm.attr, + &dev_attr_led1_pwm.attr, + &dev_attr_led2_pwm.attr, + &dev_attr_led3_pwm.attr, + &dev_attr_group_dc.attr, + &dev_attr_group_freq.attr, + &dev_attr_led0_output.attr, + &dev_attr_led1_output.attr, + &dev_attr_led2_output.attr, + &dev_attr_led3_output.attr, + NULL +}; + +static struct attribute_group pca_attr_group = { + .name = NULL, /* put in device directory */ + .attrs = pca_sysfs_entries, +}; + +#ifdef CONFIG_PM +static int pca9632_suspend(struct device *dev, pm_message_t state) +{ + /* FIXME: Not implemented */ + return 0; +} + +static int pca9632_resume(struct device *dev) +{ + /* FIXME: Not implemented */ + return 0; +} +#else +#define pca9632_suspend NULL +#define pca9632_resume NULL +#endif + +static struct i2c_driver pca9632_driver = { + .driver = { + .name = "pca9632", + .suspend = pca9632_suspend, + .resume = pca9632_resume, + }, + .id = I2C_DRIVERID_PCA9632, + .attach_adapter = pca9632_attach_adapter, + .detach_client = pca9632_detach_client, +}; + +static int pca9632_detect(struct i2c_adapter *adapter, int address, int kind) +{ + struct i2c_client *new_client; + struct pca9632_data *pca; + int err; + + pca = kzalloc(sizeof(struct pca9632_data), GFP_KERNEL); + if (!pca) + return -ENOMEM; + + mutex_init(&pca->lock); + + new_client = &pca->client; + i2c_set_clientdata(new_client, pca); + new_client->addr = address; + new_client->adapter = adapter; + new_client->driver = &pca9632_driver; + new_client->flags = 0; + + strlcpy(new_client->name, "pca9632", I2C_NAME_SIZE); + + /* register with i2c core */ + err = i2c_attach_client(new_client); + if (err) + goto exit_kfree; + + err = sysfs_create_group(&new_client->dev.kobj, &pca_attr_group); + if (err) + goto exit_detach; + + /* software reset */ + if (!software_rst(adapter)) + dev_info(&pca->client.dev, "pca9632 sw-rst done\n"); + + /* enter normal mode */ + reg_set_bit_mask(pca, PCA9632_REG_MODE1, 0x10, PCA9632_NORMAL); + + return 0; + +exit_detach: + i2c_detach_client(new_client); +exit_kfree: + kfree(pca); + + return err; +} + +static int pca9632_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, pca9632_detect); +} + +static int pca9632_detach_client(struct i2c_client *client) +{ + int err; + + sysfs_remove_group(&client->dev.kobj, &pca_attr_group); + err = i2c_detach_client(client); + + if (err) + return err; + + kfree(i2c_get_clientdata(client)); + + return 0; +} + +static int __init pca9632_plat_probe(struct platform_device *pdev) +{ + /* FIXME: platform data should be attached here */ + pca9632_pdev = pdev; + + return 0; +} + +static int pca9632_plat_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver pca9632_plat_driver = { + .probe = pca9632_plat_probe, + .remove = pca9632_plat_remove, + .driver = { + .owner = THIS_MODULE, + .name = "pca9632", + }, +}; + +static int __init pca9632_init(void) +{ + int rc; + + rc = platform_driver_register(&pca9632_plat_driver); + if (!rc) + i2c_add_driver(&pca9632_driver); + + return rc; +} + +static void __exit pca9632_exit(void) +{ + i2c_del_driver(&pca9632_driver); + + platform_driver_unregister(&pca9632_plat_driver); +} + +MODULE_AUTHOR("Matt Hsu "); +MODULE_DESCRIPTION("NXP PCA9632 driver"); +MODULE_LICENSE("GPL"); + +module_init(pca9632_init); +module_exit(pca9632_exit); diff --git a/drivers/i2c/chips/pca9632.h b/drivers/i2c/chips/pca9632.h new file mode 100644 index 0000000..be2892e --- /dev/null +++ b/drivers/i2c/chips/pca9632.h @@ -0,0 +1,24 @@ +#ifndef _PCA9632_H +#define _PCA9632_H + + +enum pca9632_regs{ + + PCA9632_REG_MODE1 = 0x00, + PCA9632_REG_MODE2 = 0x01, + PCA9632_REG_PWM0 = 0x02, + PCA9632_REG_PWM1 = 0x03, + PCA9632_REG_PWM2 = 0x04, + PCA9632_REG_PWM3 = 0x05, + PCA9632_REG_GRPPWM = 0x06, + PCA9632_REG_GRPFREQ = 0x07, + PCA9632_REG_LEDOUT = 0x08, + PCA9632_REG_SUBADDR1 = 0x09, + PCA9632_REG_SUBADDR2 = 0x0a, + PCA9632_REG_SUBADDR3 = 0x0b, + PCA9632_REG_ALLCALLADR1 = 0x0c, +}; + +#define PCA9632_DMBLNK_SHIFT 5 + +#endif /* _PCA9632_H */ diff --git a/include/linux/i2c-id.h b/include/linux/i2c-id.h index ee60156..d5a63fe 100644 --- a/include/linux/i2c-id.h +++ b/include/linux/i2c-id.h @@ -170,6 +170,7 @@ #define I2C_DRIVERID_PCF50606 1049 #define I2C_DRIVERID_TSL256X 1050 #define I2C_DRIVERID_PCF50633 1051 +#define I2C_DRIVERID_PCA9632 1052 /* * ---- Adapter types ---------------------------------------------------- -- 1.5.6.5