diff options
Diffstat (limited to 'drivers/misc/leds-an30259a.c')
-rw-r--r-- | drivers/misc/leds-an30259a.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/drivers/misc/leds-an30259a.c b/drivers/misc/leds-an30259a.c new file mode 100644 index 0000000..4f113b6 --- /dev/null +++ b/drivers/misc/leds-an30259a.c @@ -0,0 +1,495 @@ +/* + * leds_an30259a.c - driver for panasonic AN30259A led control chip + * + * Copyright (C) 2011, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * Contact: Yufi Li <tai-yun.li@samsung.com> + * + * 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. + * + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/miscdevice.h> +#include <linux/leds-an30259a.h> + +/* AN30259A register map */ +#define AN30259A_REG_SRESET 0x00 +#define AN30259A_REG_LEDON 0x01 +#define AN30259A_REG_SEL 0x02 + +#define AN30259A_REG_LED1CC 0x03 +#define AN30259A_REG_LED2CC 0x04 +#define AN30259A_REG_LED3CC 0x05 + +#define AN30259A_REG_LED1SLP 0x06 +#define AN30259A_REG_LED2SLP 0x07 +#define AN30259A_REG_LED3SLP 0x08 + +#define AN30259A_REG_LED1CNT1 0x09 +#define AN30259A_REG_LED1CNT2 0x0a +#define AN30259A_REG_LED1CNT3 0x0b +#define AN30259A_REG_LED1CNT4 0x0c + +#define AN30259A_REG_LED2CNT1 0x0d +#define AN30259A_REG_LED2CNT2 0x0e +#define AN30259A_REG_LED2CNT3 0x0f +#define AN30259A_REG_LED2CNT4 0x10 + +#define AN30259A_REG_LED3CNT1 0x11 +#define AN30259A_REG_LED3CNT2 0x12 +#define AN30259A_REG_LED3CNT3 0x13 +#define AN30259A_REG_LED3CNT4 0x14 +#define AN30259A_REG_MAX 0x15 +/* MASK */ +#define AN30259A_MASK_IMAX 0xc0 +#define AN30259A_MASK_DELAY 0xf0 +#define AN30259A_SRESET 0x01 +#define LED_SLOPE_MODE 0x10 +#define LED_ON 0x01 + +#define DUTYMAX_MAX_VALUE 0x7f +#define DUTYMIN_MIN_VALUE 0x00 +#define SLPTT_MAX_VALUE 0x0f + +#define DETENTION_MAX_VALUE 60 +#define DELAY_MAX_VALUE 7500 +#define AN30259A_TIME_UNIT 500 +#define AN30259A_DT_TIME_UNIT 4 + +#define LED_R_MASK 0x00ff0000 +#define LED_G_MASK 0x0000ff00 +#define LED_B_MASK 0x000000ff +#define LED_R_SHIFT 16 +#define LED_G_SHIFT 8 +#define LED_IMAX_SHIFT 6 +#define AN30259A_CTN_RW_FLG 0x80 + +enum an30259a_led { + LED_R, + LED_G, + LED_B, +}; + +struct an30259a_data { + struct i2c_client *client; + struct miscdevice dev; + struct mutex mutex; + u8 shadow_reg[AN30259A_REG_MAX]; +}; + +static int leds_i2c_write_all(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + int ret; + + /*we need to set all the configs setting first, then LEDON later*/ + ret = i2c_smbus_write_i2c_block_data(client, + AN30259A_REG_SEL | AN30259A_CTN_RW_FLG, + AN30259A_REG_MAX - AN30259A_REG_SEL, + &data->shadow_reg[AN30259A_REG_SEL]); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c block write\n", + __func__); + return ret; + } + ret = i2c_smbus_write_byte_data(client, AN30259A_REG_LEDON, + data->shadow_reg[AN30259A_REG_LEDON]); + + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c byte write\n", + __func__); + return ret; + } + + return 0; +} + +/* + * leds_set_slope_mode() sets correct values to corresponding shadow registers. + * led: stands for LED_R or LED_G or LED_B. + * delay: represents for starting delay time in multiple of .5 second. + * dutymax: led at slope lighting maximum PWM Duty setting. + * dutymid: led at slope lighting middle PWM Duty setting. + * dutymin: led at slope lighting minimum PWM Duty Setting. + * slptt1: total time of slope operation 1 and 2, in multiple of .5 second. + * slptt2: total time of slope operation 3 and 4, in multiple of .5 second. + * dt1: detention time at each step in slope operation 1, in multiple of 4ms. + * dt2: detention time at each step in slope operation 2, in multiple of 4ms. + * dt3: detention time at each step in slope operation 3, in multiple of 4ms. + * dt4: detention time at each step in slope operation 4, in multiple of 4ms. + */ +static void leds_set_slope_mode(struct i2c_client *client, + enum an30259a_led led, u8 delay, + u8 dutymax, u8 dutymid, u8 dutymin, + u8 slptt1, u8 slptt2, + u8 dt1, u8 dt2, u8 dt3, u8 dt4) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + + data->shadow_reg[AN30259A_REG_LED1CNT1 + led * 4] = + dutymax << 4 | dutymid; + data->shadow_reg[AN30259A_REG_LED1CNT2 + led * 4] = + delay << 4 | dutymin; + data->shadow_reg[AN30259A_REG_LED1CNT3 + led * 4] = dt2 << 4 | dt1; + data->shadow_reg[AN30259A_REG_LED1CNT4 + led * 4] = dt4 << 4 | dt3; + data->shadow_reg[AN30259A_REG_LED1SLP + led] = slptt2 << 4 | slptt1; +} + +static void leds_on(struct i2c_client *client, enum an30259a_led led, + bool on, bool slopemode, + u8 ledcc) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + + if (on) + data->shadow_reg[AN30259A_REG_LEDON] |= LED_ON << led; + else { + data->shadow_reg[AN30259A_REG_LEDON] &= ~(LED_ON << led); + data->shadow_reg[AN30259A_REG_LED1CNT2 + led * 4] &= + ~AN30259A_MASK_DELAY; + } + if (slopemode) + data->shadow_reg[AN30259A_REG_LEDON] |= LED_SLOPE_MODE << led; + else + data->shadow_reg[AN30259A_REG_LEDON] &= + ~(LED_SLOPE_MODE << led); + + data->shadow_reg[AN30259A_REG_LED1CC + led] = ledcc; +} + +/* calculate the detention time for each step, return ms */ +static u8 calculate_dt(u8 min, u8 max, u16 time) +{ + u16 step_time; + u16 detention_time; + + if (min >= max) + return 0; + + step_time = time / (u16)(max - min); + detention_time = (step_time + 0x03) & 0xfffc; + /* the detention time at each step can be set as 4ms, 8ms, ...60ms */ + detention_time = (detention_time > DETENTION_MAX_VALUE) ? + DETENTION_MAX_VALUE : detention_time; + return detention_time; +} + +/* calculate the constant current output */ +static u8 calculate_cc(u32 brightness, enum an30259a_led led) +{ + u8 value = 0; + switch (led) { + case LED_R: + value = (brightness & LED_R_MASK) >> LED_R_SHIFT; + break; + case LED_G: + value = (brightness & LED_G_MASK) >> LED_G_SHIFT; + break; + case LED_B: + value = brightness & LED_B_MASK; + break; + default: + break; + } + return value; +} + +static u8 calculate_slope_tt(u8 mid, u8 dt1, u8 dt2, u16 time) +{ + u8 slptt; + u16 tt = (dt1 * (mid - DUTYMIN_MIN_VALUE) + + dt2 * (DUTYMAX_MAX_VALUE - mid)) * AN30259A_DT_TIME_UNIT + time; + /* round up to the nearest .5 seconds */ + slptt = (tt + AN30259A_TIME_UNIT - 1) / AN30259A_TIME_UNIT; + slptt = slptt > SLPTT_MAX_VALUE ? SLPTT_MAX_VALUE : slptt; + return slptt; +} + +static int leds_handle_cmds(struct i2c_client *client, + enum an30259a_led color, + struct an30259a_pr_control *leds) +{ + u8 cc, delay, dutymid, dt1, dt2, dt3, dt4, tt1, tt2; + + cc = calculate_cc(leds->color, color); + switch (leds->state) { + case LED_LIGHT_OFF: + leds_on(client, color, false, false, 0); + break; + case LED_LIGHT_ON: + leds_on(client, color, true, false, cc); + break; + case LED_LIGHT_PULSE: + /* + * PULSE is a special case of slope with delay=0, + * dutymid = dutymax ,dt1,dt2,dt3,dt4 are all zero + */ + leds_on(client, color, true, true, cc); + leds_set_slope_mode(client, color, 0, + DUTYMAX_MAX_VALUE >> 3, DUTYMAX_MAX_VALUE >> 3, + DUTYMIN_MIN_VALUE >> 3, + (leds->time_on + AN30259A_TIME_UNIT - 1) / + AN30259A_TIME_UNIT, + (leds->time_off + AN30259A_TIME_UNIT - 1) / + AN30259A_TIME_UNIT, + 0, 0, 0, 0); + + break; + case LED_LIGHT_SLOPE: + if (leds->mid_brightness > DUTYMAX_MAX_VALUE) + return -EINVAL; + + delay = ((leds->start_delay > DELAY_MAX_VALUE) ? + DELAY_MAX_VALUE : leds->start_delay) / + AN30259A_TIME_UNIT; + + dutymid = leds->mid_brightness >> 3; + dt1 = calculate_dt(DUTYMIN_MIN_VALUE, leds->mid_brightness, + leds->time_slope_up_1) / AN30259A_DT_TIME_UNIT; + dt2 = calculate_dt(leds->mid_brightness, DUTYMAX_MAX_VALUE, + leds->time_slope_up_2) / AN30259A_DT_TIME_UNIT; + dt3 = calculate_dt(leds->mid_brightness, DUTYMAX_MAX_VALUE, + leds->time_slope_down_1) / + AN30259A_DT_TIME_UNIT; + dt4 = calculate_dt(DUTYMIN_MIN_VALUE, leds->mid_brightness, + leds->time_slope_down_2) / + AN30259A_DT_TIME_UNIT; + tt1 = calculate_slope_tt(leds->mid_brightness, + dt1, dt2, leds->time_on); + tt2 = calculate_slope_tt(leds->mid_brightness, + dt4, dt3, leds->time_off); + + leds_on(client, color, true, true, cc); + leds_set_slope_mode(client, color, delay, + DUTYMAX_MAX_VALUE >> 3, dutymid, DUTYMIN_MIN_VALUE >> 3, + tt1, tt2, dt1, dt2, dt3, dt4); + break; + } + + return 0; +} + +static int leds_set_imax(struct i2c_client *client, u8 imax) +{ + int ret; + struct an30259a_data *data = i2c_get_clientdata(client); + + data->shadow_reg[AN30259A_REG_SEL] &= ~AN30259A_MASK_IMAX; + data->shadow_reg[AN30259A_REG_SEL] |= imax << LED_IMAX_SHIFT; + + ret = i2c_smbus_write_byte_data(client, AN30259A_REG_SEL, + data->shadow_reg[AN30259A_REG_SEL]); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c write\n", + __func__); + } + return 0; +} + +static long an30250a_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct an30259a_data *leds_data = container_of(file->private_data, + struct an30259a_data, dev); + struct i2c_client *client = leds_data->client; + int retval, i; + u8 imax; + struct an30259a_pr_control leds[3]; + + mutex_lock(&leds_data->mutex); + + switch (cmd) { + case AN30259A_PR_SET_LED: + retval = copy_from_user(leds, (unsigned char __user *)arg, + sizeof(struct an30259a_pr_control)); + if (retval) + break; + + for (i = LED_R; i <= LED_B; i++) { + retval = leds_handle_cmds(client, i, leds); + if (retval < 0) + goto an30259a_ioctl_failed; + } + retval = leds_i2c_write_all(client); + break; + case AN30259A_PR_SET_LEDS: + retval = copy_from_user(leds, (unsigned char __user *)arg, + 3 * sizeof(struct an30259a_pr_control)); + + if (retval) + break; + + for (i = LED_R; i <= LED_B; i++) { + retval = leds_handle_cmds(client, i, &leds[i]); + if (retval < 0) + goto an30259a_ioctl_failed; + } + retval = leds_i2c_write_all(client); + break; + + case AN30259A_PR_SET_IMAX: + retval = copy_from_user(&imax, (unsigned char __user *)arg, + sizeof(u8)); + if (retval) + break; + + retval = leds_set_imax(client, imax); + break; + + default: + dev_err(&client->adapter->dev, + "%s: Unknown cmd %x, arg %lu\n", + __func__, cmd, arg); + retval = -EINVAL; + break; + } + +an30259a_ioctl_failed: + mutex_unlock(&leds_data->mutex); + return retval; +} + +static int an30250a_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations an30259a_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = an30250a_ioctl, + .open = an30250a_open, +}; + +static int __devinit an30259a_initialize(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + int ret; + + /* reset an30259a*/ + ret = i2c_smbus_write_byte_data(client, AN30259A_REG_SRESET, + AN30259A_SRESET); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c write (reg = 0x%2x)\n", + __func__, AN30259A_REG_SRESET); + return ret; + } + ret = i2c_smbus_read_i2c_block_data(client, + AN30259A_REG_SRESET | AN30259A_CTN_RW_FLG, + AN30259A_REG_MAX, data->shadow_reg); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: failure on i2c read block(ledxcc)\n", + __func__); + return ret; + } + + return 0; +} + +static int __devinit an30259a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct an30259a_data *data; + int ret; + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "need I2C_FUNC_I2C.\n"); + return -ENODEV; + } + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&client->adapter->dev, + "failed to allocate driver data.\n"); + return -ENOMEM; + } + + data->client = client; + + i2c_set_clientdata(client, data); + + mutex_init(&data->mutex); + + /* initialize LED */ + ret = an30259a_initialize(client); + if (ret < 0) { + dev_err(&client->adapter->dev, "failure on initialization\n"); + goto exit; + } + + data->dev.minor = MISC_DYNAMIC_MINOR; + data->dev.name = "an30259a_leds"; + data->dev.fops = &an30259a_fops; + ret = misc_register(&data->dev); + if (ret < 0) { + dev_err(&client->adapter->dev, + "%s: ERROR: misc_register returned %d\n", + __func__, ret); + goto exit; + } + + return 0; +exit: + mutex_destroy(&data->mutex); + kfree(data); + return ret; +} + +static int __devexit an30259a_remove(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + + dev_dbg(&client->adapter->dev, "%s\n", __func__); + misc_deregister(&data->dev); + mutex_destroy(&data->mutex); + kfree(data); + return 0; +} + +static struct i2c_device_id an30259a_id[] = { + {"an30259a", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, an30259a_id); + +static struct i2c_driver an30259a_i2c_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "an30259a", + }, + .id_table = an30259a_id, + .probe = an30259a_probe, + .remove = __devexit_p(an30259a_remove), +}; + +static int __init an30259a_init(void) +{ + return i2c_add_driver(&an30259a_i2c_driver); +} + +static void __exit an30259a_exit(void) +{ + i2c_del_driver(&an30259a_i2c_driver); +} + +module_init(an30259a_init); +module_exit(an30259a_exit); + +MODULE_DESCRIPTION("AN30259A LED driver"); +MODULE_AUTHOR("Yufi Li <tai-yun.li@samsung.com"); +MODULE_LICENSE("GPL v2"); |