leds: Add in lm8502 driver
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index cb9e120..f8254b3 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -536,6 +536,13 @@
This option enables support for the LEDs on the Bachmann OT200.
Say Y to enable LEDs on the Bachmann OT200.
+config LEDS_LM8502
+ tristate "LED Support for NS LM8502 controlled LEDs"
+ depends on LEDS_CLASS
+ default n
+ help
+ This options enables support for NS LM8502 LED controller.
+
config LEDS_TRIGGERS
bool "LED Trigger support"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 62e6d78..0adfccf 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -60,6 +60,7 @@
obj-$(CONFIG_LEDS_QCIBL) += leds-qci-backlight.o
obj-$(CONFIG_LEDS_MSM_PDM) += leds-msm-pdm.o
obj-$(CONFIG_LEDS_MSM_TRICOLOR) += leds-msm-tricolor.o
+obj-$(CONFIG_LEDS_LM8502) += leds-lm8502.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/drivers/leds/leds-lm8502.c b/drivers/leds/leds-lm8502.c
new file mode 100644
index 0000000..e65775c
--- /dev/null
+++ b/drivers/leds/leds-lm8502.c
@@ -0,0 +1,1931 @@
+/*
+ * linux/drivers/misc/lm8502 - Driver for the LM8502 which is a combo <LED + Vibrator + Camera flash> device
+ *
+ * Copyright (C) 2008 Palm Inc,
+ *
+ * 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Authors: Kevin McCray (kevin.mccray@palm.com)
+ * Brian Xiong (brian.xiong@palm.com)
+ * Rajat Gupta (rajat.gupta@palm.com)
+ * Amon Xie (amon.xie@palm.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/leds.h>
+#include <linux/i2c.h>
+#include <linux/ctype.h>
+#include <linux/mutex.h>
+#include <linux/i2c_lm8502_led.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/slab.h>
+#include <linux/ktime.h>
+#include <linux/export.h>
+#include <linux/module.h>
+#include "../staging/android/timed_output.h"
+#include "leds-lm8502.h"
+
+
+//TODO: There are no register settings for auto-increment. Assuming feature missing in chip for now.
+//#define AUTO_INCR_WRITE
+//TODO: The dev board did not have the strobe pin connected. Disabling for now.
+//#define STROBE_ENABLE
+
+static long lm8502_ioctl(struct file *file,
+ unsigned int cmd, unsigned long arg);
+
+DECLARE_WAIT_QUEUE_HEAD(lm8502_wait_queue);
+DECLARE_WAIT_QUEUE_HEAD(lm8502_wait_stop_engine);
+
+struct lm8502_device_state {
+ struct i2c_client *i2c_dev;
+ struct lm8502_platform_data pdata;
+ struct work_struct notify_work;
+ struct mutex lock;
+ struct file_operations fops;
+ struct miscdevice mdev;
+ u16 instruct[INSTR_LEN];
+ int suspended;
+ int interrupt_state;
+ int stop_engine_state;
+ u8 stopengine;
+ struct class *vib_class;
+ struct device *vib_class_dev;
+ u8 vib_enable;
+ u8 vib_start;
+ u8 vib_duty_cycle;
+ u8 vib_direction;
+ struct class *flash_class;
+ struct device *flash_class_dev;
+ u8 is_flash_mode; // true => flash_mode false=> torch_mode
+ u8 flash_enable;
+ u8 flash_start;
+ u16 flash_duration;
+ u16 flash_current;
+ u16 torch_current;
+ /* debugfs variables */
+ struct dentry *debug_dir;
+ u8 addr;
+ u8 value;
+
+ struct hrtimer vib_timer;
+ struct timed_output_dev timed_dev;
+ spinlock_t spinlock;
+ struct work_struct work;
+ int state;
+};
+
+
+struct engine_cmd {
+ struct lm8502_device_state *context;
+ struct work_struct workitem;
+ int engine;
+ int cmd;
+};
+
+struct brightness_entry {
+ u8 code;
+ u16 current_value;
+};
+
+static struct brightness_entry flash_brightness_map[] = {
+ {0x0, 38},
+ {0x1, 75},
+ {0x2, 113},
+ {0x3, 150},
+ {0x4, 188},
+ {0x5, 225},
+ {0x6, 263},
+ {0x7, 300},
+ {0x8, 338},
+ {0x9, 375},
+ {0xA, 413},
+ {0xB, 450},
+ {0xC, 488},
+ {0xD, 525},
+ {0xE, 563},
+ {0xF, 600}
+};
+
+static struct brightness_entry torch_brightness_map[] = {
+ {0x0, 18},
+ {0x1, 37},
+ {0x2, 56},
+ {0x3, 75},
+ {0x4, 93},
+ {0x5, 112},
+ {0x6, 131},
+ {0x7, 150},
+};
+
+static struct lm8502_device_state *lm8502_state;
+static struct workqueue_struct *lm8502_vib_wq;
+
+
+
+static u8 lm8502_get_closest_flash_current(u16 desired_current)
+{
+ u16 i;
+ for(i = 0; i < sizeof(flash_brightness_map) / sizeof(flash_brightness_map[0]); i++) {
+ if (flash_brightness_map[i].current_value >= desired_current) {
+ return i;
+ }
+ }
+ return (sizeof(flash_brightness_map)/sizeof(flash_brightness_map[0]))-1;
+}
+
+static u8 lm8502_get_closest_torch_current(u16 desired_current)
+{
+ u16 i;
+ for(i = 0; i < sizeof(torch_brightness_map) / sizeof(torch_brightness_map[0]); i++) {
+ if(torch_brightness_map[i].current_value >= desired_current) {
+ return i;
+ }
+ }
+ return (sizeof(torch_brightness_map)/sizeof(torch_brightness_map[0]))-1;
+}
+
+
+int lm8502_i2c_read_reg(struct i2c_client* client, u8 addr, u8* out)
+{
+ int ret;
+ struct i2c_msg msg[2];
+
+ msg[0].addr = client->addr;
+ msg[0].len = 1;
+ msg[0].flags = 0;
+ msg[0].buf = &addr;
+
+ msg[1].addr = client->addr;
+ msg[1].len = 1;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = out;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret != 2)
+ printk(KERN_ERR "Unable to read LM8502 registers\n");
+
+ return ret;
+}
+
+int lm8502_i2c_write_reg(struct i2c_client* client, u8 addr, u8 val)
+{
+ u8 buf[2] = {addr, val};
+ int ret;
+ struct i2c_msg msg[1];
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = 2;
+ msg[0].buf = buf;
+
+ ret = i2c_transfer(client->adapter, msg, 1);
+ if (ret != 1)
+ printk(KERN_ERR "Unable to write to LM8502 registers\n");
+
+ return ret;
+}
+
+int lm8502_set_current(uint8_t is_flash_mode, uint32_t mA)
+{
+ u8 index;
+ int rc = 0;
+
+ lm8502_state->pdata.select_flash(lm8502_state->i2c_dev);
+ lm8502_state->is_flash_mode = is_flash_mode;
+ // if vibrator enable, disable it to avoid vibrator operation
+ lm8502_state->vib_enable = 0;
+ // avoid vib_enable be set
+ lm8502_state->flash_enable = 1;
+
+ if (is_flash_mode) {
+ index = lm8502_get_closest_flash_current(mA);
+ rc = lm8502_i2c_write_reg(lm8502_state->i2c_dev, FLASH_BRIGHTNESS,
+ flash_brightness_map[index].code << 3);
+ lm8502_state->flash_current = flash_brightness_map[index].current_value;
+ }
+ else {
+ index = lm8502_get_closest_torch_current(mA);
+ rc = lm8502_i2c_write_reg(lm8502_state->i2c_dev, TORCH_BRIGHTNESS,
+ torch_brightness_map[index].code << 3);
+ lm8502_state->torch_current = torch_brightness_map[index].current_value;
+ }
+
+ lm8502_state->flash_enable = 0;
+
+ return rc;
+}
+
+#ifdef AUTO_INCR_WRITE
+static int lm8502_i2c_write_reg_auto_incr(struct i2c_client* client, u8 * address_and_data, u8 length)
+{
+ int ret;
+ struct i2c_msg msg[1];
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = length;
+ msg[0].buf = address_and_data;
+
+ ret = i2c_transfer(client->adapter, msg, 1);
+ return ret;
+}
+#endif
+
+static ssize_t lm8502_flash_or_torch_start_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", state->flash_start);
+}
+
+static ssize_t lm8502_flash_or_torch_start_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 start;
+ u8 reg;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &start) != 1)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->flash_enable) {
+ printk(KERN_ERR "Flash must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ lm8502_i2c_read_reg(state->i2c_dev, FLASH_BRIGHTNESS, ®);
+ reg = reg & ~0x03;
+ if (start) {
+ if (state->is_flash_mode)
+ reg = reg | FLASH_MODE;
+ else
+ reg = reg | TORCH_MODE;
+ }
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_BRIGHTNESS, reg);
+
+ state->flash_start = start;
+
+ return count;
+}
+
+static ssize_t lm8502_flash_duration_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", state->flash_duration);
+}
+
+static ssize_t lm8502_flash_duration_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 duration;
+ u8 reg;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &duration) != 1)
+ return -EINVAL;
+
+ if (duration >= 1024) {
+ printk(KERN_ERR "Duration is too long\n");
+ return -EINVAL;
+ }
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->flash_enable) {
+ printk(KERN_ERR "Flash must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ state->flash_duration = duration;
+ duration = duration >> 5;
+
+ lm8502_i2c_read_reg(state->i2c_dev, FLASH_DURATION, ®);
+ reg = (reg & ~0x1F) | duration;
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_DURATION, reg);
+
+ return count;
+}
+
+static ssize_t lm8502_flash_current_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", state->flash_current);
+}
+
+static ssize_t lm8502_flash_current_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 desired_current;
+ u8 index;
+ u8 reg;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &desired_current) != 1)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->flash_enable) {
+ printk(KERN_ERR "Flash must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ index = lm8502_get_closest_flash_current(desired_current);
+ lm8502_i2c_read_reg(state->i2c_dev, FLASH_BRIGHTNESS, ®);
+ reg = reg & 0x07; //bit[7:3]
+ reg = reg | STROBE_TIMEOUT | (flash_brightness_map[index].code << 3);
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_BRIGHTNESS, reg);
+
+ state->flash_current = flash_brightness_map[index].current_value;
+
+ return count;
+}
+
+static ssize_t lm8502_torch_current_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", state->torch_current);
+}
+
+static ssize_t lm8502_torch_current_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 desired_current;
+ u8 index;
+ u8 reg;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &desired_current) != 1)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->flash_enable) {
+ printk(KERN_ERR "Flash must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ index = lm8502_get_closest_torch_current(desired_current);
+ lm8502_i2c_read_reg(state->i2c_dev, TORCH_BRIGHTNESS, ®);
+ reg = reg & ~0x38; //bit[5:3]
+ reg = reg | (torch_brightness_map[index].code << 3);
+ lm8502_i2c_write_reg(state->i2c_dev, TORCH_BRIGHTNESS, reg);
+
+ state->torch_current = torch_brightness_map[index].current_value;
+
+ return count;
+}
+
+static ssize_t lm8502_flash_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", state->flash_enable);
+}
+
+static ssize_t lm8502_flash_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 enable;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &enable) != 1)
+ return -EINVAL;
+
+ if (enable > 2)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ state->flash_enable = (u8)enable;
+
+ if (!enable)
+ return count;
+
+ if (state->vib_enable) {
+ printk(KERN_ERR "Flash and vibrator cannot be used together\n");
+ return -EINVAL;
+ }
+
+ state->pdata.select_flash(state->i2c_dev);
+ state->is_flash_mode = (enable == 1); // 1 == flash, 2 == torch
+
+ return count;
+}
+
+static DEVICE_ATTR(flash_enable, S_IRUGO | S_IWUSR, lm8502_flash_enable_show, lm8502_flash_enable_store);
+static DEVICE_ATTR(torch_current, S_IRUGO | S_IWUSR, lm8502_torch_current_show, lm8502_torch_current_store);
+static DEVICE_ATTR(flash_current, S_IRUGO | S_IWUSR, lm8502_flash_current_show, lm8502_flash_current_store);
+static DEVICE_ATTR(flash_duration, S_IRUGO | S_IWUSR, lm8502_flash_duration_show, lm8502_flash_duration_store);
+static DEVICE_ATTR(flash_or_torch_start, S_IRUGO | S_IWUSR, lm8502_flash_or_torch_start_show, lm8502_flash_or_torch_start_store);
+
+static ssize_t lm8502_vib_start_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", state->vib_start);
+}
+
+static ssize_t lm8502_vib_start_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 start;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &start) != 1)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->vib_enable) {
+ printk(KERN_ERR "Vibrator must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ state->vib_start = (u8)start;
+
+ if (start) {
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_FEEDBACK_CTRL,
+ 0x02 + !!(state->pdata.vib_invert_direction ^ state->vib_direction));
+ } else {
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_FEEDBACK_CTRL, 0);
+ }
+
+ return count;
+}
+
+static ssize_t lm8502_vib_direction_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", state->vib_direction);
+}
+
+static ssize_t lm8502_vib_direction_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 dir;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &dir) != 1)
+ return -EINVAL;
+
+ if (dir != 0 && dir != 1)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->vib_enable) {
+ printk(KERN_ERR "Vibrator must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ if (state->vib_direction != (u8)dir) {
+ state->vib_direction = (u8)dir;
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_FEEDBACK_CTRL,
+ 0x02 + !!(state->pdata.vib_invert_direction ^ state->vib_direction ));
+ }
+
+ return count;
+}
+
+
+static ssize_t lm8502_vib_duty_cycle_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", state->vib_duty_cycle);
+}
+
+static ssize_t lm8502_vib_duty_cycle_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 duty_cycle;
+
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &duty_cycle) != 1)
+ return -EINVAL;
+
+ if (duty_cycle > 100)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ if (!state->vib_enable) {
+ printk(KERN_ERR "Vibrator must be enabled before use\n");
+ return -EINVAL;
+ }
+
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_PWM_DUTY_CYCLE, (duty_cycle * 255) / 100);
+ state->vib_duty_cycle = (u8)duty_cycle;
+
+ return count;
+}
+
+static ssize_t lm8502_vib_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct lm8502_device_state *state;
+
+ if (!buf)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+
+ return snprintf(buf, PAGE_SIZE, "%d\n", state->vib_enable);
+}
+
+static ssize_t lm8502_vib_enable_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct lm8502_device_state *state;
+ u32 enable;
+
+ /* Do we have valid inputs? */
+ if (!buf || !count)
+ return -EINVAL;
+
+ if (sscanf(buf, "%u", &enable) != 1)
+ return -EINVAL;
+
+ if (enable > 1)
+ return -EINVAL;
+
+ state = (struct lm8502_device_state *) dev_get_drvdata(dev);
+ state->vib_enable = (u8)enable;
+
+ if (!enable) {
+ return count;
+ }
+
+ /* Logic to enable follows */
+ if (state->flash_enable) {
+ printk(KERN_ERR "Vibrator and flash cannot be enabled together\n");
+ return -EINVAL;
+ }
+
+ state->pdata.select_vibrator(state->i2c_dev);
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_FEEDBACK_CTRL, 0);
+
+ return count;
+}
+
+static DEVICE_ATTR(vib_enable, S_IRUGO | S_IWUSR, lm8502_vib_enable_show, lm8502_vib_enable_store);
+static DEVICE_ATTR(vib_direction, S_IRUGO | S_IWUSR, lm8502_vib_direction_show, lm8502_vib_direction_store);
+static DEVICE_ATTR(vib_duty_cycle, S_IRUGO | S_IWUSR, lm8502_vib_duty_cycle_show, lm8502_vib_duty_cycle_store);
+static DEVICE_ATTR(vib_start, S_IRUGO | S_IWUSR, lm8502_vib_start_show, lm8502_vib_start_store);
+
+static int lm8502_convert_percent_to_pwm(int percent, int *pwm)
+{
+ int ret = 0;
+
+ if(percent < 0 || percent > 100)
+ {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ *pwm = (255 * percent) / 100;
+
+done:
+ return ret;
+}
+
+static int lm8502_config_engine_control_mode(struct lm8502_device_state *p_state, int eng, u8 engine_cntrl, u8 mode)
+{
+ u8 reg;
+ int ret = 0;
+
+ lm8502_i2c_read_reg(p_state->i2c_dev, engine_cntrl, ®);
+
+ // ENGINE_CNTRL1 and ENGINE_CNTRL2 have the same bit mappings
+ // bits[5:4] = Engine 1
+ // bits[3:2] = Engine 2
+
+ switch(eng)
+ {
+ case 1:
+ // clear existing bits for ENG1 control
+ reg = reg & ~(3 << ENGINE_CNTRL_ENG1_SHIFT);
+ lm8502_i2c_write_reg(p_state->i2c_dev, engine_cntrl, reg | (mode << ENGINE_CNTRL_ENG1_SHIFT));
+ break;
+ case 2:
+ // clear existing bits for ENG2 control
+ reg = reg & ~(3 << ENGINE_CNTRL_ENG2_SHIFT);
+ lm8502_i2c_write_reg(p_state->i2c_dev, engine_cntrl, reg | (mode << ENGINE_CNTRL_ENG2_SHIFT));
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void lm8502_setup_page_sizes(struct lm8502_device_state *p_state, int eng, int *startpage, int *endpage)
+{
+ switch(eng)
+ {
+ case 1:
+ *startpage = p_state->pdata.memcfg->eng1_startpage;
+ *endpage = p_state->pdata.memcfg->eng1_endpage;
+ break;
+ case 2:
+ *startpage = p_state->pdata.memcfg->eng2_startpage;
+ *endpage = p_state->pdata.memcfg->eng2_endpage;
+ break;
+ default:
+ break;
+ }
+}
+
+static void lm8502_setup_engine_params(int eng, int *engine_pc, int *engine_prog_start_addr)
+{
+ switch(eng)
+ {
+ case 1:
+ *engine_pc = ENGINE1_PC;
+ *engine_prog_start_addr = ENG1_PROG_START_ADDR;
+ break;
+ case 2:
+ *engine_pc = ENGINE2_PC;
+ *engine_prog_start_addr = ENG2_PROG_START_ADDR;
+ break;
+ default:
+ break;
+ }
+}
+
+static int lm8502_start_engine(struct lm8502_device_state *p_state, int eng)
+{
+#ifdef AUTO_INCR_WRITE
+ u8 reg, address_and_data [LM8502_INSTR_LEN_PER_PAGE * 2 + 1];
+ int k = 0;
+#else
+ u8 upper, lower;
+#endif
+ int i, j, ret=0;
+ int page_start=0, page_end=0, engine_pc=0, engine_prog_start_addr=0;
+ // configure start/end page address and engine pc/start address
+ lm8502_setup_page_sizes(p_state, eng, &page_start, &page_end);
+ lm8502_setup_engine_params(eng, &engine_pc, &engine_prog_start_addr);
+
+ //read and write to the pc needs to be in hold mode
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL1, ENGINE_CNTRL1_HOLD);
+
+ //disable current engine
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL2, ENGINE_CNTRL2_DISABLE);
+
+ // configure engine_pc, program start address and page number
+ lm8502_i2c_write_reg(p_state->i2c_dev, engine_pc, page_start * 16);
+ lm8502_i2c_write_reg(p_state->i2c_dev, engine_prog_start_addr, page_start * 16);
+ lm8502_i2c_write_reg(p_state->i2c_dev, PROG_MEM_PAGE_SELECT, page_start);
+
+ // configure load program mode
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL2, ENGINE_CNTRL2_LOAD);
+
+#ifdef AUTO_INCR_WRITE
+ // Enable the serial bus address auto increment
+ lm8502_i2c_read_reg(p_state->i2c_dev, CONFIG, ®);
+ lm8502_i2c_write_reg(p_state->i2c_dev, CONFIG, reg | CONFIG_AUTO_INCR_ON);
+#endif
+
+ for (i = page_start; i <= page_end; i++) {
+
+ /* Configure the memory page */
+ lm8502_i2c_write_reg(p_state->i2c_dev, PROG_MEM_PAGE_SELECT, i);
+
+ #ifdef AUTO_INCR_WRITE
+ k = 0;
+ // Assemble the data to be written
+ address_and_data [k++] = PROG_MEM_START;
+
+ for (j = 0; j < 16; j++)
+ {
+ // Arm has oppose upper 8 bits and lower 8 bits, needs to be reverted
+ address_and_data [k++] = p_state->instruct[i*16 + j]>>8;
+ address_and_data [k++] = p_state->instruct[i*16 + j];
+ }
+
+ // Write the entire page
+ lm8502_i2c_write_reg_auto_incr (p_state->i2c_dev, address_and_data, k);
+
+ #else
+ for (j = 0; j < 16; j++)
+ {
+ // Arm has oppose upper 8 bits and lower 8 bits, needs to be reverted
+ lower = p_state->instruct[i*16 + j];
+ upper = p_state->instruct[i*16 + j]>>8;
+
+ //printk(KERN_INFO "page=%d, offset=%d, instruction= %x, data= %x\n", i, j, upper, lower);
+
+ lm8502_i2c_write_reg(p_state->i2c_dev, (PROG_MEM_START+(2*j)%32), upper);
+ lm8502_i2c_write_reg(p_state->i2c_dev, (PROG_MEM_START+(2*j+1)%32), lower);
+
+ }
+ #endif
+
+ }
+
+#ifdef AUTO_INCR_WRITE
+ // Disable the serial bus address auto increment
+ lm8502_i2c_write_reg(p_state->i2c_dev, CONFIG, reg);
+#endif
+
+
+ // Run the program
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL1, ENGINE_CNTRL1_FREERUN);
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL2, ENGINE_CNTRL2_RUN);
+
+ // allow the engine time to start before processing the next request
+ msleep(1);
+
+ return ret;
+}
+
+static int lm8502_eng_to_bitmask(int eng)
+{
+ if(eng == 1)
+ return 1;
+ else if(eng == 2)
+ return 2;
+ else
+ return 0;
+}
+
+static int lm8502_stop_engine(struct lm8502_device_state *p_state, int eng)
+{
+ int ret=0;
+
+ // hold execution and disable the engine
+ // NOTE: The user space must make sure all LEDs are put back into the proper state
+ // if an engine was terminated before it finished.
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL1, ENGINE_CNTRL1_HOLD);
+ lm8502_config_engine_control_mode(p_state, eng, ENGINE_CNTRL2, ENGINE_CNTRL2_DISABLE);
+
+ // allow the engine time to stop before processing the next request
+ msleep(1);
+
+ p_state->stop_engine_state |= lm8502_eng_to_bitmask(eng);
+
+ if(p_state->stop_engine_state > 0)
+ {
+ wake_up_interruptible(&lm8502_wait_stop_engine);
+ }
+
+ return ret;
+}
+
+static void lm8502_process_command(struct work_struct *work)
+{
+ struct engine_cmd *command = container_of(work, struct engine_cmd, workitem);
+
+ mutex_lock(&command->context->lock);
+
+ switch(command->cmd)
+ {
+ case LM8502_START_ENGINE:
+ lm8502_start_engine(command->context, command->engine);
+ break;
+ case LM8502_STOP_ENGINE:
+ lm8502_stop_engine(command->context, command->engine);
+ break;
+ default:
+ break;
+ }
+
+ mutex_unlock(&command->context->lock);
+
+ kfree(command);
+}
+
+static void lm8502_allocate_workitem(struct lm8502_device_state *context, int engine, int cmd)
+{
+ struct engine_cmd *newcmd = kzalloc(sizeof(struct engine_cmd), GFP_KERNEL);
+
+ newcmd->context = context;
+ newcmd->engine = engine;
+ newcmd->cmd = cmd;
+
+ INIT_WORK(&newcmd->workitem, lm8502_process_command);
+ schedule_work(&newcmd->workitem);
+}
+
+static void lm8502_mod_brightness(struct work_struct *work)
+{
+ struct lm8502_led_config *led_config =
+ container_of(work, struct lm8502_led_config, brightness_work);
+ struct lm8502_device_state *state = i2c_get_clientdata(led_config->client);
+
+ int pwm_value;
+ int i = 0;
+
+ if(lm8502_convert_percent_to_pwm(led_config->brightness, &pwm_value) != 0)
+ {
+ printk(KERN_ERR "lm8502_mod_brightness: invalid brightness value (%d)\n", led_config->brightness);
+ return;
+ }
+
+ mutex_lock(&state->lock);
+
+ switch(led_config->hw_group)
+ {
+ case LED_HW_GRP_NONE:
+ // Loop through each led within group_id
+ for(i = 0; i < led_config->nleds; i++)
+ {
+ lm8502_i2c_write_reg(led_config->client, led_config->led_list[i].current_addr, pwm_value);
+ printk(KERN_DEBUG"lm8502_mod_brightness: set LED%d brightness (%d) percent\n",
+ (led_config->led_list[i].current_addr - 0x25), led_config->brightness);
+ }
+ break;
+ /* HW Groups can change brightness for all LEDs with a single command */
+ case LED_HW_GRP_1:
+ lm8502_i2c_write_reg(led_config->client, GROUP_FADER1, pwm_value);
+ break;
+ case LED_HW_GRP_2:
+ lm8502_i2c_write_reg(led_config->client, GROUP_FADER2, pwm_value);
+ break;
+ case LED_HW_GRP_3:
+ lm8502_i2c_write_reg(led_config->client, GROUP_FADER3, pwm_value);
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&state->lock);
+ return;
+}
+
+/* Program current control register, ramp registers, blink
+ * registers, and then turn on/off the LED regulators.
+ */
+static void lm8502_set_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct lm8502_led_config *led_config;
+ struct lm8502_platform_data *pdata;
+ struct lm8502_led_config *leds = NULL;
+
+ struct i2c_client *client;
+
+ /* Get the instance of LED configuration block. */
+ led_config = container_of(led_cdev, struct lm8502_led_config, cdev);
+
+ pdata = led_cdev->dev->platform_data;
+ leds = pdata->leds;
+
+ /* Sanity check. We can't be here without these set up correctly. */
+ if ((!led_config) || (!led_config->client)) {
+ printk(KERN_ERR "%s: Invalid LED. Can't set led brightness.\n",
+ LM8502_I2C_DRIVER);
+ return;
+ }
+
+ /* Get the handle to the I2C client. */
+ client = led_config->client;
+
+ /* Change the brightness */
+ led_config->brightness = value;
+ schedule_work(&led_config->brightness_work);
+
+ return;
+}
+
+static void lm8502_vib_enable(struct timed_output_dev *dev, int value)
+{
+ struct lm8502_device_state *vib = container_of(dev,
+ struct lm8502_device_state, timed_dev);
+ unsigned long flags;
+
+ hrtimer_cancel(&vib->vib_timer);
+ spin_lock_irqsave(&vib->spinlock, flags);
+#ifdef DEBUG
+ printk("%s(parent:%s): vibrates %d msec\n",
+ current->comm, current->parent->comm, value);
+#endif
+ if (value == 0)
+ vib->state = 0;
+ else {
+ vib->state = 1;
+ hrtimer_start(&vib->vib_timer,
+ ktime_set(value / 1000, (value % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ }
+ spin_unlock_irqrestore(&vib->spinlock, flags);
+ queue_work_on(0, lm8502_vib_wq, &vib->work);
+}
+
+static int lm8502_vib_set(struct lm8502_device_state *vib, int on)
+{
+ int rc;
+ if (on) {
+ rc =lm8502_i2c_write_reg(vib->i2c_dev, HAPTIC_FEEDBACK_CTRL,
+ 0x02 + !!(vib->pdata.vib_invert_direction ^ vib->vib_direction));
+ } else {
+ rc = lm8502_i2c_write_reg(vib->i2c_dev, HAPTIC_FEEDBACK_CTRL, 0);
+ }
+ return rc;
+}
+
+static void lm8502_vib_update(struct work_struct *work)
+{
+ struct lm8502_device_state *vib = container_of(work,
+ struct lm8502_device_state, work);
+
+ lm8502_vib_set(vib, vib->state);
+}
+
+
+static int lm8502_vib_get_time(struct timed_output_dev *dev)
+{
+ ktime_t r;
+ struct lm8502_device_state *vib = container_of(dev,
+ struct lm8502_device_state, timed_dev);
+
+ if (hrtimer_active(&vib->vib_timer)) {
+ r = hrtimer_get_remaining(&vib->vib_timer);
+#ifdef CONFIG_KTIME_SCALAR
+ return r.tv64;
+#else
+ return r.tv.sec * 1000 + r.tv.nsec / 1000000;
+#endif
+ } else
+ return 0;
+}
+
+static enum hrtimer_restart lm8502_vib_timer_func(struct hrtimer *timer)
+{
+ struct lm8502_device_state *vib = container_of(timer,
+ struct lm8502_device_state, vib_timer);
+#ifdef DEBUG
+ printk("%s\n", __func__);
+#endif
+ vib->state = 0;
+ queue_work_on(0, lm8502_vib_wq, &vib->work);
+ return HRTIMER_NORESTART;
+}
+
+
+static void lm8502_notify(struct work_struct *work)
+{
+ u8 reg;
+ int interrupt = 0;
+ struct lm8502_device_state *p_state =
+ container_of(work, struct lm8502_device_state, notify_work);
+
+ mutex_lock(&p_state->lock);
+
+ // clear interrrupt
+ lm8502_i2c_read_reg(p_state->i2c_dev, STATUS, ®);
+
+ // figure out which engine fired the interrupt
+ if(reg & 0x01)
+ p_state->interrupt_state = 2;
+ else if(reg & 0x02)
+ p_state->interrupt_state = 1;
+ else
+ p_state->interrupt_state = 0;
+
+ interrupt = p_state->interrupt_state;
+
+ if(p_state->interrupt_state > 0)
+ {
+ wake_up_interruptible(&lm8502_wait_queue);
+ }
+
+ if(p_state->stopengine > 0)
+ {
+ // If the user space requested the engine be stopped after the next interrupt
+ // then call lm8502_stop_engine directly and clear the appropriate bits in p_state->stopengine
+ if( (p_state->stopengine & LM8502_STOP_ENGINE1) && (interrupt == 1))
+ {
+ p_state->stopengine = p_state->stopengine &~ LM8502_STOP_ENGINE1;
+ lm8502_allocate_workitem(p_state, 1, LM8502_STOP_ENGINE);
+ }
+ if( (p_state->stopengine & LM8502_STOP_ENGINE2) && (interrupt == 2))
+ {
+ p_state->stopengine = p_state->stopengine &~ LM8502_STOP_ENGINE2;
+ lm8502_allocate_workitem(p_state, 2, LM8502_STOP_ENGINE);
+ }
+ }
+
+ mutex_unlock(&p_state->lock);
+}
+
+static irqreturn_t lm8502_isr(int irq, void *dev_id)
+{
+ struct lm8502_device_state *state = dev_id;
+ schedule_work(&state->notify_work);
+ return IRQ_HANDLED;
+}
+
+static void select_flash(struct i2c_client *client)
+{
+ //LED10 as switch between flash and vibrator
+ lm8502_i2c_write_reg(client, D10_CURRENT_CTRL, 0xFF);
+}
+
+static void select_vibrator(struct i2c_client *client)
+{
+ //LED10 as switch between flash and vibrator
+ lm8502_i2c_write_reg(client, D10_CURRENT_CTRL, 0);
+}
+
+
+static long lm8502_ioctl(struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ struct lm8502_device_state *p_state = container_of(fp->f_op, struct lm8502_device_state, fops);
+ struct lm8502_led_config *leds = NULL;
+ int i,j;
+ u8 reg;
+
+ if(!p_state)
+ {
+ printk(KERN_ERR "lm8502_ioctl: invalid context\n");
+ return -EINVAL;
+ }
+
+ switch(cmd)
+ {
+ case LM8502_READ_PWM:
+ {
+ struct lm8502_read_pwm pwm;
+ u8 addr;
+ if(copy_from_user(&pwm, (struct lm8502_read_pwm *)arg, sizeof(struct lm8502_read_pwm)))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_READ_PWM parameter is invalid\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ addr = pwm.led + D1_CURRENT_CTRL - 1;
+ if((addr < D1_CURRENT_CTRL) || (addr > D10_CURRENT_CTRL))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_READ_PWM addr is out of range\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ lm8502_i2c_read_reg(p_state->i2c_dev, addr, &pwm.value);
+
+ if(copy_to_user((void *)arg, &pwm, sizeof(struct lm8502_read_pwm)))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_READ_PWM failed to copy result\n");
+ ret = -EINVAL;
+ }
+
+ break;
+
+ }
+
+ case LM8502_DOWNLOAD_MICROCODE:
+ mutex_lock(&p_state->lock);
+ if(copy_from_user(&p_state->instruct, (u16 *)arg, sizeof(u16)*INSTR_LEN))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_DOWNLOAD_MICROCODE parameter is invalid\n");
+ ret = -EINVAL;
+ }
+ mutex_unlock(&p_state->lock);
+ break;
+
+ case LM8502_START_ENGINE:
+ case LM8502_STOP_ENGINE:
+ if ((u8)arg == 1 || (u8)arg == 2)
+ lm8502_allocate_workitem(p_state, (int)arg, cmd);
+ else
+ ret = -EINVAL;
+ break;
+ case LM8502_CONFIG_MAX_CURRENT:
+ if ((u8)arg > 4)
+ ret = -EINVAL;
+ /* Set current full-scale setting for LED output*/
+ for (i = 0,leds = p_state->pdata.leds; i < p_state->pdata.nleds; i++)
+ for (j = 0; j < leds[i].nleds; j++)
+ {
+ lm8502_i2c_read_reg(p_state->i2c_dev, leds[i].led_list[j].control_addr, ®);
+ reg = reg & (~0x18); //clear bit[4:3]
+ reg = reg | ((u8)arg << 3); //set bit[4:3]
+ lm8502_i2c_write_reg(p_state->i2c_dev, leds[i].led_list[j].control_addr, reg);
+ }
+ break;
+ case LM8502_STOP_ENGINE_AFTER_INTERRUPT:
+ // lm8502 will stop the engine execution if stopengine > 0
+ mutex_lock(&p_state->lock);
+ switch((u8)arg)
+ {
+ case 1:
+ p_state->stopengine |= LM8502_STOP_ENGINE1;
+ break;
+ case 2:
+ p_state->stopengine |= LM8502_STOP_ENGINE2;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ mutex_unlock(&p_state->lock);
+ break;
+
+ case LM8502_WAIT_FOR_INTERRUPT:
+ wait_event_interruptible(lm8502_wait_queue, p_state->interrupt_state > 0);
+
+ mutex_lock(&p_state->lock);
+ if(copy_to_user((void *)arg, &p_state->interrupt_state, sizeof(int)))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_WAIT_FOR_INTERRUPT parameter is invalid\n");
+ ret = -EINVAL;
+ }
+
+ // clear internal interrupt state
+ p_state->interrupt_state = 0;
+ mutex_unlock(&p_state->lock);
+ break;
+
+ case LM8502_WAIT_FOR_ENGINE_STOPPED:
+ wait_event_interruptible(lm8502_wait_stop_engine, p_state->stop_engine_state > 0);
+
+ mutex_lock(&p_state->lock);
+ if(copy_to_user((void *)arg, &p_state->stop_engine_state, sizeof(int)))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_WAIT_FOR_ENGINE_STOPPED parameter is invalid\n");
+ ret = -EINVAL;
+ }
+
+ // clear internal interrupt state
+ p_state->stop_engine_state = 0;
+ mutex_unlock(&p_state->lock);
+ break;
+
+ case LM8502_CONFIGURE_MEMORY:
+ mutex_lock(&p_state->lock);
+ if(copy_from_user(p_state->pdata.memcfg, (void *)arg, sizeof(struct lm8502_memory_config)))
+ {
+ printk(KERN_ERR "ERROR: lm8502_ioctl: LM8502_CONFIGURE_MEMORY parameter is invalid\n");
+ ret = -EINVAL;
+ }
+ mutex_unlock(&p_state->lock);
+ break;
+
+ default:
+ break;
+ }
+
+done:
+ return ret;
+
+}
+
+static int lm8502_open(struct inode *ip, struct file *fp)
+{
+ struct lm8502_device_state * p_state;
+ int ret = 0;
+
+ p_state = container_of(fp->f_op, struct lm8502_device_state, fops);
+
+ if(!p_state)
+ {
+ printk("ERROR: lm8502_open: p_state is bad\n");
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct file_operations lm8502_fops = {
+ .open = lm8502_open,
+ .unlocked_ioctl = lm8502_ioctl,
+};
+
+int debug_reg_open(struct inode *inode, struct file *file)
+{
+ file->private_data = inode->i_private;
+ return 0;
+}
+
+ssize_t debug_reg_read(struct file *file, char __user *userbuf,
+ size_t count, loff_t *ppos)
+{
+ int rc, ret;
+ u8 addr, value;
+ u8 pdata[255];
+ char *buf;
+ char *tmpbuf = NULL;
+ int line, rest;
+ int i, j;
+ struct lm8502_device_state *p_state;
+
+ p_state = file->private_data;
+ if (p_state == NULL) {
+ printk(KERN_ERR "debug_reg_read,%x", (int)p_state);
+ return -EINVAL;
+ }
+
+ /* Alloc memory */
+ buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!buf) {
+ ret = -ENOMEM;
+ goto done;
+ }
+ memset(buf, 0, PAGE_SIZE);
+
+ addr = 0x00;
+ /* Read 0xF1 registers */
+ value = 0xF1;
+ pdata[0] = 1;
+
+ for (i = 0; i < value; i++) {
+ rc = lm8502_i2c_read_reg(p_state->i2c_dev, addr+i, pdata+1+i);
+ if (rc != 2) {
+ pdata[0] = 0;
+ break;
+ }
+ }
+
+ if (pdata[0] == 0) {
+ ret = -EINVAL;
+ goto done;
+ } else {
+ ret = 0;
+ line = value / 0x10;
+ rest = value % 0x10;
+
+ if (line > 0) {
+ for (i = 0; i < line; i++) {
+ ret += snprintf(buf + ret, \
+ PAGE_SIZE - ret, "reg%02x--%02x: ", \
+ addr + i*0x10, addr + (i+1)*0x10 - 1);
+
+ for (j = 0; j < 0x10; j++) {
+ ret += snprintf(buf + ret, \
+ PAGE_SIZE - ret, \
+ "%02x ", pdata[i*0x10+j+1]);
+ }
+
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, \
+ "\n");
+ }
+ }
+
+ if (rest == 1) {
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, \
+ "reg%02x: %02x", line*0x10 + addr, \
+ pdata[line*0x10+1]);
+ } else if (rest > 1) {
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, \
+ "reg%02x--%02x: ", addr + line*0x10, \
+ addr + count-1);
+ for (i = 0; i < rest; i++) {
+ ret += snprintf(buf + ret, \
+ PAGE_SIZE - ret, \
+ "%02x ", pdata[i+1]);
+ }
+ }
+
+ ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
+ }
+
+ ret = simple_read_from_buffer(userbuf, count, ppos, buf, ret);
+done:
+ kfree(buf);
+ kfree(tmpbuf);
+ return ret;
+}
+
+ssize_t debug_reg_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ int rc, ret;
+ u8 addr, value;
+ struct lm8502_device_state *p_state;
+
+ if (!ubuf) {
+ printk(KERN_ERR "debug_reg_write buf=%s", ubuf);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ p_state = file->private_data;
+ if (p_state == NULL) {
+ printk(KERN_ERR "debug_reg_write p_state=%x", (int)p_state);
+ return -EINVAL;
+ }
+
+ addr = p_state->addr;
+ value = p_state->value;
+
+ if ((addr < 0) || (addr > 0xF0)) {
+ printk(KERN_ERR "ERROR:debug_reg_write addr is out of range\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ rc = lm8502_i2c_write_reg(p_state->i2c_dev, addr, value);
+ if (rc != 1)
+ ret = -EINVAL;
+ else
+ ret = 1;
+done:
+ return ret;
+}
+
+static const struct file_operations reg_fops = {
+ .owner = THIS_MODULE,
+ .open = debug_reg_open,
+ .read = debug_reg_read,
+ .write = debug_reg_write,
+};
+
+static int lm8502_i2c_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
+{
+ struct lm8502_device_state *state = NULL;
+ struct lm8502_platform_data *pdata = client->dev.platform_data;
+ struct lm8502_led_config *leds = NULL;
+ int i, j, ret;
+ u8 index;
+ u8 reg;
+
+ printk(KERN_DEBUG"LM8502 probe called\n");
+
+ /* Check the platform data. */
+ if (pdata == NULL) {
+ printk(KERN_ERR "%s: missing platform data\n",
+ LM8502_I2C_DRIVER);
+ return -ENODEV;
+ }
+
+ /* Create the device state */
+ state = kzalloc(sizeof(struct lm8502_device_state), GFP_KERNEL);
+ if (!state) {
+ return -ENOMEM;
+ }
+
+ /* Attach i2c_dev */
+ state->i2c_dev = client;
+
+ /* Attach driver data. */
+ i2c_set_clientdata(client, state);
+ lm8502_state = state;
+
+ /* Attach platform data */
+ memcpy(&state->pdata, pdata, sizeof(struct lm8502_platform_data));
+
+ /*init the vibrator or Flash select functions*/
+ state->pdata.select_flash = select_flash;
+ state->pdata.select_vibrator = select_vibrator;
+
+ /* Init workq */
+ INIT_WORK(&state->notify_work, lm8502_notify);
+
+ /* Init mutex */
+ mutex_init(&state->lock);
+
+ /* Default interrupt state */
+ state->interrupt_state = 0;
+ state->stop_engine_state = 0;
+ state->stopengine = 0;
+
+ /* Enable interrupt */
+ ret = gpio_request(pdata->interrupt_gpio, "LM8502 interrupt");
+ if (ret != 0) {
+ printk(KERN_ERR "LM8502: Failed to get GPIO for interrupt line.\n");
+ goto err0;
+ }
+ ret = gpio_direction_input(pdata->interrupt_gpio);
+ if (ret != 0) {
+ printk(KERN_ERR "LM8502: Failed to set interrupt line for input.\n");
+ goto err1;
+ }
+ ret = request_irq(gpio_to_irq(pdata->interrupt_gpio), lm8502_isr,
+ IRQF_TRIGGER_FALLING, pdata->dev_name, (void *)state);
+ if (ret < 0)
+ goto err1;
+
+
+ /* Enable the chip by setting the correct GPIO pin */
+ ret = gpio_request(pdata->enable_gpio, "LM8502 enable");
+ if (ret != 0) {
+ printk(KERN_ERR "LM8502: Failed to get GPIO for enable line. Error code = %d\n", ret);
+ goto err2;
+ }
+ ret = gpio_direction_output(pdata->enable_gpio, 1);
+ if (ret != 0) {
+ printk(KERN_ERR "LM8502: Failed to set enable line for output. Error code = %d\n", ret);
+ goto err3;
+ }
+ gpio_set_value(pdata->enable_gpio, 1);
+
+ // Reset lm8502 when power on
+ lm8502_i2c_write_reg(client, RESET, 0xFF );
+ mdelay(50);
+
+ /* Enable the chip in software by flipping the CHIP_EN bit*/
+ lm8502_i2c_read_reg(client, ENGINE_CNTRL1, ®);
+ lm8502_i2c_write_reg(client, ENGINE_CNTRL1, (reg|0x40) );
+
+ /* Configure power savings mode and enable boost,select pwm input*/
+ lm8502_i2c_read_reg(client, MISC, ®);
+ lm8502_i2c_write_reg(client, MISC, reg | (pdata->power_mode) | (1<<3) | (1<<1));
+
+ /* Create misc device */
+ memcpy(&state->fops, &lm8502_fops, sizeof(struct file_operations));
+ state->mdev.name = "lm8502";
+ state->mdev.minor = MISC_DYNAMIC_MINOR;
+ state->mdev.fops = &state->fops;
+
+ spin_lock_init(&state->spinlock);
+ INIT_WORK(&state->work, lm8502_vib_update);
+ lm8502_vib_wq = create_singlethread_workqueue("lm8502_vib_wq");
+ if (!lm8502_vib_wq) {
+ printk("%s: create_singlethread_workqueue failed\n", __func__);
+ }
+
+ hrtimer_init(&state->vib_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ state->vib_timer.function = lm8502_vib_timer_func;
+
+ state->timed_dev.name = "vibrator";
+ state->timed_dev.get_time = lm8502_vib_get_time;
+ state->timed_dev.enable = lm8502_vib_enable;
+
+ if(timed_output_dev_register(&state->timed_dev) < 0)
+ printk(KERN_ERR "LM8502: unable to register timed_output dev\n");
+
+
+ ret = misc_register(&state->mdev);
+ if(ret) {
+ printk(KERN_ERR "LM8502: Failed to create /dev/lm8502\n");
+ goto err4;
+ }
+
+
+ /* Register to led class, process platform data and assign default values*/
+ for (i = 0,leds = pdata->leds; i < pdata->nleds; i++)
+ {
+ /* check to see if this group can be put into a single hw group */
+ if(leds[i].hw_group != LED_HW_GRP_NONE)
+ {
+ /* assign hardware groups to each led in the group */
+ for(j = 0; j < leds[i].nleds; j++)
+ {
+ lm8502_i2c_read_reg(client, leds[i].led_list[j].control_addr, ®);
+ reg = reg & (~0xC0); //clear bit[7:6]
+ reg = reg | (leds[i].hw_group << 6); //set bit[7:6]
+ lm8502_i2c_write_reg(client, leds[i].led_list[j].control_addr, reg);
+ }
+
+ }
+
+ /* Set current full-scale setting for LED output*/
+ for(j = 0; j < leds[i].nleds; j++)
+ {
+ lm8502_i2c_read_reg(client, leds[i].led_list[j].control_addr, ®);
+ reg = reg & (~0x18); //clear bit[4:3]
+ reg = reg | (leds[i].default_max_current << 3); //set bit[4:3]
+ lm8502_i2c_write_reg(client, leds[i].led_list[j].control_addr, reg);
+ }
+
+ INIT_WORK(&leds[i].brightness_work, lm8502_mod_brightness);
+
+ if(leds[i].default_state == LED_ON)
+ {
+ printk(KERN_DEBUG"LM8502: Turning %s on to default %d duty cycle\n",
+ leds[i].cdev.name, leds[i].default_brightness);
+
+ leds[i].cdev.brightness = leds[i].default_brightness;
+ leds[i].brightness = leds[i].default_brightness;
+ schedule_work(&leds[i].brightness_work);
+ }
+
+ leds[i].cdev.brightness_set = lm8502_set_brightness;
+
+ /* Save the i2c client information for each led. */
+ leds[i].client = client;
+
+ /* Register this LED instance to the LED class. */
+ ret = led_classdev_register(&client->dev, &leds[i].cdev);
+ if (ret < 0)
+ goto err5;
+ }
+
+ /* Create sysfs entries for the vibrator portion of the part */
+ state->vib_class = class_create(THIS_MODULE, "vibrator");
+ if (!state->vib_class){
+ ret = -ENOMEM;
+ printk(KERN_ERR "Unable to create vibrator class node\n");
+ goto err6;
+ }
+
+ state->vib_class_dev = device_create(state->vib_class, &client->dev, 0, state, "vib0");
+ if (!state->vib_class_dev) {
+ ret = -ENOMEM;
+ printk(KERN_ERR "Unable to create vibrator class device node\n");
+ goto err7;
+ }
+
+ if ((ret = device_create_file(state->vib_class_dev, &dev_attr_vib_enable)) < 0) {
+ printk(KERN_ERR "Unable to create vibrator enable node\n");
+ goto err8;
+ }
+
+ if ((ret = device_create_file(state->vib_class_dev, &dev_attr_vib_duty_cycle)) < 0) {
+ printk(KERN_ERR "Unable to create vibrator duty cycle node\n");
+ goto err9;
+ }
+
+ if ((ret = device_create_file(state->vib_class_dev, &dev_attr_vib_direction)) < 0) {
+ printk(KERN_ERR "Unable to create vibrator direction node\n");
+ goto err10;
+ }
+
+ if ((ret = device_create_file(state->vib_class_dev, &dev_attr_vib_start)) < 0) {
+ printk(KERN_ERR "Unable to create vibrator start node\n");
+ goto err11;
+ }
+
+ /* Program the default vibrator settings as specified in the board file */
+ state->vib_direction = pdata->vib_default_direction;
+ state->vib_duty_cycle = pdata->vib_default_duty_cycle;
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_PWM_DUTY_CYCLE, (state->vib_duty_cycle * 255) / 100);
+
+ /* Create sysfs entries for the flash portion of the part */
+ state->flash_class = class_create(THIS_MODULE, "flash");
+ if (!state->flash_class){
+ ret = -ENOMEM;
+ printk(KERN_ERR "Unable to create flash class node\n");
+ goto err12;
+ }
+
+ state->flash_class_dev = device_create(state->flash_class, &client->dev, 0, state, "flash0");
+ if (!state->flash_class_dev) {
+ ret = -ENOMEM;
+ printk(KERN_ERR "Unable to create flash class device node\n");
+ goto err13;
+ }
+
+ if ((ret = device_create_file(state->flash_class_dev, &dev_attr_flash_enable)) < 0) {
+ printk(KERN_ERR "Unable to create flash current node\n");
+ goto err14;
+ }
+
+ if ((ret = device_create_file(state->flash_class_dev, &dev_attr_flash_current)) < 0) {
+ printk(KERN_ERR "Unable to create flash current node\n");
+ goto err15;
+ }
+
+ if ((ret = device_create_file(state->flash_class_dev, &dev_attr_flash_duration)) < 0) {
+ printk(KERN_ERR "Unable to create flash duration node\n");
+ goto err16;
+ }
+
+ if ((ret = device_create_file(state->flash_class_dev, &dev_attr_torch_current)) < 0) {
+ printk(KERN_ERR "Unable to create flash duration node\n");
+ goto err17;
+ }
+
+ if ((ret = device_create_file(state->flash_class_dev, &dev_attr_flash_or_torch_start)) < 0) {
+ printk(KERN_ERR "Unable to create flash fire node\n");
+ goto err18;
+ }
+
+ /* Program the default flash settings as specified in the board file */
+ index = lm8502_get_closest_flash_current(pdata->flash_default_current);
+ state->flash_current = flash_brightness_map[index].current_value;
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_BRIGHTNESS, STROBE_TIMEOUT | (flash_brightness_map[index].code << 3));
+
+ state->flash_duration = pdata->flash_default_duration;
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_DURATION, state->flash_duration >> 5);
+
+ index = lm8502_get_closest_torch_current(pdata->torch_default_current);
+ state->torch_current = torch_brightness_map[index].current_value;
+ lm8502_i2c_write_reg(state->i2c_dev, TORCH_BRIGHTNESS, torch_brightness_map[index].code << 3);
+
+ /* Create debugfs entries. */
+ state->debug_dir = debugfs_create_dir("lm8502", NULL);
+ if ((int)(state->debug_dir) == -ENODEV) {
+ /* debugfs is not enabled. */
+ printk(KERN_WARNING "debugfs not enabled in kernel\n");
+
+ } else if (state->debug_dir == NULL) {
+ printk(KERN_WARNING "error creating debugfs dir\n");
+
+ } else {
+ debugfs_create_x8("addr", S_IRUSR|S_IWUSR, \
+ state->debug_dir, &(state->addr));
+ debugfs_create_x8("value", S_IRUSR|S_IWUSR, \
+ state->debug_dir, &(state->value));
+ debugfs_create_file("lm8502_reg", 0666, \
+ state->debug_dir, state, ®_fops);
+ }
+
+ return 0;
+
+err18:
+ device_remove_file(state->flash_class_dev, &dev_attr_torch_current);
+err17:
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_duration);
+err16:
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_current);
+err15:
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_enable);
+err14:
+ device_unregister(state->flash_class_dev);
+err13:
+ class_destroy(state->flash_class);
+err12:
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_start);
+err11:
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_direction);
+err10:
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_duty_cycle);
+err9:
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_enable);
+err8:
+ device_unregister(state->vib_class_dev);
+err7:
+ class_destroy(state->vib_class);
+err6:
+err5:
+ /* Unregister previously created attribute files and return error. */
+ if (i > 1)
+ {
+ for (i = i - 1; i >= 0; i--)
+ {
+ led_classdev_unregister(&leds[i].cdev);
+ }
+ }
+err4:
+ misc_deregister(&state->mdev);
+err3:
+ gpio_free(pdata->enable_gpio);
+err2:
+ free_irq(gpio_to_irq(pdata->interrupt_gpio), state);
+err1:
+ gpio_free(pdata->interrupt_gpio);
+err0:
+ kfree(state);
+
+ return (ret);
+}
+
+static int lm8502_i2c_remove(struct i2c_client *client)
+{
+ struct lm8502_device_state *state = i2c_get_clientdata(client);
+ struct lm8502_platform_data *pdata = client->dev.platform_data;
+ struct lm8502_led_config *leds = NULL;
+ int i;
+
+ cancel_work_sync(&state->notify_work);
+
+ cancel_work_sync(&state->work);
+ hrtimer_cancel(&state->vib_timer);
+ timed_output_dev_unregister(&state->timed_dev);
+
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_or_torch_start);
+ device_remove_file(state->flash_class_dev, &dev_attr_torch_current);
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_duration);
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_current);
+ device_remove_file(state->flash_class_dev, &dev_attr_flash_enable);
+ device_unregister(state->flash_class_dev);
+ class_destroy(state->flash_class);
+
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_start);
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_direction);
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_duty_cycle);
+ device_remove_file(state->vib_class_dev, &dev_attr_vib_enable);
+ device_unregister(state->vib_class_dev);
+ class_destroy(state->vib_class);
+
+ debugfs_remove_recursive(state->debug_dir);
+
+#ifdef CONFIG_LEDS_LM8502_DBG
+ lm8502_dbg_unregister_i2c_client(state->i2c_dev);
+#endif // CONFIG_LEDS_LM8502_DBG
+
+ if (pdata) {
+ leds = pdata->leds;
+ for (i = 0; i < pdata->nleds; i++)
+ led_classdev_unregister(&leds[i].cdev);
+ }
+
+ misc_deregister(&state->mdev);
+
+ gpio_free(pdata->enable_gpio);
+ free_irq(gpio_to_irq(pdata->interrupt_gpio), state);
+ gpio_free(pdata->interrupt_gpio);
+
+ i2c_set_clientdata(client, NULL);
+
+ kfree(state);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static u8 enable_reg = 0;
+static int lm8502_i2c_suspend(struct i2c_client *client, pm_message_t pm_state)
+{
+ struct lm8502_device_state *state = i2c_get_clientdata(client);
+ struct lm8502_platform_data *pdata = client->dev.platform_data;
+ struct lm8502_led_config *leds = NULL;
+ u8 reg;
+ int i;
+
+ if (state->suspended) {
+ return 0;
+ }
+
+ /* cancel any pending work */
+ cancel_work_sync(&state->notify_work);
+
+ hrtimer_cancel(&state->vib_timer);
+ cancel_work_sync(&state->work);
+
+ if (pdata) {
+ leds = pdata->leds;
+ for (i = 0; i < pdata->nleds; i++)
+ led_classdev_suspend(&leds[i].cdev);
+ }
+
+ if (state->vib_start)
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_FEEDBACK_CTRL, 0);
+
+ if (state->flash_start && !state->is_flash_mode) {
+ lm8502_i2c_read_reg(state->i2c_dev, FLASH_BRIGHTNESS, ®);
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_BRIGHTNESS, reg & ~0x03);
+ }
+
+ lm8502_i2c_read_reg(state->i2c_dev, ENGINE_CNTRL1, &enable_reg);
+ if(((enable_reg & (3 << 4)) == ENGINE_CNTRL1_HOLD) && ((enable_reg & (3 << 2)) == ENGINE_CNTRL1_HOLD) ){
+ // Disable chip on suspend
+ lm8502_i2c_read_reg(state->i2c_dev, ENGINE_CNTRL1, &enable_reg);
+ lm8502_i2c_write_reg(state->i2c_dev, ENGINE_CNTRL1, 0);
+ }
+
+ state->suspended = 1;
+
+ return 0;
+}
+
+static int lm8502_i2c_resume(struct i2c_client *client)
+{
+ struct lm8502_device_state *state = i2c_get_clientdata(client);
+ struct lm8502_platform_data *pdata = client->dev.platform_data;
+ struct lm8502_led_config *leds = NULL;
+ u8 reg;
+ int i;
+
+ if (!state->suspended) {
+ return 0;
+ }
+
+ if((((enable_reg & (3 << 4)) == ENGINE_CNTRL1_HOLD) && ((enable_reg & (3 << 2)) == ENGINE_CNTRL1_HOLD) )){
+ //Enable chip and set to voltage mode on resume
+ lm8502_i2c_write_reg(state->i2c_dev, ENGINE_CNTRL1, (enable_reg));
+ lm8502_i2c_write_reg(state->i2c_dev, TORCH_BRIGHTNESS, 0x4);
+ }
+
+ if (pdata) {
+ leds = pdata->leds;
+ for (i = 0; i < pdata->nleds; i++)
+ led_classdev_resume(&leds[i].cdev);
+ }
+
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_PWM_DUTY_CYCLE, (state->vib_duty_cycle * 255) / 100);
+
+ if (state->vib_start)
+ lm8502_i2c_write_reg(state->i2c_dev, HAPTIC_FEEDBACK_CTRL, 0x02 + state->vib_direction);
+
+ if (state->flash_start && !state->is_flash_mode) {
+ lm8502_i2c_read_reg(state->i2c_dev, FLASH_BRIGHTNESS, ®);
+ lm8502_i2c_write_reg(state->i2c_dev, FLASH_BRIGHTNESS, reg | TORCH_MODE);
+ }
+
+ state->suspended = 0;
+
+ return 0;
+}
+#else
+#define lm8502_i2c_suspend NULL
+#define lm8502_i2c_resume NULL
+#endif
+
+static const struct i2c_device_id lm8502_ids[] = {
+ {LM8502_I2C_DRIVER, },
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, lm8502_ids);
+
+static struct i2c_driver lm8502_i2c_driver = {
+ .driver = {
+ .name = LM8502_I2C_DRIVER,
+ .owner = THIS_MODULE,
+ },
+ .id_table = lm8502_ids,
+ .probe = lm8502_i2c_probe,
+ .remove = lm8502_i2c_remove,
+ .suspend = lm8502_i2c_suspend,
+ .resume = lm8502_i2c_resume,
+};
+
+static int __init lm8502_module_init(void)
+{
+ int ret;
+
+ printk(KERN_DEBUG "LM8502 module init called\n");
+ ret = i2c_add_driver(&lm8502_i2c_driver);
+ return (ret);
+}
+
+static void __exit lm8502_module_exit(void)
+{
+ i2c_del_driver(&lm8502_i2c_driver);
+}
+
+module_init(lm8502_module_init);
+module_exit(lm8502_module_exit);
+
+MODULE_DESCRIPTION("National LM8502 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lm8502.h b/drivers/leds/leds-lm8502.h
new file mode 100644
index 0000000..9bcad55
--- /dev/null
+++ b/drivers/leds/leds-lm8502.h
@@ -0,0 +1,130 @@
+/*
+ * leds-lm8502.h
+ *
+ * Copyright (C) 2008 Palm Inc,
+ *
+ * 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Authors: Kevin McCray (kevin.mccray@palm.com)
+ * Brian Xiong (brian.xiong@palm.com)
+ * Amon Xie(amon.xie@palm.com)
+ *
+ */
+
+#ifndef _LEDS_LM8502_H
+#define _LEDS_LM8502_H
+
+
+/* LED program engine */
+#define INSTR_LEN 96
+#define LM8502_INSTR_LEN_PER_PAGE 16
+#define LM8502_STOP_ENGINE1 0x01
+#define LM8502_STOP_ENGINE2 0x02
+
+/* LM8502 i2c Registers */
+#define ENGINE_CNTRL1 0x00
+#define ENGINE_CNTRL2 0x01
+#define GROUP_FADING1 0x02
+#define GROUP_FADING2 0x03
+#define GROUP_FADING3 0x04
+
+#define D1_CONTROL 0x06
+#define D2_CONTROL 0x07
+#define D3_CONTROL 0x08
+#define D4_CONTROL 0x09
+#define D5_CONTROL 0x0A
+#define D6_CONTROL 0x0B
+#define D7_CONTROL 0x0C
+#define D8_CONTROL 0x0D
+#define D9_CONTROL 0x0E
+#define D10_CONTROL 0x0F
+
+#define HAPTIC_CONTROL 0x10
+
+#define ALS_CONTROL 0x11
+#define ZLINE0 0x12
+#define ZLINE1 0x13
+#define ZLINE2 0x14
+#define ZLINE3 0x15
+#define TARGET_LIGHT_Z0 0x16
+#define TARGET_LIGHT_Z1 0x17
+#define TARGET_LIGHT_Z2 0x18
+#define TARGET_LIGHT_Z3 0x19
+#define TARGET_LIGHT_Z4 0x1A
+#define ALS_START_VALUE 0x1B
+#define DBC_CONTROL 0x1D
+
+#define HAPTIC_FEEDBACK_CTRL 0x21
+#define HAPTIC_PWM_DUTY_CYCLE 0x22
+
+#define D1_CURRENT_CTRL 0x26
+#define D2_CURRENT_CTRL 0x27
+#define D3_CURRENT_CTRL 0x28
+#define D4_CURRENT_CTRL 0x29
+#define D5_CURRENT_CTRL 0x2A
+#define D6_CURRENT_CTRL 0x2B
+#define D7_CURRENT_CTRL 0x2C
+#define D8_CURRENT_CTRL 0x2D
+#define D9_CURRENT_CTRL 0x2E
+#define D10_CURRENT_CTRL 0x2F
+
+#define ADAPT_FLASH_CTRL 0x35
+#define MISC 0x36
+
+#define ENGINE1_PC 0x37
+#define ENGINE2_PC 0x38
+
+#define STATUS 0x3A
+#define INT 0x3B
+#define I2C_VARIABLE 0x3C
+#define RESET 0x3D
+
+#define LED_TEST_CONTROL 0x41
+#define LED_TEST_ADC 0x42
+
+#define GROUP_FADER1 0x48
+#define GROUP_FADER2 0x49
+#define GROUP_FADER3 0x4A
+
+#define ENG1_PROG_START_ADDR 0x4C
+#define ENG2_PROG_START_ADDR 0x4D
+#define PROG_MEM_PAGE_SELECT 0x4F
+
+#define PROG_MEM_START 0x50
+#define PROG_MEM_END 0x6F
+
+#define TORCH_BRIGHTNESS 0xA0
+#define FLASH_BRIGHTNESS 0xB0
+#define FLASH_DURATION 0xC0
+#define FLAG_REGISTER 0xD0
+#define CONFIG_REG1 0xE0
+#define CONFIG_REG2 0xF0
+
+#define ENGINE_CNTRL_ENG1_SHIFT 4
+#define ENGINE_CNTRL_ENG2_SHIFT 2
+
+#define ENGINE_CNTRL1_HOLD 0
+#define ENGINE_CNTRL1_STEP 1
+#define ENGINE_CNTRL1_FREERUN 2
+#define ENGINE_CNTRL1_EXECONCE 3
+
+#define ENGINE_CNTRL2_DISABLE 0
+#define ENGINE_CNTRL2_LOAD 1
+#define ENGINE_CNTRL2_RUN 2
+#define ENGINE_CNTRL2_HALT 3
+
+#define MISC_POWER_SAVE_ON (1 << 5)
+#define MISC_POWER_SAVE_OFF (0 << 5)
+
+#define STROBE_TIMEOUT (1 << 7)
+
+#define FLASH_MODE (3 << 0)
+#define TORCH_MODE (2 << 0)
+
+#endif // _LEDS_LM8502_H
diff --git a/include/linux/i2c_lm8502_led.h b/include/linux/i2c_lm8502_led.h
new file mode 100644
index 0000000..ceb0dea
--- /dev/null
+++ b/include/linux/i2c_lm8502_led.h
@@ -0,0 +1,128 @@
+/*
+ * include/linux/i2c_lm8502_led.h
+ *
+ * Copyright (C) 2008 Palm Inc,
+ *
+ * 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Authors: Kevin McCray (kevin.mccray@palm.com)
+ * Brian Xiong (brian.xiong@palm.com)
+ * Amon Xie(amon.xie@palm.com)
+ *
+ */
+
+#ifndef _LM8502_H
+#define _LM8502_H
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+
+#define LED_OFF 0
+#define LED_ON 1
+#define MISC_POWER_SAVE_ON (1 << 5)
+#define MISC_POWER_SAVE_OFF (0 << 5)
+
+#define LM8502_I2C_DEVICE "LM8502"
+#define LM8502_I2C_DRIVER "LM8502"
+#define LM8502_I2C_ADDR 0x33
+
+/*ioctl codes */
+#define LM8502_DOWNLOAD_MICROCODE 1
+#define LM8502_START_ENGINE 9
+#define LM8502_STOP_ENGINE 3
+#define LM8502_WAIT_FOR_INTERRUPT 4
+#define LM8502_CONFIGURE_MEMORY 5
+#define LM8502_STOP_ENGINE_AFTER_INTERRUPT 6
+#define LM8502_READ_PWM 7
+#define LM8502_WAIT_FOR_ENGINE_STOPPED 8
+#define LM8502_CONFIG_MAX_CURRENT 10
+
+/* lm8502 LED platform data structure */
+enum {
+ LED_GRP_1 = 0,
+ LED_GRP_2,
+ LED_GRP_3,
+ LED_GRP_4,
+ LED_GRP_5,
+ LED_GRP_6,
+ LED_GRP_7,
+ LED_GRP_8,
+ LED_GRP_9,
+ LED_GRP_10,
+};
+
+enum {
+ LED_HW_GRP_NONE = 0,
+ LED_HW_GRP_1,
+ LED_HW_GRP_2,
+ LED_HW_GRP_3,
+};
+
+enum {
+ LED_NONE = 0,
+ LED_RED,
+ LED_GREEN,
+ LED_BLUE,
+ LED_WHITE,
+};
+
+struct lm8502_read_pwm {
+ u8 led;
+ u8 value;
+};
+
+struct led_cfg {
+ int type;
+ u8 current_addr;
+ u8 control_addr;
+};
+
+
+struct lm8502_memory_config {
+ int eng1_startpage;
+ int eng1_endpage;
+ int eng2_startpage;
+ int eng2_endpage;
+};
+
+struct lm8502_led_config {
+ struct led_classdev cdev;
+ struct i2c_client *client;
+ struct led_cfg *led_list;
+ struct work_struct brightness_work;
+ int nleds;
+ int brightness;
+ int group_id;
+ int hw_group;
+ u8 default_max_current;
+ int default_brightness;
+ int default_state;
+};
+
+struct lm8502_platform_data {
+ int enable_gpio;
+ int interrupt_gpio;
+ int strobe_gpio;
+ u8 vib_default_duty_cycle;
+ u8 vib_default_direction;
+ u8 vib_invert_direction;
+ u16 flash_default_duration;
+ u16 flash_default_current;
+ u16 torch_default_current;
+ void (*select_flash) (struct i2c_client* client);
+ void (*select_vibrator) (struct i2c_client* client);
+ struct lm8502_led_config *leds;
+ struct lm8502_memory_config *memcfg;
+ int nleds;
+ u8 power_mode;
+ char *dev_name;
+};
+
+int lm8502_set_current(uint8_t is_flash_mode, uint32_t mA);
+#endif // _LM8502_H