blob: 1c72b1ca15a3cd2c5654e1d63b0279d8ed2f4367 [file] [log] [blame]
/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* 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.
*/
#define pr_fmt(fmt) "%s: " fmt, __func__
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/workqueue.h>
#include <linux/err.h>
#include <linux/wakelock.h>
#include <linux/mfd/pm8xxx/core.h>
#include <linux/mfd/pm8xxx/pwm.h>
#include <linux/leds-pm8xxx-htc.h>
#define SSBI_REG_ADDR_DRV_KEYPAD 0x48
#define PM8XXX_DRV_KEYPAD_BL_MASK 0xf0
#define PM8XXX_DRV_KEYPAD_BL_SHIFT 0x04
#define SSBI_REG_ADDR_FLASH_DRV0 0x49
#define PM8XXX_DRV_FLASH_MASK 0xf0
#define PM8XXX_DRV_FLASH_SHIFT 0x04
#define SSBI_REG_ADDR_FLASH_DRV1 0xFB
#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131
#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n))
#define PM8XXX_DRV_LED_CTRL_MASK 0xf8
#define PM8XXX_DRV_LED_CTRL_SHIFT 0x03
#define MAX_FLASH_LED_CURRENT 300
#define MAX_LC_LED_CURRENT 40
#define MAX_KP_BL_LED_CURRENT 300
#define PM8XXX_ID_LED_CURRENT_FACTOR 2
#define PM8XXX_ID_FLASH_CURRENT_FACTOR 20
#define PM8XXX_FLASH_MODE_DBUS1 1
#define PM8XXX_FLASH_MODE_DBUS2 2
#define PM8XXX_FLASH_MODE_PWM 3
#define PM8XXX_LED_OFFSET(id) (PM8XXX_ID_LED_0 - (id))
#define MAX_LC_LED_BRIGHTNESS 20
#define MAX_FLASH_BRIGHTNESS 15
#define MAX_KB_LED_BRIGHTNESS 15
#define PM8XXX_LPG_CTL_REGS 7
#define LED_DBG(fmt, ...) \
({ if (0) printk(KERN_DEBUG "[LED]" fmt, ##__VA_ARGS__); })
#define LED_INFO(fmt, ...) \
printk(KERN_INFO "[LED]" fmt, ##__VA_ARGS__)
#define LED_ERR(fmt, ...) \
printk(KERN_ERR "[LED][ERR]" fmt, ##__VA_ARGS__)
static struct workqueue_struct *g_led_work_queue;
struct wake_lock pmic_led_wake_lock;
static struct pm8xxx_led_data *pm8xxx_leds , *for_key_led_data, *green_back_led_data, *amber_back_led_data;
static int flag_hold_virtual_key = 0;
static int virtual_key_state;
static int current_blink = 0;
u8 pm8xxxx_led_pwm_mode(int flag)
{
u8 mode = 0;
switch (flag) {
case PM8XXX_ID_LED_0:
mode = PM8XXX_LED_MODE_PWM3;
break;
case PM8XXX_ID_LED_1:
mode = PM8XXX_LED_MODE_PWM2;
break;
case PM8XXX_ID_LED_2:
mode = PM8XXX_LED_MODE_PWM1;
break;
}
return mode;
}
static void led_fade_do_work(struct work_struct *work)
{
struct pm8xxx_led_data *led;
int rc, offset;
u8 level;
led = container_of(work, struct pm8xxx_led_data, fade_delayed_work.work);
level = (0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(led->id);
led->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
led->reg |= level;
rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), led->reg);
if (rc)
LED_ERR("%s can't set (%d) led value rc=%d\n", __func__, led->id, rc);
}
void pm8xxx_led_current_set_for_key(int brightness_key)
{
int rc, offset;
static u8 level, register_key;
LED_INFO("%s brightness_key: %d\n", __func__,brightness_key);
if (brightness_key) {
flag_hold_virtual_key = 1;
level = (40 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(for_key_led_data->id);
register_key = pm8xxxx_led_pwm_mode(for_key_led_data->id);
register_key &= ~PM8XXX_DRV_LED_CTRL_MASK;
register_key |= level;
rc = pm8xxx_writeb(for_key_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), register_key);
if (rc)
LED_ERR("%s can't set (%d) led value rc=%d\n", __func__, PM8XXX_ID_LED_0, rc);
pwm_config(for_key_led_data->pwm_led, 320000, 640000);
pwm_enable(for_key_led_data->pwm_led);
} else {
pwm_disable(for_key_led_data->pwm_led);
level = (0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(for_key_led_data->id);
register_key = pm8xxxx_led_pwm_mode(for_key_led_data->id);
register_key &= ~PM8XXX_DRV_LED_CTRL_MASK;
register_key |= level;
rc = pm8xxx_writeb(for_key_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), register_key);
if (rc)
LED_ERR("%s can't set (%d) led value rc=%d\n", __func__, PM8XXX_ID_LED_0, rc);
if (virtual_key_state != 0){
level = 0;
register_key = 0;
level = (40 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(for_key_led_data->id);
register_key = pm8xxxx_led_pwm_mode(for_key_led_data->id);
register_key &= ~PM8XXX_DRV_LED_CTRL_MASK;
register_key |= level;
rc = pm8xxx_writeb(for_key_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), register_key);
if (rc)
LED_ERR("%s can't set (%d) led value rc=%d\n", __func__, PM8XXX_ID_LED_0, rc);
pwm_config(for_key_led_data->pwm_led, 64000, 64000);
pwm_enable(for_key_led_data->pwm_led);
}
flag_hold_virtual_key = 0;
}
}
static void pm8xxx_led_current_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
struct pm8xxx_led_data *led = container_of(led_cdev, struct pm8xxx_led_data, cdev);
int rc, offset;
u8 level;
int *pduties;
LED_INFO("%s, bank:%d, brightness:%d\n", __func__, led->bank, brightness);
cancel_delayed_work_sync(&led->fade_delayed_work);
virtual_key_state = brightness;
if (flag_hold_virtual_key == 1) {
LED_INFO("%s, key control \n", __func__);
return;
}
if(brightness) {
level = (led->out_current << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(led->id);
led->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
led->reg |= level;
rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), led->reg);
if (rc)
LED_ERR("%s can't set (%d) led value rc=%d\n", __func__, led->id, rc);
if (led->function_flags & LED_BRETH_FUNCTION) {
pduties = led->duties;
pm8xxx_pwm_lut_config(led->pwm_led,
led->period_us,
pduties,
led->duty_time_ms,
led->start_index,
led->duites_size,
0, 0,
led->lut_flag);
pm8xxx_pwm_lut_enable(led->pwm_led, 0);
pm8xxx_pwm_lut_enable(led->pwm_led, 1);
} else {
pwm_config(led->pwm_led, 64000, 64000);
pwm_enable(led->pwm_led);
}
} else {
if (led->function_flags & LED_BRETH_FUNCTION) {
wake_lock_timeout(&pmic_led_wake_lock, HZ*2);
pduties = led->duties + led->duites_size;
pm8xxx_pwm_lut_config(led->pwm_led,
led->period_us,
pduties,
led->duty_time_ms,
led->start_index,
led->duites_size,
0, 0,
led->lut_flag);
pm8xxx_pwm_lut_enable(led->pwm_led, 0);
pm8xxx_pwm_lut_enable(led->pwm_led, 1);
queue_delayed_work(g_led_work_queue,
&led->fade_delayed_work,
msecs_to_jiffies(led->duty_time_ms*led->duites_size));
} else {
pwm_disable(led->pwm_led);
level = (0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(led->id);
led->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
led->reg |= level;
rc = pm8xxx_writeb(led->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), led->reg);
if (rc)
LED_ERR("%s can't set (%d) led value rc=%d\n", __func__, led->id, rc);
}
}
}
static void pm8xxx_led_gpio_set(struct led_classdev *led_cdev, enum led_brightness brightness)
{
int rc, offset;
u8 level;
struct pm8xxx_led_data *led = container_of(led_cdev, struct pm8xxx_led_data, cdev);
LED_INFO("%s, bank:%d, brightness:%d sync: %d\n", __func__, led->bank, brightness, led->led_sync);
if (led->gpio_status_switch != NULL)
led->gpio_status_switch(0);
pwm_disable(led->pwm_led);
if(led->led_sync) {
if (!strcmp(led->cdev.name, "green")){
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(0);
pwm_disable(green_back_led_data->pwm_led);
level = ( 0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(green_back_led_data->id);
green_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
green_back_led_data->reg |= level;
rc = pm8xxx_writeb(green_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), green_back_led_data->reg);
}
if (!strcmp(led->cdev.name, "amber")){
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(0);
pwm_disable(amber_back_led_data->pwm_led);
level = ( 0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(amber_back_led_data->id);
amber_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
amber_back_led_data->reg |= level;
rc = pm8xxx_writeb(amber_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), amber_back_led_data->reg);
}
}
if (brightness) {
if (led->gpio_status_switch != NULL)
led->gpio_status_switch(1);
pwm_config(led->pwm_led, 6400 * led->pwm_coefficient / 100, 6400);
pwm_enable(led->pwm_led);
if(led->led_sync) {
if (!strcmp(led->cdev.name, "green")) {
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(1);
level = (green_back_led_data->out_current << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(green_back_led_data->id);
green_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
green_back_led_data->reg |= level;
rc = pm8xxx_writeb(green_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), green_back_led_data->reg);
pwm_config(green_back_led_data->pwm_led, 64000, 64000);
pwm_enable(green_back_led_data->pwm_led);
}
if (!strcmp(led->cdev.name, "amber")) {
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(1);
level = (amber_back_led_data->out_current << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(amber_back_led_data->id);
amber_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
amber_back_led_data->reg |= level;
rc = pm8xxx_writeb(amber_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), amber_back_led_data->reg);
pwm_config(amber_back_led_data->pwm_led, 64000, 64000);
pwm_enable(amber_back_led_data->pwm_led);
}
}
}
}
static void led_work_func(struct work_struct *work)
{
struct pm8xxx_led_data *ldata;
int rc, offset;
u8 level;
ldata = container_of(work, struct pm8xxx_led_data, led_work);
LED_INFO("%s: bank %d sync %d\n", __func__, ldata->bank, ldata->led_sync);
pwm_disable(ldata->pwm_led);
if (ldata->gpio_status_switch != NULL)
ldata->gpio_status_switch(0);
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")){
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(0);
pwm_disable(green_back_led_data->pwm_led);
level = ( 0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(green_back_led_data->id);
green_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
green_back_led_data->reg |= level;
rc = pm8xxx_writeb(green_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), green_back_led_data->reg);
}
if (!strcmp(ldata->cdev.name, "amber")){
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(0);
pwm_disable(amber_back_led_data->pwm_led);
level = ( 0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(amber_back_led_data->id);
amber_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
amber_back_led_data->reg |= level;
rc = pm8xxx_writeb(amber_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), amber_back_led_data->reg);
}
}
}
static void led_alarm_handler(struct alarm *alarm)
{
struct pm8xxx_led_data *ldata;
ldata = container_of(alarm, struct pm8xxx_led_data, led_alarm);
queue_work(g_led_work_queue, &ldata->led_work);
}
static void led_blink_do_work(struct work_struct *work)
{
struct pm8xxx_led_data *led;
led = container_of(work, struct pm8xxx_led_data, blink_delayed_work.work);
if (led->gpio_status_switch != NULL)
led->gpio_status_switch(1);
pwm_config(led->pwm_led, led->duty_time_ms * 1000, led->period_us);
pwm_enable(led->pwm_led);
if(led->led_sync) {
if (!strcmp(led->cdev.name, "green")) {
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(1);
pwm_config(green_back_led_data->pwm_led, green_back_led_data->duty_time_ms * 1000, green_back_led_data->period_us);
pwm_enable(green_back_led_data->pwm_led);
}
if (!strcmp(led->cdev.name, "amber")) {
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(1);
pwm_config(amber_back_led_data->pwm_led, amber_back_led_data->duty_time_ms * 1000, amber_back_led_data->period_us);
pwm_enable(amber_back_led_data->pwm_led);
}
}
}
static ssize_t pm8xxx_led_blink_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%d\n", current_blink);
}
static ssize_t pm8xxx_led_blink_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct led_classdev *led_cdev;
struct pm8xxx_led_data *ldata;
int val;
int level, offset;
val = -1;
sscanf(buf, "%u", &val);
if (val < 0 || val > 255)
return -EINVAL;
current_blink= val;
led_cdev = (struct led_classdev *) dev_get_drvdata(dev);
ldata = container_of(led_cdev, struct pm8xxx_led_data, cdev);
LED_INFO("%s: bank %d blink %d sync %d\n", __func__, ldata->bank, val, ldata->led_sync);
switch (val) {
case BLINK_STOP:
if (ldata->gpio_status_switch != NULL)
ldata->gpio_status_switch(0);
pwm_disable(ldata->pwm_led);
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")) {
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(0);
pwm_disable(green_back_led_data->pwm_led);
}
if (!strcmp(ldata->cdev.name, "amber")) {
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(0);
pwm_disable(amber_back_led_data->pwm_led);
}
}
break;
case BLINK_UNCHANGE:
pwm_disable(ldata->pwm_led);
if (led_cdev->brightness) {
if (ldata->gpio_status_switch != NULL)
ldata->gpio_status_switch(1);
pwm_config(ldata->pwm_led, 6400 * ldata->pwm_coefficient / 100, 6400);
pwm_enable(ldata->pwm_led);
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")) {
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(1);
pwm_config(green_back_led_data->pwm_led, 64000, 64000);
pwm_enable(green_back_led_data->pwm_led);
}
if (!strcmp(ldata->cdev.name, "amber")) {
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(1);
pwm_config(amber_back_led_data->pwm_led, 64000, 64000);
pwm_enable(amber_back_led_data->pwm_led);
}
}
} else {
pwm_disable(ldata->pwm_led);
if (ldata->gpio_status_switch != NULL)
ldata->gpio_status_switch(0);
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")){
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(0);
pwm_disable(green_back_led_data->pwm_led);
level = ( 0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(green_back_led_data->id);
green_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
green_back_led_data->reg |= level;
pm8xxx_writeb(green_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), green_back_led_data->reg);
}
if (!strcmp(ldata->cdev.name, "amber")){
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(0);
pwm_disable(amber_back_led_data->pwm_led);
level = ( 0 << PM8XXX_DRV_LED_CTRL_SHIFT) & PM8XXX_DRV_LED_CTRL_MASK;
offset = PM8XXX_LED_OFFSET(amber_back_led_data->id);
amber_back_led_data->reg &= ~PM8XXX_DRV_LED_CTRL_MASK;
amber_back_led_data->reg |= level;
pm8xxx_writeb(amber_back_led_data->dev->parent, SSBI_REG_ADDR_LED_CTRL(offset), amber_back_led_data->reg);
}
}
}
break;
case BLINK_64MS_PER_2SEC:
if (ldata->gpio_status_switch != NULL)
ldata->gpio_status_switch(1);
pwm_disable(ldata->pwm_led);
pwm_config(ldata->pwm_led, 64000, 2000000);
pwm_enable(ldata->pwm_led);
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")) {
if (green_back_led_data->gpio_status_switch != NULL)
green_back_led_data->gpio_status_switch(1);
pwm_disable(green_back_led_data->pwm_led);
pwm_config(green_back_led_data->pwm_led, 64000, 2000000);
pwm_enable(green_back_led_data->pwm_led);
}
if (!strcmp(ldata->cdev.name, "amber")) {
if (amber_back_led_data->gpio_status_switch != NULL)
amber_back_led_data->gpio_status_switch(1);
pwm_disable(amber_back_led_data->pwm_led);
pwm_config(amber_back_led_data->pwm_led, 64000, 2000000);
pwm_enable(amber_back_led_data->pwm_led);
}
}
break;
case BLINK_64MS_ON_310MS_PER_2SEC:
cancel_delayed_work_sync(&ldata->blink_delayed_work);
pwm_disable(ldata->pwm_led);
ldata->duty_time_ms = 64;
ldata->period_us = 2000000;
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")) {
pwm_disable(green_back_led_data->pwm_led);
green_back_led_data->duty_time_ms = 64;
green_back_led_data->period_us = 2000000;
}
if (!strcmp(ldata->cdev.name, "amber")) {
pwm_disable(amber_back_led_data->pwm_led);
amber_back_led_data->duty_time_ms = 64;
amber_back_led_data->period_us = 2000000;
}
}
queue_delayed_work(g_led_work_queue, &ldata->blink_delayed_work,
msecs_to_jiffies(310));
break;
case BLINK_64MS_ON_2SEC_PER_2SEC:
cancel_delayed_work_sync(&ldata->blink_delayed_work);
pwm_disable(ldata->pwm_led);
ldata->duty_time_ms = 64;
ldata->period_us = 2000000;
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")) {
pwm_disable(green_back_led_data->pwm_led);
green_back_led_data->duty_time_ms = 64;
green_back_led_data->period_us = 2000000;
}
if (!strcmp(ldata->cdev.name, "amber")) {
pwm_disable(amber_back_led_data->pwm_led);
amber_back_led_data->duty_time_ms = 64;
amber_back_led_data->period_us = 2000000;
}
}
queue_delayed_work(g_led_work_queue, &ldata->blink_delayed_work,
msecs_to_jiffies(1000));
break;
case BLINK_1SEC_PER_2SEC:
pwm_disable(ldata->pwm_led);
pwm_config(ldata->pwm_led, 1000000, 2000000);
pwm_enable(ldata->pwm_led);
if(ldata->led_sync) {
if (!strcmp(ldata->cdev.name, "green")) {
pwm_disable(green_back_led_data->pwm_led);
pwm_config(green_back_led_data->pwm_led, 1000000, 2000000);
pwm_enable(green_back_led_data->pwm_led);
}
if (!strcmp(ldata->cdev.name, "amber")) {
pwm_disable(amber_back_led_data->pwm_led);
pwm_config(amber_back_led_data->pwm_led, 1000000, 2000000);
pwm_enable(amber_back_led_data->pwm_led);
}
}
break;
default:
LED_ERR("%s: bank %d did not support blink type %d\n", __func__, ldata->bank, val);
return -EINVAL;
}
return count;
}
static DEVICE_ATTR(blink, 0644, pm8xxx_led_blink_show, pm8xxx_led_blink_store);
static ssize_t pm8xxx_led_off_timer_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct led_classdev *led_cdev;
struct pm8xxx_led_data *ldata;
int min, sec;
uint16_t off_timer;
ktime_t interval;
ktime_t next_alarm;
min = -1;
sec = -1;
sscanf(buf, "%d %d", &min, &sec);
if (min < 0 || min > 255)
return -EINVAL;
if (sec < 0 || sec > 255)
return -EINVAL;
led_cdev = (struct led_classdev *) dev_get_drvdata(dev);
ldata = container_of(led_cdev, struct pm8xxx_led_data, cdev);
LED_INFO("Setting %s off_timer to %d min %d sec \n", led_cdev->name, min, sec);
off_timer = min * 60 + sec;
alarm_cancel(&ldata->led_alarm);
cancel_work_sync(&ldata->led_work);
if (off_timer) {
interval = ktime_set(off_timer, 0);
next_alarm = ktime_add(alarm_get_elapsed_realtime(), interval);
alarm_start_range(&ldata->led_alarm, next_alarm, next_alarm);
}
return count;
}
static DEVICE_ATTR(off_timer, 0644, NULL, pm8xxx_led_off_timer_store);
static ssize_t pm8xxx_led_currents_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev;
struct pm8xxx_led_data *ldata;
led_cdev = (struct led_classdev *) dev_get_drvdata(dev);
ldata = container_of(led_cdev, struct pm8xxx_led_data, cdev);
return sprintf(buf, "%d\n", ldata->out_current);
}
static ssize_t pm8xxx_led_currents_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int currents = 0;
struct led_classdev *led_cdev;
struct pm8xxx_led_data *ldata;
sscanf(buf, "%d", &currents);
if (currents < 0)
return -EINVAL;
led_cdev = (struct led_classdev *)dev_get_drvdata(dev);
ldata = container_of(led_cdev, struct pm8xxx_led_data, cdev);
LED_INFO("%s: bank %d currents %d\n", __func__, ldata->bank, currents);
ldata->out_current = currents;
ldata->cdev.brightness_set(led_cdev, 0);
if (currents)
ldata->cdev.brightness_set(led_cdev, 255);
return count;
}
static DEVICE_ATTR(currents, 0644, pm8xxx_led_currents_show, pm8xxx_led_currents_store);
static ssize_t pm8xxx_led_pwm_coefficient_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct led_classdev *led_cdev;
struct pm8xxx_led_data *ldata;
led_cdev = (struct led_classdev *) dev_get_drvdata(dev);
ldata = container_of(led_cdev, struct pm8xxx_led_data, cdev);
return sprintf(buf, "%d\n", ldata->pwm_coefficient);
}
static ssize_t pm8xxx_led_pwm_coefficient_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int pwm_coefficient1 = 0;
struct led_classdev *led_cdev;
struct pm8xxx_led_data *ldata;
sscanf(buf, "%d", &pwm_coefficient1);
if (pwm_coefficient1 < 0)
return -EINVAL;
led_cdev = (struct led_classdev *)dev_get_drvdata(dev);
ldata = container_of(led_cdev, struct pm8xxx_led_data, cdev);
LED_INFO("%s: pwm_coefficient %d\n", __func__, pwm_coefficient1);
ldata->pwm_coefficient = pwm_coefficient1;
return count;
}
static DEVICE_ATTR(pwm_coefficient, 0644, pm8xxx_led_pwm_coefficient_show, pm8xxx_led_pwm_coefficient_store);
static int __devinit pm8xxx_led_probe(struct platform_device *pdev)
{
const struct pm8xxx_led_platform_data *pdata = pdev->dev.platform_data;
struct pm8xxx_led_configure *curr_led;
struct pm8xxx_led_data *led, *led_dat;
int i, ret = -ENOMEM;
if (pdata == NULL) {
LED_ERR("platform data not supplied\n");
return -EINVAL;
}
led = kcalloc(pdata->num_leds + 1, sizeof(*led), GFP_KERNEL);
if (led == NULL) {
LED_ERR("failed to alloc memory\n");
return -ENOMEM;
}
wake_lock_init(&pmic_led_wake_lock, WAKE_LOCK_SUSPEND, "pmic_led");
g_led_work_queue = create_workqueue("pm8xxx-led");
if (g_led_work_queue == NULL) {
LED_ERR("failed to create workqueue\n");
goto err_create_work_queue;
}
for (i = 0; i < pdata->num_leds; i++) {
curr_led = &pdata->leds[i];
led_dat = &led[i];
led_dat->cdev.name = curr_led->name;
led_dat->id = curr_led->flags;
led_dat->bank = curr_led->flags;
led_dat->function_flags = curr_led->function_flags;
led_dat->start_index = curr_led->start_index;
led_dat->duty_time_ms = curr_led->duty_time_ms;
led_dat->period_us = curr_led->period_us;
led_dat->duites_size = curr_led->duites_size;
led_dat->lut_flag = curr_led->lut_flag;
led_dat->out_current = curr_led->out_current;
led_dat->duties = &(curr_led->duties[0]);
led_dat->led_sync = curr_led->led_sync;
led_dat->pwm_led = pwm_request(led_dat->bank, led_dat->cdev.name);
if( curr_led->pwm_coefficient > 0 )
led_dat->pwm_coefficient = curr_led->pwm_coefficient;
else
led_dat->pwm_coefficient = 100;
switch (led_dat->id) {
case PM8XXX_ID_GPIO24:
case PM8XXX_ID_GPIO25:
case PM8XXX_ID_GPIO26:
led_dat->cdev.brightness_set = pm8xxx_led_gpio_set;
if (curr_led->gpio_status_switch != NULL)
led_dat->gpio_status_switch = curr_led->gpio_status_switch;
break;
case PM8XXX_ID_LED_0:
case PM8XXX_ID_LED_1:
case PM8XXX_ID_LED_2:
led_dat->cdev.brightness_set = pm8xxx_led_current_set;
if (led_dat->function_flags & LED_PWM_FUNCTION) {
led_dat->reg = pm8xxxx_led_pwm_mode(led_dat->id);
INIT_DELAYED_WORK(&led[i].fade_delayed_work, led_fade_do_work);
} else
led_dat->reg = PM8XXX_LED_MODE_MANUAL;
break;
case PM8XXX_ID_LED_KB_LIGHT:
break;
}
led_dat->cdev.brightness = LED_OFF;
led_dat->dev = &pdev->dev;
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret) {
LED_ERR("unable to register led %d,ret=%d\n", led_dat->id, ret);
goto err_register_led_cdev;
}
if (led_dat->id >= PM8XXX_ID_LED_2 && led_dat->id <= PM8XXX_ID_LED_0) {
ret = device_create_file(led_dat->cdev.dev, &dev_attr_currents);
if (ret < 0) {
LED_ERR("%s: Failed to create %d attr currents\n", __func__, i);
goto err_register_attr_currents;
}
}
if (led_dat->id <= PM8XXX_ID_GPIO26) {
ret = device_create_file(led_dat->cdev.dev, &dev_attr_pwm_coefficient);
if (ret < 0) {
LED_ERR("%s: Failed to create %d attr pwm_coefficient\n", __func__, i);
goto err_register_attr_pwm_coefficient;
}
}
if (led_dat->function_flags & LED_BLINK_FUNCTION) {
INIT_DELAYED_WORK(&led[i].blink_delayed_work, led_blink_do_work);
ret = device_create_file(led_dat->cdev.dev, &dev_attr_blink);
if (ret < 0) {
LED_ERR("%s: Failed to create %d attr blink\n", __func__, i);
goto err_register_attr_blink;
}
ret = device_create_file(led_dat->cdev.dev, &dev_attr_off_timer);
if (ret < 0) {
LED_ERR("%s: Failed to create %d attr off timer\n", __func__, i);
goto err_register_attr_off_timer;
}
alarm_init(&led[i].led_alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, led_alarm_handler);
INIT_WORK(&led[i].led_work, led_work_func);
}
if (!strcmp(led_dat->cdev.name, "button-backlight")) {
for_key_led_data = led_dat;
}
if (!strcmp(led_dat->cdev.name, "green-back")) {
LED_INFO("%s: matt green-back, 000 probe, led_dat = %x\n", __func__, (unsigned int)led_dat);
green_back_led_data = led_dat;
}
if (!strcmp(led_dat->cdev.name, "amber-back")) {
LED_INFO("%s: matt amber-back\n", __func__);
amber_back_led_data = led_dat;
}
}
pm8xxx_leds = led;
platform_set_drvdata(pdev, led);
return 0;
err_register_attr_off_timer:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
if (led[i].function_flags & LED_BLINK_FUNCTION)
device_remove_file(led[i].cdev.dev, &dev_attr_off_timer);
}
}
i = pdata->num_leds;
err_register_attr_blink:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
if (led[i].function_flags & LED_BLINK_FUNCTION)
device_remove_file(led[i].cdev.dev, &dev_attr_blink);
}
}
i = pdata->num_leds;
err_register_attr_pwm_coefficient:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
if (led[i].function_flags <= PM8XXX_ID_GPIO26)
device_remove_file(led[i].cdev.dev, &dev_attr_pwm_coefficient);
}
}
i = pdata->num_leds;
err_register_attr_currents:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
if (led[i].function_flags >= PM8XXX_ID_LED_2 && led[i].function_flags <= PM8XXX_ID_LED_0)
device_remove_file(led[i].cdev.dev, &dev_attr_currents);
}
}
i = pdata->num_leds;
err_register_led_cdev:
if (i > 0) {
for (i = i - 1; i >= 0; i--) {
pwm_free(led[i].pwm_led);
led_classdev_unregister(&led[i].cdev);
}
}
destroy_workqueue(g_led_work_queue);
err_create_work_queue:
kfree(led);
wake_lock_destroy(&pmic_led_wake_lock);
return ret;
}
static int __devexit pm8xxx_led_remove(struct platform_device *pdev)
{
int i;
const struct led_platform_data *pdata =
pdev->dev.platform_data;
struct pm8xxx_led_data *led = platform_get_drvdata(pdev);
for (i = 0; i < pdata->num_leds; i++) {
led_classdev_unregister(&led[i].cdev);
if (led[i].function_flags >= PM8XXX_ID_LED_2 && led[i].function_flags <= PM8XXX_ID_LED_0)
device_remove_file(led[i].cdev.dev, &dev_attr_currents);
if (led[i].function_flags & LED_BLINK_FUNCTION)
device_remove_file(led[i].cdev.dev, &dev_attr_blink);
if (led[i].function_flags & LED_BLINK_FUNCTION)
device_remove_file(led[i].cdev.dev, &dev_attr_off_timer);
}
destroy_workqueue(g_led_work_queue);
wake_lock_destroy(&pmic_led_wake_lock);
kfree(led);
return 0;
}
static struct platform_driver pm8xxx_led_driver = {
.probe = pm8xxx_led_probe,
.remove = __devexit_p(pm8xxx_led_remove),
.driver = {
.name = PM8XXX_LEDS_DEV_NAME,
.owner = THIS_MODULE,
},
};
static int __init pm8xxx_led_init(void)
{
return platform_driver_register(&pm8xxx_led_driver);
}
subsys_initcall(pm8xxx_led_init);
static void __exit pm8xxx_led_exit(void)
{
platform_driver_unregister(&pm8xxx_led_driver);
}
module_exit(pm8xxx_led_exit);
MODULE_DESCRIPTION("PM8XXX LEDs driver");
MODULE_LICENSE("GPL v2");
MODULE_VERSION("1.0");
MODULE_ALIAS("platform:pm8xxx-led");