blob: 23b7558674e5ede6313a5d744a0efe761d12af8b [file] [log] [blame]
/*
* 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, &reg);
if (reg & 0x50) {
ret = lm3535_read_reg (LM3535_ALS_REG, &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, &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, &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");