diff options
Diffstat (limited to 'drivers/leds/leds-lm3535.c')
| -rw-r--r-- | drivers/leds/leds-lm3535.c | 1769 |
1 files changed, 1769 insertions, 0 deletions
diff --git a/drivers/leds/leds-lm3535.c b/drivers/leds/leds-lm3535.c new file mode 100644 index 00000000000..23b7558674e --- /dev/null +++ b/drivers/leds/leds-lm3535.c @@ -0,0 +1,1769 @@ +/* + * Copyright (C) 2013 Motorola Mobility, Inc. + * Author: Alina Yakovleva <qvdh43@motorola.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Linux driver for LM3535 display backlight + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/hrtimer.h> +#include <linux/platform_device.h> +#include <linux/miscdevice.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <asm/io.h> +#include <linux/clk.h> +#include <linux/irq.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/leds.h> +#include <linux/mutex.h> +#include <linux/debugfs.h> +#include <linux/list.h> +#include <asm/gpio.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include "als.h" +#undef CONFIG_HAS_EARLYSUSPEND +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif +#ifdef CONFIG_LM3535_ESD_RECOVERY +#include <mot/esd_poll.h> +#endif /* CONFIG_LM3535_ESD_RECOVERY */ +#include <linux/notifier.h> +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY +#include <linux/als_notify.h> +#include <linux/wakeup_source_notify.h> +#define MIN_DOCK_BVALUE 36 +#include <linux/m4sensorhub.h> +#include <linux/m4sensorhub/MemMapUserSettings.h> +#endif + +#define MODULE_NAME "leds_lm3535" + +/****************************************************************************** + * LM3535 registers + ******************************************************************************/ +#define LM3535_ENABLE_REG 0x10 +#define LM3535_CONFIG_REG 0x20 +#define LM3535_OPTIONS_REG 0x30 +#define LM3535_ALS_REG 0x40 +#define LM3535_ALS_CTRL_REG 0x50 +#define LM3535_ALS_RESISTOR_REG 0x51 +#ifdef CONFIG_LM3535_BUTTON_BL +#define LM3535_ALS_SELECT_REG 0x52 +#endif +#define LM3535_BRIGHTNESS_CTRL_REG_A 0xA0 +#define LM3535_BRIGHTNESS_CTRL_REG_B 0xB0 +#define LM3535_BRIGHTNESS_CTRL_REG_C 0xC0 +#define LM3535_ALS_ZB0_REG 0X60 +#define LM3535_ALS_ZB1_REG 0X61 +#define LM3535_ALS_ZB2_REG 0X62 +#define LM3535_ALS_ZB3_REG 0X63 +#define LM3535_ALS_Z0T_REG 0X70 +#define LM3535_ALS_Z1T_REG 0X71 +#define LM3535_ALS_Z2T_REG 0X72 +#define LM3535_ALS_Z3T_REG 0X73 +#define LM3535_ALS_Z4T_REG 0X74 +#define LM3535_TRIM_REG 0xD0 + +#define ALS_FLAG_MASK 0x08 +#define ALS_ZONE_MASK 0x07 +#define ALS_NO_ZONE 5 +#define LM3535_TRIM_VALUE 0x68 + +#define LM3535_LED_MAX 0x7F // Max brightness value supported by LM3535 + +#define LM3535_HWEN_GPIO 98 + +/* Final revision CONFIG value */ +#define CONFIG_VALUE 0x4C +#define CONFIG_VALUE_NO_ALS 0x0C + +/* ALS Averaging time */ +#define ALS_AVERAGING 0x50 // 1600ms, needs 2 ave. periods + +#define MAX_AMBIENT_BACKLIGHT 36 + +/* Zone boundaries */ +static unsigned als_zb[] = {0x04, 0x42, 0x96, 0xFF}; +module_param_array(als_zb, uint, NULL, 0644); +static unsigned resistor_value = 0x30; +module_param(resistor_value, uint, 0644); +static unsigned pwm_value = 0x1; +module_param(pwm_value, uint, 0644); + +static unsigned als_sleep = 350; +module_param(als_sleep, uint, 0644); +static unsigned ramp_time = 200000; +module_param(ramp_time, uint, 0644); +enum { + TRACE_SUSPEND = 0x1, + TRACE_ALS = 0x2, + TRACE_BRIGHTNESS = 0x4, + TRACE_WRITE = 0x8, + TRACE_EVENT = 0x10, +}; +unsigned do_trace; /* = TRACE_ALS | TRACE_SUSPEND | TRACE_BRIGHTNESS;*/ +module_param(do_trace, uint, 0644); + +#define printk_write(fmt,args...) if (do_trace & TRACE_WRITE) printk(KERN_INFO fmt, ##args) +#define printk_br(fmt,args...) if (do_trace & TRACE_BRIGHTNESS) printk(KERN_INFO fmt, ##args) +#define printk_als(fmt,args...) if (do_trace & TRACE_ALS) printk(KERN_INFO fmt, ##args) +#define printk_suspend(fmt,args...) if (do_trace & TRACE_SUSPEND) printk(KERN_INFO fmt, ##args) +#define printk_event(fmt,args...) if (do_trace & TRACE_EVENT) printk(KERN_INFO fmt, ##args) + +/* ALS callbacks */ +static DEFINE_MUTEX(als_cb_mutex); +static LIST_HEAD(als_callbacks); +struct als_callback { + als_cb cb; + uint32_t cookie; + struct list_head entry; +}; + +struct lm3535_options_register_r1 { + int rs : 2; // Ramp step + int gt : 2; // Gain transition filter + int rev : 2; +}; +/* Ramp times for Rev1 in microseconds */ +static unsigned int lm3535_ramp_r1[] = {51, 13000, 26000, 52000}; + +struct lm3535_options_register_r2 { + int rs_down : 2; + int rs_up : 2; + int gt : 2; +}; +/* Ramp times for Rev2 in microseconds */ +static unsigned int lm3535_ramp_r2[] = {6, 6000, 12000, 24000}; + +struct lm3535_options_register_r3 { + int rs_down : 3; + int rs_up : 3; + int gt : 2; +}; +/* Ramp times for Rev3 in microseconds */ +static unsigned int lm3535_ramp_r3[] = + {6, 770, 1500, 3000, 6000, 12000, 25000, 50000}; + +static void lm3535_send_als_event (int zone); +static char *reg_name (int reg); +static int lm3535_configure (void); +static void lm3535_call_als_callbacks (unsigned old_zone, unsigned zone); +static void lm3535_set_options_r1 (uint8_t *buf, unsigned ramp); +static void lm3535_set_options_r2 (uint8_t *buf, unsigned ramp); +static void lm3535_set_options_r3 (uint8_t *buf, unsigned ramp); +static int lm3535_write_reg (unsigned reg, uint8_t value, const char *caller); +static int lm3535_read_reg (unsigned reg, uint8_t *value); +static int lm3535_set_ramp (struct i2c_client *client, + unsigned int on, unsigned int nsteps, unsigned int *rtime); +static int lm3535_enable(struct i2c_client *client, unsigned int on); +static int lm3535_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int lm3535_setup (struct i2c_client *client); +static int lm3535_remove (struct i2c_client *client); +static void lm3535_work_func (struct work_struct *work); +static void lm3535_allow_als_work_func(struct work_struct *work); +static irqreturn_t lm3535_irq_handler (int irq, void *dev_id); +static void lm3535_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value); + +#ifdef CONFIG_HAS_AMBIENTMODE +static void lm3535_brightness_set_raw_als(struct led_classdev *led_cdev, + unsigned int value); +#endif +/* static unsigned lm3535_read_als_zone (void); */ +#ifdef CONFIG_PM +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lm3535_early_suspend (struct early_suspend *h); +static void lm3535_late_resume (struct early_suspend *h); +static struct early_suspend early_suspend_data = { + .level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 5, + .suspend = lm3535_early_suspend, + .resume = lm3535_late_resume, +}; +#endif +#if defined(CONFIG_HAS_EARLYSUSPEND) || !defined(CONFIG_HAS_AMBIENTMODE) +static int lm3535_suspend (struct i2c_client *client, pm_message_t mesg); +static int lm3535_resume (struct i2c_client *client); +#endif +#endif + +static void (*lm3535_set_options_f)(uint8_t *buf, unsigned ramp) = + lm3535_set_options_r1; +static unsigned int *lm3535_ramp = lm3535_ramp_r1; + +/* LED class struct */ +static struct led_classdev lm3535_led = { + .name = "lcd-backlight", + .brightness_set = lm3535_brightness_set, +#ifdef CONFIG_HAS_AMBIENTMODE + .brightness_set_raw_als = lm3535_brightness_set_raw_als, +#endif +}; + +/* LED class struct for no ramping */ +static struct led_classdev lm3535_led_noramp = { + .name = "lcd-nr-backlight", + .brightness_set = lm3535_brightness_set, +#ifdef CONFIG_HAS_AMBIENTMODE + .brightness_set_raw_als = lm3535_brightness_set_raw_als, +#endif +}; +#ifdef CONFIG_LM3535_BUTTON_BL +static struct led_classdev lm3535_led_button = { + .name = "button-backlight", + .brightness_set = lm3535_button_brightness_set, +#ifdef CONFIG_HAS_AMBIENTMODE + .brightness_set_raw_als = lm3535_brightness_set_raw_als, +#endif +}; +#endif + +static const struct of_device_id of_lm3535_match[] = { + { .compatible = "ti,lm3535", }, + {}, +}; + +static const struct i2c_device_id lm3535_id[] = { + { "lm3535", 0 }, + { } +}; + +#define LM3535_NUM_ZONES 6 +struct lm3535 { + uint16_t addr; + struct i2c_client *client; + struct input_dev *idev; + unsigned initialized; + unsigned enabled; + int use_irq; + int revision; + int nramp; + atomic_t als_zone; // Current ALS zone + atomic_t bright_zone; // Current brightness zone, diff. from ALS + atomic_t use_als; // Whether to use ALS + atomic_t in_suspend; // Whether the driver is in TCMD SUSPEND mode + atomic_t do_als_config; // Whether to configure ALS averaging + unsigned bvalue; // Current brightness register value + unsigned saved_bvalue; // Brightness before TCMD SUSPEND + //struct hrtimer timer; + struct work_struct work; + struct delayed_work als_delayed_work; + int prevent_als_read; /* Whether to prevent als reads for a time */ +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY + atomic_t docked; + atomic_t alsstatus; +#ifdef CONFIG_ALS_WHILE_CHARGING + atomic_t interactive; +#endif + struct notifier_block dock_nb; + struct notifier_block als_nb; +#endif +}; +static DEFINE_MUTEX(lm3535_mutex); + +static struct lm3535 lm3535_data = { + .nramp = 4, + .bvalue = 0x79, +}; + +#if defined CONFIG_PM_SLEEP + +static int lm3535_pm_suspend(struct device *dev) +{ + cancel_delayed_work_sync(&lm3535_data.als_delayed_work); + lm3535_data.prevent_als_read = 0; + + return 0; +} + +static SIMPLE_DEV_PM_OPS(lm3535_pm_ops, lm3535_pm_suspend, NULL); +#define LM3535_PM_OPS (&lm3535_pm_ops) + +#else +#define LM3535_PM_OPS NULL +#endif + +/* This is the I2C driver that will be inserted */ +static struct i2c_driver lm3535_driver = { + .driver = { + .name = "lm3535", + .of_match_table = of_match_ptr(of_lm3535_match), + .pm = LM3535_PM_OPS, + }, + .id_table = lm3535_id, + .probe = lm3535_probe, + .remove = lm3535_remove, +#if !defined(CONFIG_HAS_EARLYSUSPEND) && !defined(CONFIG_HAS_AMBIENTMODE) + .suspend = lm3535_suspend, + .resume = lm3535_resume, +#endif +}; + +#if 0 +unsigned lm3535_read_als_zone (void) +{ + uint8_t reg; + int ret; + + if (lm3535_data.revision <= 1) { + printk (KERN_ERR "%s: early revison, setting zone to 5 (no ALS)\n", + __FUNCTION__); + atomic_set (lm3535_data.als_zone, ALS_NO_ZONE); + return ALS_NO_ZONE; + } + lm3535_read_reg (LM3535_CONFIG_REG, ®); + if (reg & 0x50) { + ret = lm3535_read_reg (LM3535_ALS_REG, ®); + lm3535_data.als_zone = reg & ALS_ZONE_MASK; + return lm3535_data.als_zone; + } else { + printk (KERN_ERR "%s: ALS is not enabled, CONFIG=0x%x, zone=5\n", + __FUNCTION__, reg); + lm3535_data.als_zone = 5; + return 5; + } +} +#endif +int lm3535_register_als_callback(als_cb func, uint32_t cookie) +{ + struct als_callback *c; + + //printk (KERN_INFO "%s: enter\n", __FUNCTION__); + c = kzalloc (sizeof (struct als_callback), GFP_KERNEL); + if (c == NULL) { + printk (KERN_ERR "%s: unable to register ALS callback: kzalloc\n", + __FUNCTION__); + return -ENOMEM; + } + c->cb = func; + c->cookie = cookie; + mutex_lock (&als_cb_mutex); + list_add (&c->entry, &als_callbacks); + mutex_unlock (&als_cb_mutex); + return 0; +} +EXPORT_SYMBOL(lm3535_register_als_callback); + +void lm3535_unregister_als_callback (als_cb func) +{ + struct als_callback *c; + + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return; + } + printk (KERN_INFO "%s: enter\n", __FUNCTION__); + mutex_lock (&als_cb_mutex); + list_for_each_entry(c, &als_callbacks, entry) { + if (c->cb == func) { + list_del (&c->entry); + kfree(c); + mutex_unlock (&als_cb_mutex); + return; + } + } + mutex_unlock (&als_cb_mutex); + printk (KERN_ERR "%s: callback 0x%x not found\n", + __FUNCTION__, (unsigned int)func); +} +EXPORT_SYMBOL(lm3535_unregister_als_callback); + +unsigned lm3535_als_is_dark (void) +{ + unsigned zone; + + zone = atomic_read (&lm3535_data.als_zone); + printk (KERN_ERR "%s: enter, zone = %d\n", + __FUNCTION__, zone); + if (zone == 0 || zone == 5) + return 1; + else + return 0; +} +EXPORT_SYMBOL(lm3535_als_is_dark); + +static int lm3535_read_reg (unsigned reg, uint8_t *value) +{ + struct i2c_client *client = lm3535_data.client; + uint8_t buf[1]; + int ret = 0; + + if (!value) + return -EINVAL; + buf[0] = reg; + ret = i2c_master_send (client, buf, 1); + if (ret > 0) { + msleep_interruptible (1); + ret = i2c_master_recv (client, buf, 1); + if (ret > 0) + *value = buf[0]; + } + return ret; +} + +static int lm3535_write_reg (unsigned reg, uint8_t value, const char *caller) +{ + uint8_t buf[2] = {reg, value}; + int ret = 0; + + printk_write ("%s: writing 0x%X to reg 0x%X (%s) at addr 0x%X\n", + caller, buf[1], buf[0], reg_name (reg), lm3535_data.client->addr); + ret = i2c_master_send (lm3535_data.client, buf, 2); + if (ret < 0) + printk (KERN_ERR "%s: i2c_master_send error %d\n", + caller, ret); + return ret; +} +/* RAW ALS coefficients */ +static int raw_als_z0[] = {-44773, 6893285, 23168221}; +static int raw_als_z1[] = {-141, 358959, 274671182}; +static int raw_als_z2[] = {-74, 529533, 37559974}; + +/* ALS Coefficients */ +static long als_z0[] = {24, -15295, 4123276, 634967215}; +module_param_array(als_z0, long, NULL, 0644); + +static long als_z1[] = {24, -14596, 3987380, 659307205}; +module_param_array(als_z1, long, NULL, 0644); + +static long als_z2[] = {0, -5700, 2553000, 953424200}; +module_param_array(als_z2, long, NULL, 0644); + +static long als_z3[] = {0, -5700, 2553000, 953424200}; +module_param_array(als_z3, long, NULL, 0644); + +static long als_z4[] = {0, -5700, 2553000, 953424200}; +module_param_array(als_z4, long, NULL, 0644); + +static unsigned long als_denom = 10000000; +module_param(als_denom, ulong, 0644); + +static unsigned dim_values[] = {0x03, 0x30, 0x50, 0x50, 0x50}; +module_param_array(dim_values, uint, NULL, 0644); + +static int abs_als_to_backlight(unsigned int als_value) +{ + int backlight; + int als = als_value; + /* + Clamp ALS to max 2330. + for ALS 0 to 86 + Y=(-44772.9*X^2+6893284.5*X+23168220.5) + + for ALS 87 to 999 + Y=(-141.2*X^2+358959.2*X+274671182) + + for ALS 1000 to 2330 + Y=(-74.1*X^2+529533.4*X+37559974.7) + + For all ranges, Y = Y / 10000000; + This is the final backlight value + */ + if (als < 86) { + backlight = (int)(((raw_als_z0[0] * als * als) + + (raw_als_z0[1] * als) + + raw_als_z0[2])/10000000); + } else if (als > 87 && als < 999) { + backlight = (int)(((raw_als_z1[0] * als * als) + + (raw_als_z1[1] * als) + + raw_als_z1[2])/10000000); + } else { + if (als > 2330) + als = 2330; + + backlight = (int)(((raw_als_z2[0] * als * als) + + (raw_als_z2[1] * als) + + raw_als_z2[2])/10000000); + } + + /* Cap ambient mode backlight to 36*/ + if (backlight > MAX_AMBIENT_BACKLIGHT) + backlight = MAX_AMBIENT_BACKLIGHT; + pr_info("ALS: %d, backlight: %d\n", als, backlight); + return backlight; +} +/* Convert slider value into LM3535 register value */ +static uint8_t lm3535_convert_value (unsigned value, unsigned zone) +{ + uint8_t reg; + uint32_t res; +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY + struct m4sensorhub_data *m4sensorhub; + int size; + static int ambient_als_backlight; + uint16_t als; + bool adjust_als = false; +#endif + + if (!value) + return 0; + + if (atomic_read (&lm3535_data.in_suspend)) { + printk_br ("%s: in TCMD SUSPEND, returning 0x%x\n", + __FUNCTION__, value/2); + return value/2; + } + switch (zone) { + case 0: + if (value == 1) + res = dim_values[0]; // DIM value + else + res = als_z0[0] * value * value * value + +als_z0[1] * value * value + +als_z0[2] * value + +als_z0[3]; + break; + case 1: + if (value == 1) + res = dim_values[1]; // DIM value + else + res = als_z1[0] * value * value * value + +als_z1[1] * value * value + +als_z1[2] * value + +als_z1[3]; + break; + case 2: + if (value == 1) + res = dim_values[2]; // DIM value + else + res = als_z2[0] * value * value * value + +als_z2[1] * value * value + +als_z2[2] * value + +als_z2[3]; + break; + case 3: + if (value == 1) + res = dim_values[3]; // DIM value + else + res = als_z3[0] * value * value * value + +als_z3[1] * value * value + +als_z3[2] * value + +als_z3[3]; + break; + case 4: + default: + if (value == 1) + res = dim_values[4]; // DIM value + else + res = als_z4[0] * value * value * value + +als_z4[1] * value * value + +als_z4[2] * value + +als_z4[3]; + break; + } + if (value == 1) + reg = res; + else + reg = res / als_denom; + +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY + if (atomic_read(&lm3535_data.alsstatus)) { + if (!lm3535_data.prevent_als_read) { + /* make sure this is atleast as + high as corresponding ambient + * mode value for current ALS condition */ + m4sensorhub = m4sensorhub_client_get_drvdata(); + size = m4sensorhub_reg_getsize(m4sensorhub, + M4SH_REG_LIGHTSENSOR_SIGNAL); + if (size != sizeof(als)) { + pr_err("can't get M4 reg size for ALS\n"); + ambient_als_backlight = 0; + } else if (size != m4sensorhub_reg_read(m4sensorhub, + M4SH_REG_LIGHTSENSOR_SIGNAL, + (char *)&als)) { + pr_err("error reading M4 ALS value\n"); + ambient_als_backlight = 0; + } else { + adjust_als = true; + /* prevent als reads for next 500 ms */ + lm3535_data.prevent_als_read = 1; + schedule_delayed_work( + &lm3535_data.als_delayed_work, + msecs_to_jiffies(500)); + } + } else if (ambient_als_backlight > reg) { + /* If valid, use previously read als value */ + reg = ambient_als_backlight; + } + } + + if (adjust_als) { + ambient_als_backlight = abs_als_to_backlight(als); + + if (ambient_als_backlight > reg) + reg = ambient_als_backlight; + } +#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY*/ + + printk_br(KERN_INFO "%s: v=%d, z=%d, res=0x%x, reg=0x%x\n", + __func__, value, zone, res, reg); + + return reg; +} + +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY +static int lm3535_dock_notifier(struct notifier_block *self, + unsigned long action, void *dev) +{ + switch (action) { + case DISPLAY_WAKE_EVENT_DOCKON: + atomic_set(&lm3535_data.docked, 1); + break; + case DISPLAY_WAKE_EVENT_DOCKOFF: + atomic_set(&lm3535_data.docked, 0); + break; + } + + return NOTIFY_OK; +} +#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY */ + +#ifdef CONFIG_HAS_AMBIENTMODE +struct led_classdev *led_get_default_dev(void) +{ + return &lm3535_led; +} +EXPORT_SYMBOL(led_get_default_dev); + + + +static void lm3535_brightness_set_raw_als(struct led_classdev *led_cdev, + unsigned int value) +{ + struct i2c_client *client = lm3535_data.client; + int ret; + unsigned breg = LM3535_BRIGHTNESS_CTRL_REG_A; + + int bvalue = abs_als_to_backlight(value); + mutex_lock(&lm3535_mutex); + + if (!lm3535_data.enabled && value != 0) + lm3535_enable(client, 1); + + ret = lm3535_write_reg(breg, bvalue, __func__); + lm3535_data.bvalue = bvalue; + led_cdev->brightness = bvalue; + + mutex_unlock(&lm3535_mutex); +} +#endif + +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY +static int lm3535_als_notifier(struct notifier_block *self, + unsigned long action, void *dev) +{ + pr_info("%s: ALS value is %lu\n", __func__, action); + switch (action) { + case ALS_ENABLED: + case ALS_DISABLED: + atomic_set(&lm3535_data.use_als, (action == ALS_ENABLED)); + break; + default: +#ifdef CONFIG_ALS_WHILE_CHARGING + if (atomic_read(&lm3535_data.interactive) == 0) + lm3535_brightness_set_raw_als(led_get_default_dev(), + (unsigned int)action); + else + pr_info("%s: ignoring ALS notifications\n", __func__); +#endif + break; + } + return NOTIFY_OK; +} +#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY */ + +static void lm3535_brightness_set (struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct i2c_client *client = lm3535_data.client; + int ret, nsteps; + unsigned int total_time = 0; + unsigned breg = LM3535_BRIGHTNESS_CTRL_REG_A; + unsigned bright_zone; + unsigned bvalue; + unsigned do_ramp = 1; + + printk_br ("%s: %s, 0x%x (%d)\n", __FUNCTION__, + led_cdev->name, value, value); + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return; + } + if (strstr (led_cdev->name, "nr")) + do_ramp = 0; + + bright_zone = atomic_read (&lm3535_data.bright_zone); + mutex_lock (&lm3535_mutex); + if (value == -1) + value = led_cdev->brightness; /* Special case for ALS adjustment */ + else if ((value > 0) && (value < 5)) + value = 0; /* Special case for turn off */ + else if ((value >= 5) && (value < 10)) + value = 1; /* Special case for dim */ + + if ((value == 0) && (!lm3535_data.enabled)) { + /* If LED already disabled, we don't need to do anything */ + mutex_unlock(&lm3535_mutex); + return; + } + +#ifdef CONFIG_LM3535_ESD_RECOVERY + if (value == LED_OFF && esd_polling) + { + esd_poll_stop(lm3535_check_esd); + esd_polling = 0; + } +#endif /* CONFIG_LM3535_ESD_RECOVERY */ + if (!lm3535_data.enabled && value != 0) + lm3535_enable(client, 1); + + /* Calculate brightness value for each zone relative to its cap */ + bvalue = lm3535_convert_value (value, bright_zone); + +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY + if (atomic_read(&lm3535_data.docked) && (bvalue < MIN_DOCK_BVALUE)) + bvalue = MIN_DOCK_BVALUE; /* hard code for dock mode */ +#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY */ + + /* Calculate number of steps for ramping */ + nsteps = bvalue - lm3535_data.bvalue; + if (nsteps < 0) + nsteps = nsteps * (-1); + + lm3535_set_ramp (client, do_ramp, nsteps, &total_time); + + printk_br ("%s: zone %d, 0x%x => 0x%x, %d steps, ramp time %dus\n", + __FUNCTION__, bright_zone, + lm3535_data.bvalue, bvalue, nsteps, total_time); + + /* Write to each zone brightness register so that when it jumps into + * the next zone the value is adjusted automatically + */ + ret = lm3535_write_reg (breg, bvalue, __FUNCTION__); + lm3535_data.bvalue = bvalue; + + if (value == 0) { + /* Disable everything */ + if (do_ramp) { + /* Wait for ramping to finish */ + udelay (total_time); + } + lm3535_enable(client, 0); + } + +#ifdef CONFIG_LM3535_ESD_RECOVERY + if ((value > 0) && (!esd_polling)) + { + esd_poll_start(lm3535_check_esd, 0); + esd_polling = 1; + } +#endif /* CONFIG_LM3535_ESD_RECOVERY */ + + + mutex_unlock (&lm3535_mutex); +} + +#ifdef CONFIG_LM3535_BUTTON_BL +static void lm3535_button_brightness_set (struct led_classdev *led_cdev, + enum led_brightness value) +{ + int ret; + unsigned breg = LM3535_BRIGHTNESS_CTRL_REG_C; + struct i2c_client *client = lm3535_data.client; + + printk_br ("%s: %s, 0x%x (%d)\n", __FUNCTION__, + led_cdev->name, value, value); + + mutex_lock (&lm3535_mutex); + + if (!lm3535_data.button_enabled && value != 0) { + lm3535_enable (client, lm3535_data.enabled, 1); + } + + ret = lm3535_write_reg (breg, 0xF8, __FUNCTION__); // Lowest setting + + if (value == 0) + lm3535_enable(client, 0); + + mutex_unlock(&lm3535_mutex); +} +#endif + +static int lm3535_als_open (struct inode *inode, struct file *file) +{ + if (!lm3535_data.initialized) + return -ENODEV; + + return 0; +} + +static int lm3535_als_release (struct inode *inode, struct file *file) +{ + return 0; +} +#define CMD_LEN 5 +static ssize_t lm3535_als_write (struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + unsigned char cmd[CMD_LEN]; + int len; + uint8_t value; + unsigned old_zone; + + if (count < 1) + return 0; + + len = count > CMD_LEN-1 ? CMD_LEN-1 : count; + + if (copy_from_user (cmd, buf, len)) + return -EFAULT; + + if (lm3535_data.revision <= 1) + return -EFAULT; + + cmd[len] = '\0'; + if (cmd[len-1] == '\n') { + cmd[len-1] = '\0'; + len--; + } + if (!strcmp (cmd, "1")) { + printk (KERN_INFO "%s: enabling ALS\n", __FUNCTION__); + value = CONFIG_VALUE | 0x80; + mutex_lock (&lm3535_mutex); + atomic_set (&lm3535_data.use_als, 1); + /* No need to change ALS zone; interrupt handler will do it */ + lm3535_write_reg (LM3535_CONFIG_REG, value, __FUNCTION__); + mutex_unlock (&lm3535_mutex); + } else if (!strcmp (cmd, "0")) { + printk (KERN_INFO "%s: disabling ALS\n", __FUNCTION__); + value = CONFIG_VALUE_NO_ALS; + mutex_lock (&lm3535_mutex); + old_zone = atomic_read (&lm3535_data.als_zone); + lm3535_write_reg (LM3535_CONFIG_REG, value, __FUNCTION__); + atomic_set (&lm3535_data.use_als, 0); + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + mutex_unlock (&lm3535_mutex); + if (atomic_read (&lm3535_data.bright_zone) < 2) { + atomic_set (&lm3535_data.bright_zone, ALS_NO_ZONE); + printk_als ("%s: ALS canceled; changing brightness\n", + __FUNCTION__); + /* Adjust brightness */ + lm3535_brightness_set (&lm3535_led, -1); + } else { + atomic_set (&lm3535_data.bright_zone, ALS_NO_ZONE); + } + lm3535_call_als_callbacks (old_zone, 0); + lm3535_send_als_event (0); + } else { + printk (KERN_ERR "%s: invalid command %s\n", __FUNCTION__, cmd); + return -EFAULT; + } + + return count; +} + +static ssize_t lm3535_als_read (struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + char z[23]; + + if (file->private_data) + return 0; + + if (!atomic_read (&lm3535_data.use_als)) { + sprintf (z, "%d\n", ALS_NO_ZONE); + } else { + sprintf (z, "%d %d\n", + atomic_read (&lm3535_data.als_zone), + atomic_read (&lm3535_data.bright_zone)); + } + if (copy_to_user (buf, z, strlen (z))) + return -EFAULT; + + file->private_data = (void *)1; + return strlen (z); +} + +static const struct file_operations als_fops = { + .owner = THIS_MODULE, + .read = lm3535_als_read, + .write = lm3535_als_write, + .open = lm3535_als_open, + .release = lm3535_als_release, +}; + +static struct miscdevice als_miscdev = { + .minor = MISC_DYNAMIC_MINOR, + .name = "als", + .fops = &als_fops, +}; + +static ssize_t lm3535_suspend_show (struct device *dev, + struct device_attribute *attr, char *buf) +{ + sprintf (buf, "%d\n", atomic_read (&lm3535_data.in_suspend)); + return strlen(buf)+1; +} + +static ssize_t lm3535_suspend_store (struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned value = 0; + + if (!buf || size == 0) { + printk (KERN_ERR "%s: invalid command\n", __FUNCTION__); + return -EINVAL; + } + + sscanf (buf, "%d", &value); + if (value) { + printk (KERN_INFO "%s: going into TCMD SUSPEND mode\n", + __FUNCTION__); + atomic_set (&lm3535_data.in_suspend, 1); + lm3535_data.saved_bvalue = lm3535_led.brightness; + lm3535_led.brightness = 255; + } else { + printk (KERN_INFO "%s: exiting TCMD SUSPEND mode\n", + __FUNCTION__); + atomic_set (&lm3535_data.in_suspend, 0); + lm3535_led.brightness = lm3535_data.saved_bvalue; + } + /* Adjust brightness */ + lm3535_brightness_set (&lm3535_led, -1); + return size; +} +static DEVICE_ATTR(suspend, 0644, lm3535_suspend_show, lm3535_suspend_store); + +#ifdef CONFIG_ALS_WHILE_CHARGING +static ssize_t lm3535_interactive_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", atomic_read(&lm3535_data.interactive)); +} + +static ssize_t lm3535_interactive_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + unsigned value = 0; + + if (!buf || size == 0) { + pr_err("%s: invalid command\n", __func__); + return -EINVAL; + } + + sscanf(buf, "%d", &value); + if (value) + atomic_set(&lm3535_data.interactive, 1); + else + atomic_set(&lm3535_data.interactive, 0); + + return size; +} +static DEVICE_ATTR(interactive, S_IRUGO | S_IWUSR, + lm3535_interactive_show, + lm3535_interactive_store); +#endif + +/* This function is called by i2c_probe */ +static int lm3535_probe (struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret = 0; + unsigned long request_flags = IRQF_TRIGGER_LOW; + + gpio_request(LM3535_HWEN_GPIO, "LM3535 HWEN"); + gpio_direction_output(LM3535_HWEN_GPIO, 1); + msleep(1); + + /* We should be able to read and write byte data */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk (KERN_ERR "%s: I2C_FUNC_I2C not supported\n", + __FUNCTION__); + return -ENOTSUPP; + } + + lm3535_data.client = client; + i2c_set_clientdata (client, &lm3535_data); + + /* Initialize chip */ + lm3535_setup (lm3535_data.client); + + /* Initialize interrupts */ + if (lm3535_data.revision > 1) { + INIT_WORK(&lm3535_data.work, lm3535_work_func); + if (client->irq) { + ret = request_irq (client->irq, lm3535_irq_handler, request_flags, + "lm3535", &lm3535_data); + + if (ret == 0) { + lm3535_data.use_irq = 1; + ret = irq_set_irq_wake (client->irq, 1); + } else { + printk (KERN_ERR "request_irq %d for lm3535 failed: %d\n", + client->irq, ret); + free_irq (client->irq, &lm3535_data); + lm3535_data.use_irq = 0; + } + } + } + + /* Register LED class */ + ret = led_classdev_register (&client->adapter->dev, &lm3535_led); + if (ret) { + printk (KERN_ERR "%s: led_classdev_register %s failed: %d\n", + __FUNCTION__, lm3535_led.name, ret); + return ret; + } + + /* Register LED class for no ramping */ + ret = led_classdev_register (&client->adapter->dev, &lm3535_led_noramp); + if (ret) { + printk (KERN_ERR "%s: led_classdev_register %s failed: %d\n", + __FUNCTION__, lm3535_led.name, ret); + } + if ((ret = misc_register (&als_miscdev))) { + printk (KERN_ERR "%s: misc_register failed, error %d\n", + __FUNCTION__, ret); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + return ret; + } + + atomic_set (&lm3535_data.in_suspend, 0); + ret = device_create_file (lm3535_led.dev, &dev_attr_suspend); + if (ret) { + printk (KERN_ERR "%s: unable to create suspend device file for %s: %d\n", + __FUNCTION__, lm3535_led.name, ret); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + misc_deregister (&als_miscdev); + return ret; + } +#ifdef CONFIG_ALS_WHILE_CHARGING + ret = device_create_file(lm3535_led.dev, &dev_attr_interactive); + if (ret) { + pr_err("err creating interactive file for %s: %d\n", + lm3535_led.name, ret); + led_classdev_unregister(&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + device_remove_file(lm3535_led.dev, &dev_attr_suspend); + misc_deregister(&als_miscdev); + return ret; + } +#endif + dev_set_drvdata (lm3535_led.dev, &lm3535_led); +#if 0 + lm3535_data.idev = input_allocate_device(); + if (lm3535_data.idev == NULL) { + printk (KERN_ERR "%s: unable to allocate input device file for als\n", + __FUNCTION__); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + misc_deregister (&als_miscdev); + device_remove_file (lm3535_led.dev, &dev_attr_suspend); + return -ENOMEM; + } + lm3535_data.idev->name = "als"; + input_set_capability(lm3535_data.idev, EV_MSC, MSC_RAW); + input_set_capability(lm3535_data.idev, EV_LED, LED_MISC); + ret = input_register_device (lm3535_data.idev); + if (ret) { + printk (KERN_ERR "%s: unable to register input device file for als: %d\n", + __FUNCTION__, ret); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); + misc_deregister (&als_miscdev); + device_remove_file (lm3535_led.dev, &dev_attr_suspend); + input_free_device (lm3535_data.idev); + return ret; + } +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + register_early_suspend (&early_suspend_data); +#endif + + lm3535_led.brightness = 84; + lm3535_led_noramp.brightness = 84; + /* lm3535_brightness_set (&lm3535_led_noramp, 255); */ + lm3535_write_reg(LM3535_BRIGHTNESS_CTRL_REG_A, 87, __func__); + lm3535_data.initialized = 1; +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY + atomic_set(&lm3535_data.docked, 0); + /* default setting for minnow is to use ALS */ + atomic_set(&lm3535_data.alsstatus, 1); +#ifdef CONFIG_ALS_WHILE_CHARGING + atomic_set(&lm3535_data.interactive, 1); +#endif + lm3535_data.dock_nb.notifier_call = lm3535_dock_notifier; + wakeup_source_register_notify(&lm3535_data.dock_nb); + + lm3535_data.als_nb.notifier_call = lm3535_als_notifier; + als_register_notify(&lm3535_data.als_nb); +#endif /* CONFIG_WAKEUP_SOURCE_NOTIFY */ + + INIT_DELAYED_WORK(&lm3535_data.als_delayed_work, + lm3535_allow_als_work_func); + + return 0; +} + +static irqreturn_t lm3535_irq_handler (int irq, void *dev_id) +{ + struct lm3535 *data_ptr = (struct lm3535 *)dev_id; + + pr_debug ("%s: got an interrupt %d\n", __FUNCTION__, irq); + + disable_irq (irq); + schedule_work (&data_ptr->work); + + return IRQ_HANDLED; +} + +static void lm3535_send_als_event (int zone) +{ +#ifdef CONFIG_ALS_UEVENT + char event_string[20]; + char *envp[] = {event_string, NULL}; + int ret; + + sprintf (event_string, "ALS_ZONE=%d", zone); + ret = kobject_uevent_env (&als_miscdev.this_device->kobj, + KOBJ_CHANGE, envp); + if (ret) { + printk (KERN_ERR "%s: kobject_uevent_env failed: %d\n", + __FUNCTION__, ret); + } else { + printk_event ("%s: kobject_uevent_env %s success\n", + __FUNCTION__, event_string); + } +#else + //input_event (lm3535_data.idev, EV_MSC, MSC_RAW, light_value); + input_event (lm3535_data.idev, EV_LED, LED_MISC, zone); + input_sync (lm3535_data.idev); + +#endif +} + +static void lm3535_call_als_callbacks(unsigned old_zone, unsigned zone) +{ + struct als_callback *c; + unsigned old, new; + + old = (old_zone == ALS_NO_ZONE) ? 0 : old_zone; + new = (zone == ALS_NO_ZONE) ? 0 : zone; + + mutex_lock (&als_cb_mutex); + list_for_each_entry(c, &als_callbacks, entry) { + c->cb(old, new, c->cookie); + } + mutex_unlock (&als_cb_mutex); +} + +static void lm3535_work_func (struct work_struct *work) +{ + int ret; + uint8_t reg; + unsigned zone, old_zone; + + pr_debug ("%s: work function called\n", __FUNCTION__); + ret = lm3535_read_reg (LM3535_ALS_REG, ®); + if (ret) { + if (reg & ALS_FLAG_MASK) { + zone = reg & ALS_ZONE_MASK; + if (zone > 4) + zone = 4; + old_zone = atomic_read (&lm3535_data.als_zone); + printk_als ("%s: ALS zone changed: %d => %d, register = 0x%x\n", + __FUNCTION__, old_zone, zone, reg); + atomic_set (&lm3535_data.als_zone, zone); + if (zone > atomic_read (&lm3535_data.bright_zone) || + atomic_read (&lm3535_data.bright_zone) == ALS_NO_ZONE) { + atomic_set (&lm3535_data.bright_zone, zone); + if (!atomic_read (&lm3535_data.in_suspend)) { + printk_als ("%s: ALS zone increased; changing brightness\n", + __FUNCTION__); + /* Adjust brightness */ + lm3535_brightness_set (&lm3535_led, -1); + } else { + printk_als ("%s: ALS zone increased; SUSPEND mode - not changing brightness\n", + __FUNCTION__); + } + } + /* See if PWM needs to be changed */ + if (old_zone < 2 && zone >= 2) { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE | 0x80, + __FUNCTION__); + printk_als ("%s: moved from dim/dark to bright; disable PWM\n", + __FUNCTION__); + } else if (old_zone >= 2 && zone < 2) { + lm3535_write_reg (LM3535_CONFIG_REG, + CONFIG_VALUE|0x80|pwm_value, + __FUNCTION__); + printk_als ("%s: moved from bright to dim/dark; enable PWM\n", + __FUNCTION__); + } + if (!atomic_read (&lm3535_data.in_suspend)) { + lm3535_call_als_callbacks (old_zone, zone); + } + lm3535_send_als_event (zone); + } else { + printk_als ("%s: got ALS interrupt but flag is not set: 0x%x\n", + __FUNCTION__, reg); + } + } + if (atomic_read (&lm3535_data.do_als_config)) { + lm3535_write_reg (LM3535_ALS_CTRL_REG, ALS_AVERAGING, __FUNCTION__); + atomic_set (&lm3535_data.do_als_config, 0); + printk_als ("%s: configured ALS averaging 0x%x\n", + __FUNCTION__, ALS_AVERAGING); + } + enable_irq (lm3535_data.client->irq); +} + +static void lm3535_allow_als_work_func(struct work_struct *work) +{ + lm3535_data.prevent_als_read = 0; +} + +#if 0 +static enum hrtimer_restart lm3535_timer_func (struct hrtimer *timer) +{ + schedule_work(&lm3535_data.work); + + hrtimer_start(&lm3535_data.timer, + ktime_set(1, 0), HRTIMER_MODE_REL); + return HRTIMER_NORESTART; +} +#endif + +static void lm3535_set_options_r1 (uint8_t *buf, unsigned ramp) +{ + struct lm3535_options_register_r1 *r = + (struct lm3535_options_register_r1 *)buf; + + if (!r) + return; + *buf = 0; + r->rs = ramp; + r->gt = 0x2; + r->rev = 0; +} + +static void lm3535_set_options_r2 (uint8_t *buf, unsigned ramp) +{ + struct lm3535_options_register_r2 *r = + (struct lm3535_options_register_r2 *)buf; + + if (!r) + return; + *buf = 0; + r->rs_up = ramp; + r->rs_down = ramp; + r->gt = 0; +} + +static void lm3535_set_options_r3 (uint8_t *buf, unsigned ramp) +{ + struct lm3535_options_register_r3 *r = + (struct lm3535_options_register_r3 *)buf; + + if (!r) + return; + *buf = 0; + r->rs_up = ramp; + r->rs_down = ramp; + r->gt = 0x02; +} + +/* This function calculates ramp step time so that total ramp time is + * equal to ramp_time defined currently at 200ms + */ +static int lm3535_set_ramp (struct i2c_client *client, + unsigned int on, unsigned int nsteps, unsigned int *rtime) +{ + int ret, i = 0; + uint8_t value = 0; + unsigned int total_time = 0; + + if (on) { + /* Calculate the closest possible ramp time */ + for (i = 0; i < lm3535_data.nramp; i++) { + total_time = nsteps * lm3535_ramp[i]; + if (total_time >= ramp_time) + break; + } + if (i > 0 && total_time > ramp_time) { +#if 0 + /* If previous value is closer */ + if (total_time - ramp_time > + ramp_time - nsteps * lm3535_ramp[i-1]) { + i--; + total_time = nsteps * lm3535_ramp[i]; + } +#endif + i--; + total_time = nsteps * lm3535_ramp[i]; + } + lm3535_set_options_f (&value, i); + } + +#if 0 + printk (KERN_ERR "%s: ramp = %s, ramp step = %d us (total = %d us)\n", + __FUNCTION__, on ? "on" : "off", lm3535_ramp[i], total_time); +#endif + if (rtime) + *rtime = total_time; + ret = lm3535_write_reg (LM3535_OPTIONS_REG, value, __FUNCTION__); +#if 0 + printk_br ("%s: nsteps = %d, OPTIONS_REG = 0x%x, total ramp = %dus\n", + __FUNCTION__, nsteps, value, lm3535_ramp[i] * nsteps); +#endif + return ret; +} + +static int lm3535_enable(struct i2c_client *client, unsigned int on) +{ + int ret; + uint8_t value = 0x0F; // Enable A + + if (on) { + gpio_set_value(LM3535_HWEN_GPIO, 1); + msleep(1); + } else + value = 0; + + ret = lm3535_write_reg (LM3535_ENABLE_REG, value, __FUNCTION__); + if (ret < 0) + return ret; + + if (lm3535_data.revision == 2 || lm3535_data.revision == 3) { + if (on) { + ret = lm3535_write_reg (LM3535_TRIM_REG, LM3535_TRIM_VALUE, + __FUNCTION__); + } + } + if (!on) + gpio_set_value(LM3535_HWEN_GPIO, 0); + + lm3535_data.enabled = on; + return ret; +} + +static int lm3535_configure (void) +{ + int ret = 0; + +#if 1 + /* On G2, we are not using ALS interrupt so we want to leave the ALS + interrupt disabled and ALS resistor in high impedance mode */ + /* Disable ALS interrupt */ + lm3535_write_reg(LM3535_CONFIG_REG, 0, __func__); + /* Put ALS Resistor into high impedance mode to save current */ + lm3535_write_reg(LM3535_ALS_RESISTOR_REG, 0, __func__); + +#else + uint8_t value = 0x03; + uint8_t reg = 0; + unsigned old_zone = 0; + unsigned new_zone = ALS_NO_ZONE; + + /* Config register bits: + * Rev1: AVE2 AVE1 AVE0 ALS-SD ALS-EN PWM-EN 53A 62A + * Rev2: ALSF ALS-SD ALS-ENB ALS-ENA 62A 53A PWM-P PWM-EN + * Rev3: ALSF ALS-EN ALS-ENB ALS-ENA 62A 53A PWM-P PWM-EN + * + * ALSF: sets E1B/INT pin to interrupt Pin; 0 = D1B, 1 = INT. + * Open Drain Interrupt. Pulls low when change occurs. Flag cleared + * once a I2C read command for register 0x40 occurs. + * ALS-SD (rev2) and ALS-EN (final): turn off ALS feature + * ALS-SD: 0 = Active, 1 = Shutdown. Rev2 cannot have ALS active without + * LEDs turned on (ENxx bits = 1). + * ALS-EN: 0 = Shutdown, 1 = Active. Final can have ALS active without + * LEDs turned on. ALS-EN overrides the ENC bit. + * ALS-ENB and ALS-ENA: 1 enables ALS control of diode current. BankA has + * full ALS control, BankB just has on/off ability. + * 62A and 53A: 1 sets D62 and D53 to BankA (Required for 6 LEDs in BankA). + * 0 sets them to BankB. + * PWM-P: PWM Polarity, 0 = Active (Diodes on) High, 1 = Active (Diodes on) + * Low. + * PWM-EN: Enables PWM Functionality. 1 = Active. + */ + +#ifndef CONFIG_MAC_MOT +#ifdef CONFIG_LM3535_ESD_RECOVERY + /* Configure lighting zone max brightness */ + lm3535_write_reg (LM3535_ALS_Z0T_REG, als_zone_max[0], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z1T_REG, als_zone_max[1], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z2T_REG, als_zone_max[2], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z3T_REG, als_zone_max[3], + __FUNCTION__); + lm3535_write_reg (LM3535_ALS_Z4T_REG, als_zone_max[4], + __FUNCTION__); +#endif +#endif + + if (lm3535_data.revision > 1) { + /* Configure internal ALS resistor register */ + ret = lm3535_write_reg (LM3535_ALS_RESISTOR_REG, + (uint8_t)resistor_value, __FUNCTION__); + +#ifndef CONFIG_MAC_MOT +#ifdef CONFIG_LM3535_BUTTON_BL + ret = lm3535_write_reg (LM3535_ALS_SELECT_REG, 0x2, + __FUNCTION__); +#endif +#endif + /* Configure lighting zone boundaries */ + lm3535_write_reg (LM3535_ALS_ZB0_REG, als_zb[0], __FUNCTION__); + lm3535_write_reg (LM3535_ALS_ZB1_REG, als_zb[1], __FUNCTION__); + lm3535_write_reg (LM3535_ALS_ZB2_REG, als_zb[2], __FUNCTION__); + lm3535_write_reg (LM3535_ALS_ZB3_REG, als_zb[3], __FUNCTION__); + + /* Configure ALS averaging to be very short the first time */ + lm3535_write_reg (LM3535_ALS_CTRL_REG, 0x80, __FUNCTION__); + } + + if (lm3535_data.revision == 0) { + value = 0x3; // Just enable A, don't bother with ALS + atomic_set (&lm3535_data.use_als, 0); + } else if (lm3535_data.revision == 1) { + value = 0x0C; // No ALS or PWM + atomic_set (&lm3535_data.use_als, 0); + } else { + if (atomic_read (&lm3535_data.use_als)) + value = CONFIG_VALUE; + else + value = CONFIG_VALUE_NO_ALS; + } + pr_debug ("%s: use_als is %d, value = 0x%x\n", __FUNCTION__, + atomic_read (&lm3535_data.use_als), value); + ret = lm3535_write_reg (LM3535_CONFIG_REG, value, __FUNCTION__); + // Nothing else to do for older revisions + if (lm3535_data.revision <= 1) { + return 0; + } + + /* Has to be at least 300ms even with ALS averaging set to 0 */ + msleep_interruptible (als_sleep); // Wait for ALS to kick in + /* Read current ALS zone */ + old_zone = atomic_read (&lm3535_data.als_zone); + if (atomic_read (&lm3535_data.use_als)) { + ret = lm3535_read_reg (LM3535_ALS_REG, ®); + if (ret) { + new_zone = reg & ALS_ZONE_MASK; + if (new_zone > 4) { + new_zone = 4; + } + atomic_set (&lm3535_data.als_zone, new_zone); + printk_als ("%s: ALS Register: 0x%x, zone %d\n", + __FUNCTION__, reg, atomic_read (&lm3535_data.als_zone)); + } else { + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + atomic_set (&lm3535_data.use_als, 0); + printk (KERN_ERR "%s: unable to read ALS zone; disabling ALS\n", + __FUNCTION__); + } + } else { + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + } + /* Brightness zone is for now the same as ALS zone */ + atomic_set (&lm3535_data.bright_zone, + atomic_read (&lm3535_data.als_zone)); + lm3535_call_als_callbacks (old_zone, new_zone); + if (lm3535_data.initialized) { + lm3535_send_als_event (new_zone); + } + if (atomic_read (&lm3535_data.use_als)) { + /* Configure averaging */ + //lm3535_write_reg (LM3535_ALS_CTRL_REG, ALS_AVERAGING, __FUNCTION__); + /* Enable interrupt and PWM for CABC */ + if (new_zone <= 1) { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE|0x80|pwm_value, + __FUNCTION__); + } else { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE | 0x80, + __FUNCTION__); + } + } else { + lm3535_write_reg (LM3535_CONFIG_REG, CONFIG_VALUE | pwm_value, + __FUNCTION__); + } +#endif + + return ret; +} + +static int lm3535_setup (struct i2c_client *client) +{ + int ret; + uint8_t value; + + /* Read revision number */ + ret = lm3535_read_reg (LM3535_ALS_CTRL_REG, &value); + if (ret < 0) { + printk (KERN_ERR "%s: unable to read from chip: %d\n", + __FUNCTION__, ret); + printk(KERN_ERR "client->addr = %x failed\n", client->addr); + + /* If the first I2C address doesn't work, try 0x36 */ + client->addr = 0x36; + ret = lm3535_read_reg (LM3535_ALS_CTRL_REG, &value); + if (ret < 0) { + printk (KERN_ERR "%s: unable to read from chip: %d\n", + __FUNCTION__, ret); + printk(KERN_ERR "client->addr = %x failed\n", client->addr); + return ret; + } + } + + switch (value) { + case 0xFF: lm3535_data.revision = 0; break; + case 0xF0: lm3535_data.revision = 1; break; + case 0x02: lm3535_data.revision = 2; break; + case 0x00: lm3535_data.revision = 3; break; + case 0x01: lm3535_data.revision = 4; break; + default: lm3535_data.revision = 4; break; // Assume final + } + /* revision is going to be an index to lm3535_ramp array */ + printk (KERN_INFO "%s: revision %d (0x%X)\n", + __FUNCTION__, lm3535_data.revision+1, value); + if (lm3535_data.revision == 0) { + lm3535_ramp = lm3535_ramp_r1; + lm3535_set_options_f = lm3535_set_options_r1; + } else if (lm3535_data.revision == 1) { + lm3535_ramp = lm3535_ramp_r2; + lm3535_set_options_f = lm3535_set_options_r2; + } else { + lm3535_ramp = lm3535_ramp_r3; + lm3535_set_options_f = lm3535_set_options_r3; + lm3535_data.nramp = 8; + } + + /* PWM */ + if (lm3535_data.revision < 4) { + pwm_value = 0; + } + atomic_set (&lm3535_data.als_zone, ALS_NO_ZONE); + atomic_set (&lm3535_data.use_als, 1); + atomic_set (&lm3535_data.do_als_config, 1); + ret = lm3535_configure (); + if (ret < 0) + return ret; + ret = lm3535_enable(client, 1); + if (ret < 0) + return ret; + + //hrtimer_init (&lm3535_data.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + //lm3535_data.timer.function = lm3535_timer_func; + //hrtimer_start(&lm3535_data.timer, ktime_set(2, 0), HRTIMER_MODE_REL); + + return ret; +} + +static int lm3535_remove (struct i2c_client *client) +{ + struct lm3535 *data_ptr = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&lm3535_data.als_delayed_work); + lm3535_data.prevent_als_read = 0; + if (data_ptr->use_irq) + free_irq (client->irq, data_ptr); + led_classdev_unregister (&lm3535_led); + led_classdev_unregister(&lm3535_led_noramp); +/* led_classdev_unregister (&lm3535_led_noramp); */ + misc_deregister (&als_miscdev); + device_remove_file (lm3535_led.dev, &dev_attr_suspend); +#ifdef CONFIG_ALS_WHILE_CHARGING + device_remove_file(lm3535_led.dev, &dev_attr_interactive); +#endif +#if 0 + input_unregister_device (lm3535_data.idev); + input_free_device (lm3535_data.idev); +#endif + +#ifdef CONFIG_WAKEUP_SOURCE_NOTIFY + wakeup_source_unregister_notify(&lm3535_data.dock_nb); + als_unregister_notify(&lm3535_data.als_nb); +#endif + return 0; +} + +#if defined(CONFIG_HAS_EARLYSUSPEND) || !defined(CONFIG_HAS_AMBIENTMODE) +static int lm3535_suspend (struct i2c_client *client, pm_message_t mesg) +{ + printk_suspend ("%s: called with pm message %d\n", + __FUNCTION__, mesg.event); + + led_classdev_suspend (&lm3535_led); + +#if 0 + /* Disable ALS interrupt */ + lm3535_write_reg (LM3535_CONFIG_REG, 0, __FUNCTION__); + /* Put ALS Resistor into high impedance mode to save current */ + lm3535_write_reg (LM3535_ALS_RESISTOR_REG, 0, __FUNCTION__); + + /* Reset ALS averaging */ + lm3535_write_reg (LM3535_ALS_CTRL_REG, 0, __FUNCTION__); + atomic_set (&lm3535_data.do_als_config, 1); +#endif + + gpio_set_value(LM3535_HWEN_GPIO, 0); + + return 0; +} + +static int lm3535_resume (struct i2c_client *client) +{ + printk_suspend ("%s: resuming\n", __FUNCTION__); + mutex_lock (&lm3535_mutex); + + /* If LED was disabled going into suspend, we don't want to enable yet + until brightness is set to non-zero value */ + if (lm3535_data.enabled == 0) { + gpio_set_value(LM3535_HWEN_GPIO, 1); + udelay(10); + + lm3535_configure(); + } + mutex_unlock (&lm3535_mutex); + led_classdev_resume (&lm3535_led); + printk_suspend ("%s: driver resumed\n", __FUNCTION__); + + return 0; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lm3535_early_suspend (struct early_suspend *h) +{ + lm3535_suspend (lm3535_data.client, PMSG_SUSPEND); +} + +static void lm3535_late_resume (struct early_suspend *h) +{ + lm3535_resume (lm3535_data.client); +} +#endif + + +static int __init lm3535_init (void) +{ + int ret; + + printk (KERN_INFO "%s: enter\n", __FUNCTION__); + ret = i2c_add_driver (&lm3535_driver); + if (ret) { + printk (KERN_ERR "%s: i2c_add_driver failed, error %d\n", + __FUNCTION__, ret); + } + + return ret; +} + +static void __exit lm3535_exit(void) +{ + i2c_del_driver (&lm3535_driver); +} + +static char *reg_name (int reg) +{ + switch (reg) { + case (LM3535_ENABLE_REG): return "ENABLE"; break; + case (LM3535_CONFIG_REG): return "CONFIG"; break; + case (LM3535_OPTIONS_REG): return "OPTIONS"; break; + case (LM3535_ALS_REG): return "ALS"; break; + case (LM3535_ALS_RESISTOR_REG): return "ALS_RESISTOR"; break; + case (LM3535_ALS_CTRL_REG): return "ALS CONTROL"; break; + case (LM3535_BRIGHTNESS_CTRL_REG_A): return "BRIGHTNESS_CTRL_A"; break; + case (LM3535_BRIGHTNESS_CTRL_REG_B): return "BRIGHTNESS_CTRL_B"; break; + case (LM3535_BRIGHTNESS_CTRL_REG_C): return "BRIGHTNESS_CTRL_C"; break; + case (LM3535_ALS_ZB0_REG): return "ALS_ZB0"; break; + case (LM3535_ALS_ZB1_REG): return "ALS_ZB1"; break; + case (LM3535_ALS_ZB2_REG): return "ALS_ZB2"; break; + case (LM3535_ALS_ZB3_REG): return "ALS_ZB3"; break; + case (LM3535_ALS_Z0T_REG): return "ALS_Z0T"; break; + case (LM3535_ALS_Z1T_REG): return "ALS_Z1T"; break; + case (LM3535_ALS_Z2T_REG): return "ALS_Z2T"; break; + case (LM3535_ALS_Z3T_REG): return "ALS_Z3T"; break; + case (LM3535_ALS_Z4T_REG): return "ALS_Z4T"; break; + case (LM3535_TRIM_REG): return "TRIM"; break; + default: return "UNKNOWN"; break; + } + return "UNKNOWN"; +} + +unsigned lmxxxx_detect_esd (void) +{ + uint8_t value = 0; + + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return 0; + } + //0 - no ESD, 1 - ESD + lm3535_read_reg (LM3535_ALS_RESISTOR_REG, &value); + if (value != (uint8_t)resistor_value) + return 1; + return 0; +} +EXPORT_SYMBOL(lmxxxx_detect_esd); + +void lmxxxx_fix_esd (void) +{ + if (!lm3535_data.initialized) { + printk (KERN_ERR "%s: not initialized\n", __FUNCTION__); + return; + } + mutex_lock (&lm3535_mutex); + lm3535_configure (); + mutex_unlock (&lm3535_mutex); + return; +} +EXPORT_SYMBOL(lmxxxx_fix_esd); + +void lmxxxx_set_pwm (unsigned en_dis) +{ + if (en_dis) { + pwm_value = 1; + } else { + pwm_value = 0; + } + printk (KERN_INFO "%s: setting PWM to %d\n", + __FUNCTION__, pwm_value); +} +EXPORT_SYMBOL(lmxxxx_set_pwm); +module_init(lm3535_init); +module_exit(lm3535_exit); + +MODULE_DESCRIPTION("LM3535 DISPLAY BACKLIGHT DRIVER"); +MODULE_LICENSE("GPL v2"); |