From c6da2cfeb05178a11c6d062a06f8078150ee492f Mon Sep 17 00:00:00 2001 From: codeworkx Date: Sat, 2 Jun 2012 13:09:29 +0200 Subject: samsung update 1 --- drivers/leds/Kconfig | 47 +++ drivers/leds/Makefile | 6 + drivers/leds/leds-aat1290a.c | 247 ++++++++++++ drivers/leds/leds-an30259a.c | 876 +++++++++++++++++++++++++++++++++++++++++++ drivers/leds/leds-lp5521.c | 294 ++++++++++++++- drivers/leds/leds-max77693.c | 359 ++++++++++++++++++ drivers/leds/leds-max8997.c | 313 ++++++++++++++++ drivers/leds/leds-spfcw043.c | 202 ++++++++++ drivers/leds/ledtrig-sleep.c | 80 ++++ 9 files changed, 2406 insertions(+), 18 deletions(-) create mode 100644 drivers/leds/leds-aat1290a.c create mode 100644 drivers/leds/leds-an30259a.c create mode 100644 drivers/leds/leds-max77693.c create mode 100644 drivers/leds/leds-max8997.c create mode 100644 drivers/leds/leds-spfcw043.c create mode 100644 drivers/leds/ledtrig-sleep.c (limited to 'drivers/leds') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 713d43b..d63605c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -5,6 +5,13 @@ config LEDS_GPIO_REGISTER As this function is used by arch code it must not be compiled as a module. +config LEDS_SPFCW043 + bool "LED support for the SPFCW043" + default n + help + This option enables support for the LEDs on the SPFCW043. + + menuconfig NEW_LEDS bool "LED Support" help @@ -211,6 +218,15 @@ config LEDS_LP3944 To compile this driver as a module, choose M here: the module will be called leds-lp3944. +config LEDS_AN30259A + tristate "LED driver for panasonic AN30259A RGB LED" + depends on I2C + help + This option enables support for AN30259A RGB LED chips + accessed via the I2C bus. + AN30259A has three channel LED driver. By synchronous clock + function, combined operation is possible. + config LEDS_LP5521 tristate "LED Support for N.S. LP5521 LED driver chip" depends on LEDS_CLASS && I2C @@ -399,6 +415,31 @@ config LEDS_ASIC3 cannot be used. This driver supports hardware blinking with an on+off period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700. +config LEDS_MAX77693 + bool "LED support for the MAX77693" + depends on LEDS_CLASS + depends on MFD_MAX77693 + default y + help + This option enables support for the LEDs on the MAX77693. + +config LEDS_MAX8997 + tristate "MAX8997 LED support" + depends on LEDS_CLASS + default n + help + This option enables support for the LEDs on the Max8997. + You may control the hard drive or power LEDs on the Max8997 + function. Using this driver can support the brightness of LED + by controling current of the Max8997. + +config LEDS_AAT1290A + bool "LED support for the AAT1290A" + depends on LEDS_CLASS + default n + help + This option enables support for the LEDs on the AAT1290A. + config LEDS_TRIGGERS bool "LED Trigger support" depends on LEDS_CLASS @@ -466,6 +507,12 @@ config LEDS_TRIGGER_DEFAULT_ON This allows LEDs to be initialised in the ON state. If unsure, say Y. +config LEDS_TRIGGER_SLEEP + tristate "LED Sleep Mode Trigger" + depends on LEDS_TRIGGERS && HAS_EARLYSUSPEND + help + This turns LEDs on when the screen is off but the cpu still running. + comment "iptables trigger is under Netfilter config (LED target)" depends on LEDS_TRIGGERS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index bbfd2e3..e538739 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -43,6 +43,11 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o +obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o +obj-$(CONFIG_LEDS_AAT1290A) += leds-aat1290a.o +obj-$(CONFIG_LEDS_SPFCW043) += leds-spfcw043.o +obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o +obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o @@ -54,3 +59,4 @@ obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o +obj-$(CONFIG_LEDS_TRIGGER_SLEEP) += ledtrig-sleep.o diff --git a/drivers/leds/leds-aat1290a.c b/drivers/leds/leds-aat1290a.c new file mode 100644 index 0000000..8889360 --- /dev/null +++ b/drivers/leds/leds-aat1290a.c @@ -0,0 +1,247 @@ +/* + * LED driver for AAT1290A - leds-aat1290a.c + * + * Copyright (C) 2011 DongHyun Chang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +int *aat1290a_ctrl; +struct aat1290a_led_platform_data *led_pdata; + +struct class *flash_class; +struct device *aat1290a_dev; + +static int aat1290a_setPower(int onoff, int level) +{ + int i; + if (led_pdata->status == STATUS_AVAILABLE) { + led_pdata->torch_en(0); + led_pdata->torch_set(0); + led_pdata->switch_sel(!onoff); + /* onoff = 0 - select aat1290a, onoff = 1 - select ISP*/ + udelay(10); + if (onoff) { + led_pdata->torch_set(1); + for (i = 1; i < level; i++) { + udelay(1); + led_pdata->torch_set(0); + udelay(1); + led_pdata->torch_set(1); + } + } + } else { + LED_ERROR("GPIO is not available!"); + return -ENODEV; + } + + return 0; +} + +static int aat1290a_freeGpio(void) +{ + if (led_pdata->status == STATUS_AVAILABLE) { + if (led_pdata->freeGpio()) { + LED_ERROR("Can't free GPIO for led\n"); + return -ENODEV; + } + led_pdata->status = STATUS_UNAVAILABLE; + } else { + LED_ERROR("GPIO already free!"); + } + + return 0; +} + +static int aat1290a_setGpio(void) +{ + if (led_pdata->status != STATUS_AVAILABLE) { + if (led_pdata->setGpio()) { + LED_ERROR("Can't set GPIO for led\n"); + return -ENODEV; + } + led_pdata->status = STATUS_AVAILABLE; + } else { + LED_ERROR("GPIO already set!"); + } + + return 0; +} + +static int aat1290a_open(struct inode *inode, struct file *file) +{ + int ret; + led_pdata->brightness = TORCH_BRIGHTNESS_50; /*normal setting*/ + led_pdata->status = STATUS_INVALID; + + ret = aat1290a_setGpio(); + + file->private_data = (int *) aat1290a_ctrl; + + return ret; +} + +static int aat1290a_release(struct inode *inode, struct file *file) +{ + if (aat1290a_freeGpio()) { + LED_ERROR("aat1290a_freeGpio failed!\n"); + return -ENODEV; + } + + return 0; +} + + +static long aat1290a_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int *ctrl = (int *)file->private_data; + + if (!ctrl) { + LED_ERROR("invalid input data\n"); + return -1; + } + + switch (cmd) { + case IOCTL_AAT1290A_SET_BRIGHTNESS: + if (*ctrl < 0 || *ctrl > TORCH_BRIGHTNESS_0) + return -EINVAL; + led_pdata->brightness = *ctrl; + break; + case IOCTL_AAT1290A_GET_STATUS: + *ctrl = led_pdata->status; + break; + case IOCTL_AAT1290A_SET_POWER: + aat1290a_setPower(*ctrl, led_pdata->brightness); + break; + default: + LED_ERROR("invalid input data\n"); + return -EINVAL; + } + + return 0; +} + +static ssize_t aat1290a_power(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int brightness = 0; + + if (buf[0] < 0x30 || buf[0] > 0x39) { + LED_ERROR("data is wrong!"); + return count; + } else if (buf[1] < 0x30 || buf[1] > 0x39) + brightness = (int) (buf[0] - 0x30); + else { + brightness = brightness + (int) (buf[0] - 0x30) * 10; + brightness = brightness + (int) (buf[1] - 0x30); + } + + if (brightness < 0 || brightness >= TORCH_BRIGHTNESS_INVALID) { + LED_ERROR("brightness data is wrong! %d", brightness); + return count; + } + + if (brightness == 0) { + aat1290a_setPower(0, 0); + /* + if (aat1290a_freeGpio()) { + LED_ERROR("aat1290a_freeGpio failed!\n"); + return count; + } + */ + } else { + if (aat1290a_setGpio()) { + LED_ERROR("aat1290a_setGpio failed!\n"); + return count; + } + aat1290a_setPower(1, brightness); + } + + return count; +} + +static DEVICE_ATTR(flash_power, S_IWUSR|S_IWGRP|S_IROTH, + NULL, aat1290a_power); + +static const struct file_operations aat1290a_fops = { + .owner = THIS_MODULE, + .open = aat1290a_open, + .release = aat1290a_release, + .unlocked_ioctl = aat1290a_ioctl, +}; + +static struct miscdevice aat1290a_miscdev = { + .minor = 255, + .name = "aat1290a_led", + .fops = &aat1290a_fops, +}; + +static int aat1290a_led_probe(struct platform_device *pdev) +{ + LED_ERROR("Probe\n"); + led_pdata = + (struct aat1290a_led_platform_data *) pdev->dev.platform_data; + + if (misc_register(&aat1290a_miscdev)) { + LED_ERROR("Failed to register misc driver\n"); + return -ENODEV; + } + + flash_class = class_create(THIS_MODULE, "flash"); + if (IS_ERR(flash_class)) + LED_ERROR("Failed to create class(flash)!\n"); + aat1290a_dev = device_create(flash_class, NULL, 0, NULL, "flash"); + if (IS_ERR(aat1290a_dev)) + LED_ERROR("Failed to create device(flash)!\n"); + + if (device_create_file(aat1290a_dev, &dev_attr_flash_power) < 0) { + LED_ERROR("failed to create device file, %s\n", + dev_attr_flash_power.attr.name); + } + led_pdata->initGpio(); + aat1290a_setGpio(); + return 0; +} + +static int __devexit aat1290a_led_remove(struct platform_device *pdev) +{ + led_pdata->freeGpio(); + misc_deregister(&aat1290a_miscdev); + + device_remove_file(aat1290a_dev, &dev_attr_flash_power); + device_destroy(flash_class, 0); + class_destroy(flash_class); + + return 0; +} + +static struct platform_driver aat1290a_led_driver = { + .probe = aat1290a_led_probe, + .remove = __devexit_p(aat1290a_led_remove), + .driver = { + .name = "aat1290a-led", + .owner = THIS_MODULE, + }, +}; + +static int __init aat1290a_led_init(void) +{ + return platform_driver_register(&aat1290a_led_driver); +} + +static void __exit aat1290a_led_exit(void) +{ + platform_driver_unregister(&aat1290a_led_driver); +} + +module_init(aat1290a_led_init); +module_exit(aat1290a_led_exit); + +MODULE_AUTHOR("DongHyun Chang "); +MODULE_DESCRIPTION("AAT1290A LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c new file mode 100644 index 0000000..1b7e0af --- /dev/null +++ b/drivers/leds/leds-an30259a.c @@ -0,0 +1,876 @@ +/* + * leds_an30259a.c - driver for panasonic AN30259A led control chip + * + * Copyright (C) 2012, Samsung Electronics Co. Ltd. All Rights Reserved. + * + * Contact: Haeil Hyun + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 7500 + +#define AN30259A_TIME_UNIT 500 + +#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 + +#define LED_R_CURRENT 0x28 +#define LED_G_CURRENT 0x28 +#define LED_B_CURRENT 0x28 +#define LED_OFF 0x00 + +#define MAX_NUM_LEDS 3 + +static struct an30259_led_conf led_conf[] = { + { + .name = "led_r", + .brightness = LED_OFF, + .max_brightness = 0x3C, + .flags = 0, + }, { + .name = "led_g", + .brightness = LED_OFF, + .max_brightness = 0x3C, + .flags = 0, + }, { + .name = "led_b", + .brightness = LED_OFF, + .max_brightness = 0x3C, + .flags = 0, + } +}; + +enum an30259a_led_enum { + LED_R, + LED_G, + LED_B, +}; + +enum an30259a_pattern { + PATTERN_OFF, + CHARGING, + CHARGING_ERR, + MISSED_NOTI, + LOW_BATTERY, + FULLY_CHARGED, + POWERING, +}; + +struct an30259a_led { + u8 channel; + u8 brightness; + struct led_classdev cdev; + struct work_struct brightness_work; + unsigned long delay_on_time_ms; + unsigned long delay_off_time_ms; +}; + +struct an30259a_data { + struct i2c_client *client; + struct mutex mutex; + struct an30259a_led leds[MAX_NUM_LEDS]; + u8 shadow_reg[AN30259A_REG_MAX]; +}; + +struct i2c_client *b_client; + +#define SEC_LED_SPECIFIC +#define LED_DEEP_DEBUG + +#ifdef SEC_LED_SPECIFIC +extern struct class *sec_class; +struct device *led_dev; +/*path : /sys/class/sec/led/led_pattern*/ +/*path : /sys/class/sec/led/led_blink*/ +/*path : /sys/class/leds/led_r/brightness*/ +/*path : /sys/class/leds/led_g/brightness*/ +/*path : /sys/class/leds/led_b/brightness*/ +#endif + +static void leds_on(enum an30259a_led_enum led, bool on, bool slopemode, + u8 ledcc); + +static inline struct an30259a_led *cdev_to_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct an30259a_led, cdev); +} + +#ifdef LED_DEEP_DEBUG +static void an30259a_debug(struct i2c_client *client) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + int ret; + u8 buff[21] = {0,}; + ret = i2c_smbus_read_i2c_block_data(client, + AN30259A_REG_SRESET|AN30259A_CTN_RW_FLG, + sizeof(buff), buff); + if (ret != sizeof(buff)) { + dev_err(&data->client->dev, + "%s: failure on i2c_smbus_read_i2c_block_data\n", + __func__); + } + print_hex_dump(KERN_ERR, "an30259a: ", + DUMP_PREFIX_OFFSET, 32, 1, buff, + sizeof(buff), false); +} +#endif + +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*/ + mutex_lock(&data->mutex); + 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__); + goto exit; + } + 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__); + goto exit; + } + mutex_unlock(&data->mutex); + return 0; + +exit: + mutex_unlock(&data->mutex); + return ret; +} + +void an30259a_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct an30259a_led *led = cdev_to_led(cdev); + led->brightness = (u8)brightness; + schedule_work(&led->brightness_work); +} + +static void an30259a_led_brightness_work(struct work_struct *work) +{ + struct i2c_client *client = b_client; + struct an30259a_led *led = container_of(work, + struct an30259a_led, brightness_work); + leds_on(led->channel, true, false, led->brightness); + leds_i2c_write_all(client); +} + +/* + * 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_enum 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(enum an30259a_led_enum led, bool on, bool slopemode, + u8 ledcc) +{ + struct an30259a_data *data = i2c_get_clientdata(b_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; +} + +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; +} + +#ifdef SEC_LED_SPECIFIC +static void an30259a_reset_register_work(struct work_struct *work) +{ + int retval; + struct i2c_client *client; + client = b_client; + + leds_on(LED_R, false, false, 0); + leds_on(LED_G, false, false, 0); + leds_on(LED_B, false, false, 0); + + retval = leds_i2c_write_all(client); + if (retval) + printk(KERN_WARNING "leds_i2c_write_all failed\n"); +} + +static void an30259a_start_led_pattern(int mode) +{ + int retval; + + struct i2c_client *client; + struct work_struct *reset = 0; + client = b_client; + + if (mode > POWERING) + return; + /* Set all LEDs Off */ + an30259a_reset_register_work(reset); + if (mode == LED_OFF) + return; + + switch (mode) { + /* leds_set_slope_mode(client, LED_SEL, DELAY, MAX, MID, MIN, + SLPTT1, SLPTT2, DT1, DT2, DT3, DT4) */ + case CHARGING: + leds_on(LED_R, true, false, LED_R_CURRENT); + leds_on(LED_G, false, false, LED_OFF); + leds_on(LED_B, false, false, LED_OFF); + break; + + case CHARGING_ERR: + leds_on(LED_R, true, true, LED_R_CURRENT); + leds_on(LED_G, false, false, LED_OFF); + leds_on(LED_B, false, false, LED_OFF); + leds_set_slope_mode(client, LED_R, + 1, 15, 15, 0, 1, 1, 0, 0, 0, 0); + break; + + case MISSED_NOTI: + leds_on(LED_R, false, false, LED_OFF); + leds_on(LED_G, false, false, LED_OFF); + leds_on(LED_B, true, true, LED_B_CURRENT); + leds_set_slope_mode(client, LED_B, + 10, 15, 15, 0, 1, 10, 0, 0, 0, 0); + break; + + case LOW_BATTERY: + leds_on(LED_R, true, true, LED_R_CURRENT); + leds_on(LED_G, false, false, LED_OFF); + leds_on(LED_B, false, false, LED_OFF); + leds_set_slope_mode(client, LED_R, + 10, 15, 15, 0, 1, 10, 0, 0, 0, 0); + break; + + case FULLY_CHARGED: + leds_on(LED_R, false, false, LED_OFF); + leds_on(LED_G, true, false, LED_G_CURRENT); + leds_on(LED_B, false, false, LED_OFF); + break; + + case POWERING: + leds_on(LED_R, false, false, 0); + leds_on(LED_G, true, true, LED_G_CURRENT); + leds_on(LED_B, true, true, LED_B_CURRENT); + leds_set_slope_mode(client, LED_G, + 0, 8, 4, 1, 2, 2, 3, 3, 3, 3); + leds_set_slope_mode(client, LED_B, + 0, 15, 14, 12, 2, 2, 7, 7, 7, 7); + break; + + default: + return; + break; + } + retval = leds_i2c_write_all(client); + if (retval) + printk(KERN_WARNING "leds_i2c_write_all failed\n"); +} + +static void an30259a_set_led_blink(enum an30259a_led_enum led, + unsigned int delay_on_time, + unsigned int delay_off_time, + u8 brightness) +{ + struct i2c_client *client; + client = b_client; + + if (delay_on_time > SLPTT_MAX_VALUE) + delay_on_time = SLPTT_MAX_VALUE; + if (delay_off_time > SLPTT_MAX_VALUE) + delay_off_time = SLPTT_MAX_VALUE; + + if (delay_off_time == LED_OFF) { + leds_on(led, true, false, brightness); + if (brightness == LED_OFF) + leds_on(led, false, false, brightness); + return; + } else if (brightness == LED_OFF) { + leds_on(led, false, false, brightness); + return; + } else + leds_on(led, true, true, brightness); + + leds_set_slope_mode(client, led, 0, 15, 15, 0, + (delay_on_time + AN30259A_TIME_UNIT - 1) / + AN30259A_TIME_UNIT, + (delay_off_time + AN30259A_TIME_UNIT - 1) / + AN30259A_TIME_UNIT, + 0, 0, 0, 0); +} + +static ssize_t store_an30259a_led_br_lev(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int retval; + unsigned long brightness_lev; + struct i2c_client *client; + struct an30259a_data *data = dev_get_drvdata(dev); + client = b_client; + + retval = strict_strtoul(buf, 16, &brightness_lev); + if (retval != 0) { + dev_err(&data->client->dev, "fail to get led_pattern mode.\n"); + return count; + } + + leds_set_imax(client, brightness_lev); + + return count; +} + +static ssize_t store_an30259a_led_pattern(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int retval; + unsigned long mode; + struct an30259a_data *data = dev_get_drvdata(dev); + + retval = strict_strtoul(buf, 16, &mode); + if (retval != 0) { + dev_err(&data->client->dev, "fail to get led_pattern mode.\n"); + return count; + } + + an30259a_start_led_pattern(mode); + printk(KERN_DEBUG "led pattern : %lu is activated\n", mode); + + return count; +} + +static ssize_t store_an30259a_led_blink(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + int retval; + unsigned int led_brightness = 0; + unsigned int delay_on_time = 0; + unsigned int delay_off_time = 0; + struct an30259a_data *data = dev_get_drvdata(dev); + u8 led_r_brightness = 0; + u8 led_g_brightness = 0; + u8 led_b_brightness = 0; + + retval = sscanf(buf, "0x%x %d %d", &led_brightness, + &delay_on_time, &delay_off_time); + + if (retval == 0) { + dev_err(&data->client->dev, "fail to get led_blink value.\n"); + return count; + } + /*Reset an30259a*/ + an30259a_start_led_pattern(LED_OFF); + + /*Set LED blink mode*/ + led_r_brightness = ((u32)led_brightness & LED_R_MASK) + >> LED_R_SHIFT; + led_g_brightness = ((u32)led_brightness & LED_G_MASK) + >> LED_G_SHIFT; + led_b_brightness = ((u32)led_brightness & LED_B_MASK); + + an30259a_set_led_blink(LED_R, delay_on_time, + delay_off_time, led_r_brightness); + an30259a_set_led_blink(LED_G, delay_on_time, + delay_off_time, led_g_brightness); + an30259a_set_led_blink(LED_B, delay_on_time, + delay_off_time, led_b_brightness); + + leds_i2c_write_all(data->client); + + printk(KERN_DEBUG "led_blink is called\n"); + + return count; +} + + +static ssize_t store_led_r(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct an30259a_data *data = dev_get_drvdata(dev); + char buff[10] = {0,}; + int cnt, ret; + u8 brightness; + + cnt = count; + cnt = (buf[cnt-1] == '\n') ? cnt-1 : cnt; + memcpy(buff, buf, cnt); + buff[cnt] = '\0'; + + ret = kstrtou8(buff, 0, &brightness); + if (ret != 0) { + dev_err(&data->client->dev, "fail to get brightness.\n"); + goto out; + } + + if (brightness == 0) + leds_on(LED_R, false, false, 0); + else + leds_on(LED_R, true, false, brightness); + + leds_i2c_write_all(data->client); + an30259a_debug(data->client); +out: + return count; +} + +static ssize_t store_led_g(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct an30259a_data *data = dev_get_drvdata(dev); + char buff[10] = {0,}; + int cnt, ret; + u8 brightness; + + cnt = count; + cnt = (buf[cnt-1] == '\n') ? cnt-1 : cnt; + memcpy(buff, buf, cnt); + buff[cnt] = '\0'; + + ret = kstrtou8(buff, 0, &brightness); + if (ret != 0) { + dev_err(&data->client->dev, "fail to get brightness.\n"); + goto out; + } + + if (brightness == 0) + leds_on(LED_G, false, false, 0); + else + leds_on(LED_G, true, false, brightness); + + leds_i2c_write_all(data->client); + an30259a_debug(data->client); +out: + return count; +} + +static ssize_t store_led_b(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count) +{ + struct an30259a_data *data = dev_get_drvdata(dev); + char buff[10] = {0,}; + int cnt, ret; + u8 brightness; + + cnt = count; + cnt = (buf[cnt-1] == '\n') ? cnt-1 : cnt; + memcpy(buff, buf, cnt); + buff[cnt] = '\0'; + + ret = kstrtou8(buff, 0, &brightness); + if (ret != 0) { + dev_err(&data->client->dev, "fail to get brightness.\n"); + goto out; + } + + if (brightness == 0) + leds_on(LED_B, false, false, 0); + else + leds_on(LED_B, true, false, brightness); + + leds_i2c_write_all(data->client); + an30259a_debug(data->client); +out: + return count; + +} +#endif + +/* Added for led common class */ +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct an30259a_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%lu\n", led->delay_on_time_ms); +} + +static ssize_t led_delay_on_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct an30259a_led *led = cdev_to_led(led_cdev); + unsigned long time; + + if (strict_strtoul(buf, 0, &time)) + return -EINVAL; + + led->delay_on_time_ms = (int)time; + return len; +} + +static ssize_t led_delay_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct an30259a_led *led = cdev_to_led(led_cdev); + + return sprintf(buf, "%lu\n", led->delay_off_time_ms); +} + +static ssize_t led_delay_off_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct an30259a_led *led = cdev_to_led(led_cdev); + unsigned long time; + + if (strict_strtoul(buf, 0, &time)) + return -EINVAL; + + led->delay_off_time_ms = (int)time; + + return len; +} + +static ssize_t led_blink_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct an30259a_led *led = cdev_to_led(led_cdev); + unsigned long blink_set; + + if (strict_strtoul(buf, 0, &blink_set)) + return -EINVAL; + + if (!blink_set) { + led->delay_on_time_ms = LED_OFF; + an30259a_set_brightness(led_cdev, LED_OFF); + } + + led_blink_set(led_cdev, + &led->delay_on_time_ms, &led->delay_off_time_ms); + + return len; +} + +/* permission for sysfs node */ +static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); +static DEVICE_ATTR(blink, 0644, NULL, led_blink_store); + +#ifdef SEC_LED_SPECIFIC +/* below nodes is SAMSUNG specific nodes */ +static DEVICE_ATTR(led_r, 0664, NULL, store_led_r); +static DEVICE_ATTR(led_g, 0664, NULL, store_led_g); +static DEVICE_ATTR(led_b, 0664, NULL, store_led_b); +/* led_pattern node permission is 664 */ +/* To access sysfs node from other groups */ +static DEVICE_ATTR(led_pattern, 0664, NULL, \ + store_an30259a_led_pattern); +static DEVICE_ATTR(led_blink, 0664, NULL, \ + store_an30259a_led_blink); +static DEVICE_ATTR(led_br_lev, 0664, NULL, \ + store_an30259a_led_br_lev); + +#endif +static struct attribute *led_class_attrs[] = { + &dev_attr_delay_on.attr, + &dev_attr_delay_off.attr, + &dev_attr_blink.attr, + NULL, +}; + +static struct attribute_group common_led_attr_group = { + .attrs = led_class_attrs, +}; + +#ifdef SEC_LED_SPECIFIC +static struct attribute *sec_led_attributes[] = { + &dev_attr_led_r.attr, + &dev_attr_led_g.attr, + &dev_attr_led_b.attr, + &dev_attr_led_pattern.attr, + &dev_attr_led_blink.attr, + &dev_attr_led_br_lev.attr, + NULL, +}; + +static struct attribute_group sec_led_attr_group = { + .attrs = sec_led_attributes, +}; +#endif + +static int __devinit an30259a_initialize(struct i2c_client *client, + struct an30259a_led *led, int channel) +{ + struct an30259a_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + 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; + } + led->channel = channel; + led->cdev.brightness_set = an30259a_set_brightness; + led->cdev.name = led_conf[channel].name; + led->cdev.brightness = led_conf[channel].brightness; + led->cdev.max_brightness = led_conf[channel].max_brightness; + led->cdev.flags = led_conf[channel].flags; + + ret = led_classdev_register(dev, &led->cdev); + + if (ret < 0) { + dev_err(dev, "can not register led channel : %d\n", channel); + return ret; + } + + ret = sysfs_create_group(&led->cdev.dev->kobj, + &common_led_attr_group); + + if (ret < 0) { + dev_err(dev, "can not register sysfs attribute\n"); + return ret; + } + + leds_set_imax(client, 0x00); + + return 0; +} + + +static int __devinit an30259a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct an30259a_data *data; + int ret, i; + + 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; + } + + i2c_set_clientdata(client, data); + data->client = client; + b_client = client; + + mutex_init(&data->mutex); + /* initialize LED */ + for (i = 0; i < MAX_NUM_LEDS; i++) { + + ret = an30259a_initialize(client, &data->leds[i], i); + + if (ret < 0) { + dev_err(&client->adapter->dev, "failure on initialization\n"); + goto exit; + } + INIT_WORK(&(data->leds[i].brightness_work), + an30259a_led_brightness_work); + } + +#ifdef SEC_LED_SPECIFIC + led_dev = device_create(sec_class, NULL, 0, data, "led"); + if (IS_ERR(led_dev)) { + dev_err(&client->dev, + "Failed to create device for samsung specific led\n"); + ret = -ENODEV; + } + ret = sysfs_create_group(&led_dev->kobj, &sec_led_attr_group); + if (ret) + dev_err(&client->dev, + "Failed to create sysfs group for samsung specific led\n"); +#endif + return ret; +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); + int i; + dev_dbg(&client->adapter->dev, "%s\n", __func__); +#ifdef SEC_LED_SPECIFIC + sysfs_remove_group(&led_dev->kobj, &sec_led_attr_group); +#endif + for (i = 0; i < MAX_NUM_LEDS; i++) { + sysfs_remove_group(&data->leds[i].cdev.dev->kobj, + &common_led_attr_group); + led_classdev_unregister(&data->leds[i].cdev); + cancel_work_sync(&data->leds[i].brightness_work); + } + 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("Haeil Hyun * + * Updated: Milo(Woogyom) Kim + * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. @@ -35,6 +37,11 @@ #include #include +#ifdef CONFIG_DEBUG_FS +#include +#include +#endif + #define LP5521_PROGRAM_LENGTH 32 /* in bytes */ #define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */ @@ -82,21 +89,13 @@ #define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ #define LP5521_EXEC_RUN 0x2A -/* Bits in CONFIG register */ -#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ -#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ -#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ -#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ -#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ -#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ -#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ -#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ -#define LP5521_CLK_INT 1 /* Internal clock */ -#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ - /* Status */ #define LP5521_EXT_CLK_USED 0x08 +#define LED_DEBUG 1 + +extern struct class *sec_class; + struct lp5521_engine { int id; u8 mode; @@ -114,6 +113,13 @@ struct lp5521_led { u8 brightness; }; +#ifdef CONFIG_DEBUG_FS +struct dbg_dentry { + struct dentry *dir; + struct dentry *reg; +}; +#endif + struct lp5521_chip { struct lp5521_platform_data *pdata; struct mutex lock; /* Serialize control */ @@ -122,8 +128,18 @@ struct lp5521_chip { struct lp5521_led leds[LP5521_MAX_LEDS]; u8 num_channels; u8 num_leds; +#ifdef CONFIG_DEBUG_FS + struct dbg_dentry dd; +#endif + struct device *led_dev; }; +static struct lp5521_chip *g_chip; + +#ifdef LED_DEBUG +static struct i2c_client *g_client; +#endif + static inline struct lp5521_led *cdev_to_led(struct led_classdev *cdev) { return container_of(cdev, struct lp5521_led, cdev); @@ -141,6 +157,8 @@ static inline struct lp5521_chip *led_to_lp5521(struct lp5521_led *led) leds[led->id]); } +void lp5521_led_brightness(u8 channel, u8 brightness); + static void lp5521_led_brightness_work(struct work_struct *work); static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) @@ -237,6 +255,7 @@ static void lp5521_init_engine(struct lp5521_chip *chip) static int lp5521_configure(struct i2c_client *client) { struct lp5521_chip *chip = i2c_get_clientdata(client); + u8 cfg = chip->pdata->update_config; int ret; lp5521_init_engine(chip); @@ -244,9 +263,8 @@ static int lp5521_configure(struct i2c_client *client) /* Set all PWMs to direct control mode */ ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); - /* Enable auto-powersave, set charge pump to auto, red to battery */ - ret |= lp5521_write(client, LP5521_REG_CONFIG, - LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + if (cfg) + ret |= lp5521_write(client, LP5521_REG_CONFIG, cfg); /* Initialize all channels PWM to zero -> leds off */ ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); @@ -574,6 +592,218 @@ static const struct attribute_group lp5521_group = { .attrs = lp5521_attributes, }; +#ifdef CONFIG_DEBUG_FS +static int lp5521_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t lp5521_help_register(struct file *file, char __user *userbuf, + size_t count, loff_t *ppos) +{ + char buf[320]; + unsigned int len; + const char *help = "\n How to read/write LP5521 registers\n\n"; +/* (example) To read 0x00 register,\n \ + echo 0x00 r > /sys/kernel/debug/lp5521/registers\n \ + To write 0xff into 0x1 address,\n \ + echo 0x00 0xff w > /sys/kernel/debug/lp5521/registers \n \ + To dump values from 0x00 to 0x0a address,\n \ + echo 0x00 0x0a d > /sys/kernel/debug/lp5521/registers\n"; +*/ + len = snprintf(buf, sizeof(buf), "%s\n", help); + if (len > sizeof(buf)) + len = sizeof(buf); + + return simple_read_from_buffer(userbuf, count, ppos, buf, len); +} + +static char *lp5521_parse_register_cmd(const char *cmd, u8 *byte) +{ + char tmp[10]; + char *blank; + unsigned long arg; + + blank = strchr(cmd, ' '); + memset(tmp, 0x0, sizeof(tmp)); + memcpy(tmp, cmd, blank - cmd); + + if (strict_strtol(tmp, 16, &arg) < 0) + return NULL; + + *byte = arg; + return blank; +} + +static ssize_t lp5521_ctrl_register(struct file *file, + const char __user *userbuf, size_t count, + loff_t *ppos) +{ + char mode, buf[20]; + char *pos, *pos2; + u8 i, arg1, arg2, val = 0; + struct lp5521_chip *chip = file->private_data; + struct i2c_client *cl = chip->client; + + if (copy_from_user(buf, userbuf, min(count, sizeof(buf)))) + return -EFAULT; + + mode = buf[count - 2]; + switch (mode) { + case 'r': + if (!lp5521_parse_register_cmd(buf, &arg1)) + return -EINVAL; + + lp5521_read(cl, arg1, &val); + dev_info(&cl->dev, "Read [0x%.2x] = 0x%.2x\n", arg1, val); + break; + case 'w': + pos = lp5521_parse_register_cmd(buf, &arg1); + if (!pos) + return -EINVAL; + pos2 = lp5521_parse_register_cmd(pos + 1, &arg2); + if (!pos2) + return -EINVAL; + + lp5521_write(cl, arg1, arg2); + dev_info(&cl->dev, "Written [0x%.2x] = 0x%.2x\n", arg1, arg2); + break; + case 'd': + pos = lp5521_parse_register_cmd(buf, &arg1); + if (!pos) + return -EINVAL; + pos2 = lp5521_parse_register_cmd(pos + 1, &arg2); + if (!pos2) + return -EINVAL; + + for (i = arg1; i <= arg2; i++) { + lp5521_read(cl, i, &val); + dev_info(&cl->dev, "Read [0x%.2x] = 0x%.2x\n", i, val); + } + break; + default: + break; + } + + return count; +} + +static const struct file_operations fops_registers = { + .open = lp5521_dbg_open, + .read = lp5521_help_register, + .write = lp5521_ctrl_register, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void lp5521_create_debugfs(struct lp5521_chip *chip) +{ + struct dbg_dentry *dd = &chip->dd; + + dd->dir = debugfs_create_dir("lp5521", NULL); + dd->reg = debugfs_create_file("registers", S_IWUSR | S_IRUGO, + dd->dir, chip, &fops_registers); +} + +static void lp5521_remove_debugfs(struct lp5521_chip *chip) +{ + struct dbg_dentry *dd = &chip->dd; + + debugfs_remove(dd->reg); + debugfs_remove(dd->dir); +} +#else +static inline void lp5521_create_debugfs(struct lp5521_chip *chip) +{ + return; +} + +static inline void lp5521_remove_debugfs(struct lp5521_chip *chip) +{ + return; +} +#endif + +#define LP5521_ENABLE_DEFAULT \ + (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM) +#define LP5521_ENABLE_RUN_PROGRAM \ + (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) + +static void lp5521_program_pattern(int mode, struct lp5521_chip *chip) +{ + struct i2c_client *cl = chip->client; + int i; + u8 mode1_red[] = { + 0x40, 0xED, 0x41, 0x00, /* 15.6ms on with red 0xff */ + 0x40, 0x00, 0x46, 0x00, /* 100ms off */ + 0x40, 0xED, 0x41, 0x00, /* 15.6ms on with red 0xff */ + 0x40, 0x00, 0x60, 0x00, /* 500ms off */ + 0xa4, 0x87, /* 4500ms off = 500ms off x 9 times */ + }; + + u8 mode1_green[] = { + 0x40, 0x59, 0x41, 0x00, /* 15.6ms on with green 0x25 */ + 0x40, 0x00, 0x46, 0x00, /* 100ms off */ + 0x40, 0x59, 0x41, 0x00, /* 15.6ms on with green 0x25 */ + 0x40, 0x00, 0x60, 0x00, /* 500ms off */ + 0xa4, 0x87, /* 4500ms off = 500ms off x 9 times */ + }; + + if (mode == 0) { + lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT); + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + } else if (mode == 1) { + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); + + for (i = 0 ; i < ARRAY_SIZE(mode1_red) ; i++) + lp5521_write(cl, LP5521_REG_R_PROG_MEM + i, + mode1_red[i]); + + for (i = 0 ; i < ARRAY_SIZE(mode1_green) ; i++) + lp5521_write(cl, LP5521_REG_G_PROG_MEM + i, + mode1_green[i]); + + lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_RUN); + lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); + } +} + +static ssize_t lp5521_store_pattern(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + unsigned long val; + int ret; + + ret = strict_strtoul(buf, 16, &val); + if (ret) + return ret; + + lp5521_program_pattern(val, g_chip); + + return count; +} + +static struct device_attribute lp5521_pattern_attr = { + .attr = { + .name = "led-pattern", + .mode = 0644, + }, + .store = lp5521_store_pattern, +}; + +static int lp5521_create_pattern_nodes(struct lp5521_chip *chip) +{ + return device_create_file(chip->led_dev, &lp5521_pattern_attr); +} + +static void lp5521_remove_pattern_nodes(struct lp5521_chip *chip) +{ + device_remove_file(chip->led_dev, &lp5521_pattern_attr); +} + static int lp5521_register_sysfs(struct i2c_client *client) { struct device *dev = &client->dev; @@ -617,10 +847,16 @@ static int __devinit lp5521_init_led(struct lp5521_led *led, return -EINVAL; } - snprintf(name, sizeof(name), "%s:channel%d", - pdata->label ?: client->name, chan); led->cdev.brightness_set = lp5521_set_brightness; - led->cdev.name = name; + + if (pdata->led_config[chan].name) { + led->cdev.name = pdata->led_config[chan].name; + } else { + snprintf(name, sizeof(name), "%s:channel%d", + pdata->label ?: client->name, chan); + led->cdev.name = name; + } + res = led_classdev_register(dev, &led->cdev); if (res < 0) { dev_err(dev, "couldn't register led on channel %d\n", chan); @@ -650,7 +886,11 @@ static int __devinit lp5521_probe(struct i2c_client *client, i2c_set_clientdata(client, chip); chip->client = client; + g_chip = chip; +#ifdef LED_DEBUG + g_client = client; +#endif pdata = client->dev.platform_data; if (!pdata) { @@ -700,6 +940,7 @@ static int __devinit lp5521_probe(struct i2c_client *client, chip->num_channels = pdata->num_channels; chip->num_leds = 0; led = 0; + chip->led_dev = device_create(sec_class, NULL, 0, NULL, "leds"); for (i = 0; i < pdata->num_channels; i++) { /* Do not initialize channels that are not connected */ if (pdata->led_config[i].led_current == 0) @@ -728,6 +969,10 @@ static int __devinit lp5521_probe(struct i2c_client *client, dev_err(&client->dev, "registering sysfs failed\n"); goto fail3; } + + lp5521_create_pattern_nodes(chip); + lp5521_create_debugfs(chip); + return ret; fail3: for (i = 0; i < chip->num_leds; i++) { @@ -744,11 +989,24 @@ fail1: return ret; } +#ifdef LED_DEBUG +void lp5521_led_brightness(u8 channel, u8 brightness) +{ + + lp5521_write(g_client, LP5521_REG_LED_PWM_BASE + channel, + brightness); +} +EXPORT_SYMBOL(lp5521_led_brightness); +#endif + static int lp5521_remove(struct i2c_client *client) { struct lp5521_chip *chip = i2c_get_clientdata(client); int i; + lp5521_program_pattern(0, chip); + lp5521_remove_pattern_nodes(chip); + lp5521_remove_debugfs(chip); lp5521_unregister_sysfs(client); for (i = 0; i < chip->num_leds; i++) { diff --git a/drivers/leds/leds-max77693.c b/drivers/leds/leds-max77693.c new file mode 100644 index 0000000..bdbb953 --- /dev/null +++ b/drivers/leds/leds-max77693.c @@ -0,0 +1,359 @@ +/* + * LED driver for Maxim MAX77693 - leds-max77673.c + * + * Copyright (C) 2011 ByungChang Cha + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct max77693_led_data { + struct led_classdev led; + struct max77693_dev *max77693; + struct max77693_led *data; + struct i2c_client *i2c; + struct work_struct work; + struct mutex lock; + spinlock_t value_lock; + int brightness; + int test_brightness; +}; + +static u8 led_en_mask[MAX77693_LED_MAX] = { + MAX77693_FLASH_FLED1_EN, + MAX77693_FLASH_FLED2_EN, + MAX77693_TORCH_FLED1_EN, + MAX77693_TORCH_FLED2_EN +}; + +static u8 led_en_shift[MAX77693_LED_MAX] = { + 6, + 4, + 2, + 0, +}; + +static u8 reg_led_timer[MAX77693_LED_MAX] = { + MAX77693_LED_REG_FLASH_TIMER, + MAX77693_LED_REG_FLASH_TIMER, + MAX77693_LED_REG_ITORCHTORCHTIMER, + MAX77693_LED_REG_ITORCHTORCHTIMER, +}; + +static u8 reg_led_current[MAX77693_LED_MAX] = { + MAX77693_LED_REG_IFLASH1, + MAX77693_LED_REG_IFLASH2, + MAX77693_LED_REG_ITORCH, + MAX77693_LED_REG_ITORCH, +}; + +static u8 led_current_mask[MAX77693_LED_MAX] = { + MAX77693_FLASH_IOUT1, + MAX77693_FLASH_IOUT2, + MAX77693_TORCH_IOUT1, + MAX77693_TORCH_IOUT2 +}; + +static u8 led_current_shift[MAX77693_LED_MAX] = { + 0, + 0, + 0, + 4, +}; + +static int max77693_set_bits(struct i2c_client *client, const u8 reg, + const u8 mask, const u8 inval) +{ + int ret; + u8 value; + + ret = max77693_read_reg(client, reg, &value); + if (unlikely(ret < 0)) + return ret; + + value = (value & ~mask) | (inval & mask); + + ret = max77693_write_reg(client, reg, value); + + return ret; +} + +static void print_all_reg_value(struct i2c_client *client) +{ + u8 value; + u8 i; + + for (i = 0; i != 0x11; ++i) { + max77693_read_reg(client, i, &value); + printk(KERN_ERR "LEDS_MAX77693 REG(%d) = %x\n", i, value); + } +} + +static int max77693_led_get_en_value(struct max77693_led_data *led_data, int on) +{ + if (on) + return 0x03; + + if (led_data->data->cntrl_mode == MAX77693_LED_CTRL_BY_I2C) + return 0x00; + else if (led_data->data->id < 2) + return 0x01; + else + return 0x02; +} + +static void max77693_led_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + unsigned long flags; + struct max77693_led_data *led_data + = container_of(led_cdev, struct max77693_led_data, led); + + pr_debug("[LED] %s\n", __func__); + + spin_lock_irqsave(&led_data->value_lock, flags); + led_data->test_brightness = min((int)value, MAX77693_FLASH_IOUT1); + spin_unlock_irqrestore(&led_data->value_lock, flags); + + schedule_work(&led_data->work); +} + +static void led_set(struct max77693_led_data *led_data) +{ + int ret; + struct max77693_led *data = led_data->data; + int id = data->id; + u8 shift = led_current_shift[id]; + int value; + + if (led_data->test_brightness == LED_OFF) + { + value = max77693_led_get_en_value(led_data, 0); + ret = max77693_set_bits(led_data->i2c, + MAX77693_LED_REG_FLASH_EN, + led_en_mask[id], + value << led_en_shift[id]); + if (unlikely(ret)) + goto error_set_bits; + + ret = max77693_set_bits(led_data->i2c, reg_led_current[id], + led_current_mask[id], + led_data->brightness << shift); + if (unlikely(ret)) + goto error_set_bits; + + return; + } + + /* Set current */ + ret = max77693_set_bits(led_data->i2c, reg_led_current[id], + led_current_mask[id], + led_data->test_brightness << shift); + if (unlikely(ret)) + goto error_set_bits; + + /* Turn off LED */ + value = max77693_led_get_en_value(led_data, 0); + ret = max77693_set_bits(led_data->i2c, MAX77693_LED_REG_FLASH_EN, + led_en_mask[id], + value << led_en_shift[id]); + + if (unlikely(ret)) + goto error_set_bits; + + /* Turn on LED */ + ret = max77693_set_bits(led_data->i2c, MAX77693_LED_REG_FLASH_EN, + led_en_mask[id], led_en_mask[id]); + + if (unlikely(ret)) + goto error_set_bits; + + return; + +error_set_bits: + pr_err("%s: can't set led level %d\n", __func__, ret); + return; +} + +static void max77693_led_work(struct work_struct *work) +{ + struct max77693_led_data *led_data + = container_of(work, struct max77693_led_data, work); + + pr_debug("[LED] %s\n", __func__); + + mutex_lock(&led_data->lock); + led_set(led_data); + mutex_unlock(&led_data->lock); +} + +static int max77693_led_setup(struct max77693_led_data *led_data) +{ + int ret = 0; + struct max77693_led *data = led_data->data; + int id = data->id; + int value; + + ret |= max77693_write_reg(led_data->i2c, MAX77693_LED_REG_VOUT_CNTL, + MAX77693_BOOST_FLASH_FLEDNUM_2 + | MAX77693_BOOST_FLASH_MODE_BOTH); + ret |= max77693_write_reg(led_data->i2c, MAX77693_LED_REG_VOUT_FLASH1, + MAX77693_BOOST_VOUT_FLASH_FROM_VOLT(5000)); + ret |= max77693_write_reg(led_data->i2c, + MAX77693_LED_REG_MAX_FLASH1, 0xEC); + ret |= max77693_write_reg(led_data->i2c, + MAX77693_LED_REG_MAX_FLASH2, 0x00); + + value = max77693_led_get_en_value(led_data, 0); + + ret |= max77693_set_bits(led_data->i2c, MAX77693_LED_REG_FLASH_EN, + led_en_mask[id], + value << led_en_shift[id]); + + /* Set TORCH_TMR_DUR or FLASH_TMR_DUR */ + if (id < 2) { + ret |= max77693_write_reg(led_data->i2c, reg_led_timer[id], + (data->timer | data->timer_mode << 7)); + } else { + ret |= max77693_write_reg(led_data->i2c, reg_led_timer[id], + 0xC0); + } + + /* Set current */ + ret |= max77693_set_bits(led_data->i2c, reg_led_current[id], + led_current_mask[id], + led_data->brightness << led_current_shift[id]); + + return ret; +} + +static int max77693_led_probe(struct platform_device *pdev) +{ + int ret = 0; + int i; + struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent); + struct max77693_platform_data *max77693_pdata + = dev_get_platdata(max77693->dev); + struct max77693_led_platform_data *pdata = max77693_pdata->led_data; + struct max77693_led_data *led_data; + struct max77693_led *data; + struct max77693_led_data **led_datas; + + if (pdata == NULL) { + pr_err("[LED] no platform data for this led is found\n"); + return -EFAULT; + } + + led_datas = kzalloc(sizeof(struct max77693_led_data *) + * MAX77693_LED_MAX, GFP_KERNEL); + if (unlikely(!led_datas)) { + pr_err("[LED] memory allocation error %s", __func__); + return -ENOMEM; + } + platform_set_drvdata(pdev, led_datas); + + pr_debug("[LED] %s\n", __func__); + + for (i = 0; i != pdata->num_leds; ++i) { + data = &(pdata->leds[i]); + + led_data = kzalloc(sizeof(struct max77693_led_data), + GFP_KERNEL); + led_datas[i] = led_data; + if (unlikely(!led_data)) { + pr_err("[LED] memory allocation error %s\n", __func__); + ret = -ENOMEM; + continue; + } + + led_data->max77693 = max77693; + led_data->i2c = max77693->i2c; + led_data->data = data; + led_data->led.name = data->name; + led_data->led.brightness_set = max77693_led_set; + led_data->led.brightness = LED_OFF; + led_data->brightness = data->brightness; + led_data->led.flags = LED_CORE_SUSPENDRESUME; + led_data->led.max_brightness = data->id < 2 + ? MAX_FLASH_DRV_LEVEL : MAX_TORCH_DRV_LEVEL; + + mutex_init(&led_data->lock); + spin_lock_init(&led_data->value_lock); + INIT_WORK(&led_data->work, max77693_led_work); + + ret = led_classdev_register(&pdev->dev, &led_data->led); + if (unlikely(ret)) { + pr_err("unable to register LED\n"); + kfree(led_data); + ret = -EFAULT; + continue; + } + + ret = max77693_led_setup(led_data); + if (unlikely(ret)) { + pr_err("unable to register LED\n"); + led_classdev_unregister(&led_data->led); + kfree(led_data); + ret = -EFAULT; + } + } + /* print_all_reg_value(max77693->i2c); */ + return ret; +} + +static int __devexit max77693_led_remove(struct platform_device *pdev) +{ + struct max77693_led_data **led_datas = platform_get_drvdata(pdev); + int i; + + for (i = 0; i != MAX77693_LED_MAX; ++i) { + if (led_datas[i] == NULL) + continue; + + cancel_work_sync(&led_datas[i]->work); + mutex_destroy(&led_datas[i]->lock); + led_classdev_unregister(&led_datas[i]->led); + kfree(led_datas[i]); + } + kfree(led_datas); + + return 0; +} + +static struct platform_driver max77693_led_driver = +{ + .probe = max77693_led_probe, + .remove = __devexit_p(max77693_led_remove), + .driver = + { + .name = "max77693-led", + .owner = THIS_MODULE, + }, +}; + +static int __init max77693_led_init(void) +{ + return platform_driver_register(&max77693_led_driver); +} +module_init(max77693_led_init); + +static void __exit max77693_led_exit(void) +{ + platform_driver_unregister(&max77693_led_driver); +} +module_exit(max77693_led_exit); + +MODULE_AUTHOR("ByungChang Cha "); +MODULE_DESCRIPTION("MAX77693 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-max8997.c b/drivers/leds/leds-max8997.c new file mode 100644 index 0000000..54c4a04 --- /dev/null +++ b/drivers/leds/leds-max8997.c @@ -0,0 +1,313 @@ +/* + * leds-max8997.c - LED class driver for MAX8997 LEDs. + * + * Copyright (C) 2011 Samsung Electronics + * + * Inspired by leds-regulator driver. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define to_regulator_led(led_cdev) \ + container_of(led_cdev, struct regulator_led, cdev) + +struct regulator_led { + struct led_classdev cdev; + enum led_brightness value; + int enabled; + struct mutex mutex; + struct work_struct work; + + struct regulator *vcc; +}; + +static inline int led_regulator_get_max_brightness(struct regulator *supply) +{ + int ret; + int voltage = regulator_list_voltage(supply, 0); + + /*TODO*/ +#if defined(CONFIG_MACH_Q1_BD) + return LED_BRIGHTNESS_MAX_LEVEL; +#else + return 1; +#endif + + if (voltage <= 0) + return 1; + + /* even if regulator can't change voltages, + * we still assume it can change status + * and the LED can be turned on and off. + */ + ret = regulator_set_voltage(supply, voltage, voltage); + if (ret < 0) + return 1; + + return regulator_count_voltages(supply); +} + +static int led_regulator_get_voltage(struct regulator *supply, + enum led_brightness brightness) +{ + if (brightness == 0) + return -EINVAL; + + return regulator_list_voltage(supply, brightness - 1); +} + + +static void regulator_led_enable(struct regulator_led *led) +{ + int ret; + + if (led->enabled) + return; + + ret = regulator_enable(led->vcc); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret); + return; + } + + led->enabled = 1; +} + +static void regulator_led_disable(struct regulator_led *led) +{ + int ret; + + if (!led->enabled) + return; + + ret = regulator_disable(led->vcc); + if (ret != 0) { + dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret); + return; + } + + led->enabled = 0; +} + +static void regulator_led_set_value(struct regulator_led *led) +{ + int voltage; + int ret; + struct regulator *movie; + + mutex_lock(&led->mutex); + + if (led->cdev.flags & LED_SUSPENDED) + goto out; + + if (led->value == LED_OFF) { + regulator_led_disable(led); + goto out; + } + + movie = regulator_get(NULL, "led_movie"); + if (IS_ERR(movie)) { + pr_err("%s: led_movie is failed.\n", __func__); + goto out; + } + +/* Q1 has torch light widget, and it changes */ +/* its brightness level without disabling rerulator */ +/* So, remove the below code for Q1 */ +#if !defined(CONFIG_MACH_Q1_BD) + if (regulator_is_enabled(movie) > 0) { + pr_info("%s: led_movie is enabled.\n", __func__); + goto end; + } +#endif + +#if 0 /*TODO*/ + if (led->cdev.max_brightness > 1) { + voltage = led_regulator_get_voltage(led->vcc, led->value); + dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n", + led->value, voltage); + + ret = regulator_set_voltage(led->vcc, voltage, voltage); + if (ret != 0) + dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n", + voltage, ret); + } +#endif + +#if defined(CONFIG_MACH_Q1_BD) + switch (led->value) { + case LED_BRIGHTNESS_LEVEL1: + regulator_set_current_limit(led->vcc, 10000, 20000); + break; + case LED_BRIGHTNESS_LEVEL2: + regulator_set_current_limit(led->vcc, 90000, 110000); + break; + case LED_BRIGHTNESS_LEVEL3: + regulator_set_current_limit(led->vcc, 180000, 200000); + break; + default: + pr_err("%s: fail to set current, out of range\n", __func__); + goto end; + break; + } +#else + regulator_set_current_limit(led->vcc, 90000, 110000); +#endif + + regulator_led_enable(led); + +end: + regulator_put(movie); + +out: + mutex_unlock(&led->mutex); +} + +static void led_work(struct work_struct *work) +{ + struct regulator_led *led; + + led = container_of(work, struct regulator_led, work); + regulator_led_set_value(led); +} + +static void regulator_led_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct regulator_led *led = to_regulator_led(led_cdev); + + led->value = value; + schedule_work(&led->work); +} + +static int __devinit regulator_led_probe(struct platform_device *pdev) +{ + struct led_max8997_platform_data *pdata = pdev->dev.platform_data; + struct regulator_led *led; + struct regulator *vcc; + int ret = 0; + + pr_info("%s +\n", __func__); + + if (pdata == NULL) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENODEV; + } + + vcc = regulator_get_exclusive(&pdev->dev, "led_torch"); + if (IS_ERR(vcc)) { + dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name); + return PTR_ERR(vcc); + } + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (led == NULL) { + ret = -ENOMEM; + goto err_vcc; + } + + led->cdev.max_brightness = led_regulator_get_max_brightness(vcc); + if (pdata->brightness > led->cdev.max_brightness) { + dev_err(&pdev->dev, "Invalid default brightness %d\n", + pdata->brightness); + ret = -EINVAL; + goto err_led; + } + + /* Set default brightness */ + led->value = pdata->brightness; + + led->cdev.brightness_set = regulator_led_brightness_set; + /* Set leds name for class driver */ + led->cdev.name = pdata->name; + led->cdev.flags |= LED_CORE_SUSPENDRESUME; + led->vcc = vcc; + + mutex_init(&led->mutex); + INIT_WORK(&led->work, led_work); + + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(&pdev->dev, &led->cdev); + if (ret < 0) { + cancel_work_sync(&led->work); + goto err_led; + } + + /* to expose the default value to userspace */ + led->cdev.brightness = led->value; + + /* Set the default led status */ + regulator_led_set_value(led); + + pr_info("%s -\n", __func__); + + return 0; + +err_led: + kfree(led); +err_vcc: + regulator_put(vcc); + return ret; +} + +static int __devexit regulator_led_remove(struct platform_device *pdev) +{ + struct regulator_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + cancel_work_sync(&led->work); + regulator_led_disable(led); + regulator_put(led->vcc); + kfree(led); + return 0; +} + +static void regulator_led_shutdown(struct platform_device *pdev) +{ + struct regulator_led *led = platform_get_drvdata(pdev); + + if (regulator_is_enabled(led->vcc)) + regulator_led_disable(led); + + return; +} + +static struct platform_driver regulator_led_driver = { + .driver = { + .name = "leds-max8997", + .owner = THIS_MODULE, + }, + .probe = regulator_led_probe, + .remove = __devexit_p(regulator_led_remove), + .shutdown = regulator_led_shutdown, +}; + +static int __init regulator_led_init(void) +{ + return platform_driver_register(®ulator_led_driver); +} +module_init(regulator_led_init); + +static void __exit regulator_led_exit(void) +{ + platform_driver_unregister(®ulator_led_driver); +} +module_exit(regulator_led_exit); + +MODULE_AUTHOR("Byun.C.W"); +MODULE_DESCRIPTION("MAX8997 LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:leds-max8997"); diff --git a/drivers/leds/leds-spfcw043.c b/drivers/leds/leds-spfcw043.c new file mode 100644 index 0000000..555c49c --- /dev/null +++ b/drivers/leds/leds-spfcw043.c @@ -0,0 +1,202 @@ +/* + * LED driver for SPFCW043 - leds-spfcw043.c + * + * Copyright (C) 2011 DongHyun Chang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +int *spfcw043_ctrl; +struct spfcw043_led_platform_data *led_pdata; + +extern struct class *camera_class; +extern struct device *camera_rear; + +static int spfcw043_setPower(int onoff, int level) +{ + int i; + if (led_pdata->status == STATUS_AVAILABLE) { + led_pdata->torch_en(0); + led_pdata->torch_set(0); + /* onoff = 0 - select spfcw043, onoff = 1 - select ISP*/ + udelay(10); + if (onoff) { + led_pdata->torch_set(1); + for (i = 1; i < level; i++) { + udelay(1); + led_pdata->torch_set(0); + udelay(1); + led_pdata->torch_set(1); + } + } + } else { + LED_ERROR("GPIO is not available!"); + return -ENODEV; + } + + return 0; +} + +static int spfcw043_freeGpio(void) +{ + if (led_pdata->status == STATUS_AVAILABLE) { + if (led_pdata->freeGpio()) { + LED_ERROR("Can't free GPIO for led\n"); + return -ENODEV; + } + led_pdata->status = STATUS_UNAVAILABLE; + } else { + LED_ERROR("GPIO already free!"); + } + + return 0; +} + +static int spfcw043_setGpio(void) +{ + if (led_pdata->status != STATUS_AVAILABLE) { + if (led_pdata->setGpio()) { + LED_ERROR("Can't set GPIO for led\n"); + return -ENODEV; + } + led_pdata->status = STATUS_AVAILABLE; + } else { + LED_ERROR("GPIO already set!"); + } + + return 0; +} + +static int spfcw043_open(struct inode *inode, struct file *file) +{ + int ret; + led_pdata->brightness = TORCH_BRIGHTNESS_50; /*normal setting*/ + led_pdata->status = STATUS_INVALID; + + ret = spfcw043_setGpio(); + + file->private_data = (int *) spfcw043_ctrl; + + return ret; +} + +static int spfcw043_release(struct inode *inode, struct file *file) +{ + if (spfcw043_freeGpio()) { + LED_ERROR("spfcw043_freeGpio failed!\n"); + return -ENODEV; + } + + return 0; +} + +static ssize_t spfcw043_power(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + int brightness = 0; + + if (buf[0] < 0x30 || buf[0] > 0x39) { + LED_ERROR("data is wrong!"); + return count; + } else if (buf[1] < 0x30 || buf[1] > 0x39) + brightness = (int) (buf[0] - 0x30); + else { + brightness = brightness + (int) (buf[0] - 0x30) * 10; + brightness = brightness + (int) (buf[1] - 0x30); + } + + if (brightness < 0 || brightness >= TORCH_BRIGHTNESS_INVALID) { + LED_ERROR("brightness data is wrong! %d", brightness); + return count; + } + + if (brightness == 0) { + spfcw043_setPower(0, 0); + } else { + if (spfcw043_setGpio()) { + LED_ERROR("spfcw043_setGpio failed!\n"); + return count; + } + spfcw043_setPower(1, brightness); + } + + return count; +} + +static DEVICE_ATTR(rear_flash, S_IWUSR|S_IWGRP|S_IROTH, NULL, spfcw043_power); + +static const struct file_operations spfcw043_fops = { + .owner = THIS_MODULE, + .open = spfcw043_open, + .release = spfcw043_release, +}; + +static struct miscdevice spfcw043_miscdev = { + .minor = 255, + .name = "spfcw043_led", + .fops = &spfcw043_fops, +}; + +static int spfcw043_led_probe(struct platform_device *pdev) +{ + LED_ERROR("Probe\n"); + led_pdata = + (struct spfcw043_led_platform_data *) pdev->dev.platform_data; + + if (misc_register(&spfcw043_miscdev)) { + LED_ERROR("Failed to register misc driver\n"); + return -ENODEV; + } + + if (IS_ERR(camera_rear)) + LED_ERROR("Failed to create device(flash)!\n"); + + if (device_create_file(camera_rear, &dev_attr_rear_flash) < 0) { + LED_ERROR("failed to create device file, %s\n", + dev_attr_rear_flash.attr.name); + } + spfcw043_setGpio(); + return 0; +} + +static int __devexit spfcw043_led_remove(struct platform_device *pdev) +{ + led_pdata->freeGpio(); + misc_deregister(&spfcw043_miscdev); + + device_remove_file(camera_rear, &dev_attr_rear_flash); + device_destroy(camera_class, 0); + class_destroy(camera_class); + + return 0; +} + +static struct platform_driver spfcw043_led_driver = { + .probe = spfcw043_led_probe, + .remove = __devexit_p(spfcw043_led_remove), + .driver = { + .name = "spfcw043-led", + .owner = THIS_MODULE, + }, +}; + +static int __init spfcw043_led_init(void) +{ + return platform_driver_register(&spfcw043_led_driver); +} + +static void __exit spfcw043_led_exit(void) +{ + platform_driver_unregister(&spfcw043_led_driver); +} + +module_init(spfcw043_led_init); +module_exit(spfcw043_led_exit); + +MODULE_AUTHOR("DongHyun Chang "); +MODULE_DESCRIPTION("SPFCW043 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-sleep.c b/drivers/leds/ledtrig-sleep.c new file mode 100644 index 0000000..f164042 --- /dev/null +++ b/drivers/leds/ledtrig-sleep.c @@ -0,0 +1,80 @@ +/* drivers/leds/ledtrig-sleep.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include +#include +#include + +static int ledtrig_sleep_pm_callback(struct notifier_block *nfb, + unsigned long action, + void *ignored); + +DEFINE_LED_TRIGGER(ledtrig_sleep) +static struct notifier_block ledtrig_sleep_pm_notifier = { + .notifier_call = ledtrig_sleep_pm_callback, + .priority = 0, +}; + +static void ledtrig_sleep_early_suspend(struct early_suspend *h) +{ + led_trigger_event(ledtrig_sleep, LED_FULL); +} + +static void ledtrig_sleep_early_resume(struct early_suspend *h) +{ + led_trigger_event(ledtrig_sleep, LED_OFF); +} + +static struct early_suspend ledtrig_sleep_early_suspend_handler = { + .suspend = ledtrig_sleep_early_suspend, + .resume = ledtrig_sleep_early_resume, +}; + +static int ledtrig_sleep_pm_callback(struct notifier_block *nfb, + unsigned long action, + void *ignored) +{ + switch (action) { + case PM_HIBERNATION_PREPARE: + case PM_SUSPEND_PREPARE: + led_trigger_event(ledtrig_sleep, LED_OFF); + return NOTIFY_OK; + case PM_POST_HIBERNATION: + case PM_POST_SUSPEND: + led_trigger_event(ledtrig_sleep, LED_FULL); + return NOTIFY_OK; + } + + return NOTIFY_DONE; +} + +static int __init ledtrig_sleep_init(void) +{ + led_trigger_register_simple("sleep", &ledtrig_sleep); + register_pm_notifier(&ledtrig_sleep_pm_notifier); + register_early_suspend(&ledtrig_sleep_early_suspend_handler); + return 0; +} + +static void __exit ledtrig_sleep_exit(void) +{ + unregister_early_suspend(&ledtrig_sleep_early_suspend_handler); + unregister_pm_notifier(&ledtrig_sleep_pm_notifier); + led_trigger_unregister_simple(ledtrig_sleep); +} + +module_init(ledtrig_sleep_init); +module_exit(ledtrig_sleep_exit); + -- cgit v1.1