1032 lines
24 KiB
C
1032 lines
24 KiB
C
/*
|
|
* This file is part of the AL3320 sensor driver.
|
|
* AL3320 is an ambient light sensor.
|
|
*
|
|
* Contact: YC Hou <yc.hou@liteonsemi.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This 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 St, Fifth Floor, Boston, MA
|
|
* 02110-1301 USA
|
|
*
|
|
*
|
|
* Filename: al3320.c
|
|
*
|
|
* Summary:
|
|
* AL3320 sensor dirver.
|
|
*
|
|
* Modification History:
|
|
* Date By Summary
|
|
* -------- -------- -------------------------------------------------------
|
|
* 12/26/12 YC Original Creation (Test version:1.0)
|
|
* 01/07/13 YC Add a-dummy and the recommand settings in intial fuction.
|
|
* 05/08/13 YC Update the range according to datasheet rev 1.17.
|
|
* Change to v1.02.
|
|
* 05/20/13 YC 1. Move up timer initial function to avoid fatal error.
|
|
* 2. Correct the polling condition in initial.
|
|
* 3. Move up reset action to fix the always reset error.
|
|
* Change to v1.03.
|
|
* 06/06/13 YC Add functions for set delay of HAL.
|
|
*/
|
|
|
|
#include "al3320.h"
|
|
|
|
/*
|
|
* register access helpers
|
|
*/
|
|
static struct wake_lock al3320b_wl;
|
|
|
|
static int __al3320_read_reg(struct i2c_client *client,
|
|
u32 reg, u8 mask, u8 shift)
|
|
{
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
u8 idx = 0xff;
|
|
|
|
ADD_TO_IDX(reg,idx)
|
|
return (data->reg_cache[idx] & mask) >> shift;
|
|
}
|
|
|
|
static int __al3320_write_reg(struct i2c_client *client,
|
|
u32 reg, u8 mask, u8 shift, u8 val)
|
|
{
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
u8 tmp;
|
|
u8 idx = 0xff;
|
|
|
|
ADD_TO_IDX(reg,idx)
|
|
if (idx >= AL3320_NUM_CACHABLE_REGS)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
tmp = data->reg_cache[idx];
|
|
tmp &= ~mask;
|
|
tmp |= val << shift;
|
|
|
|
ret = i2c_smbus_write_byte_data(client, reg, tmp);
|
|
if (!ret)
|
|
data->reg_cache[idx] = tmp;
|
|
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* internally used functions
|
|
*/
|
|
|
|
/* mode */
|
|
static int al3320_get_mode(struct i2c_client *client)
|
|
{
|
|
int ret;
|
|
|
|
ret = __al3320_read_reg(client, AL3320_MODE_COMMAND,
|
|
AL3320_MODE_MASK, AL3320_MODE_SHIFT);
|
|
return ret;
|
|
}
|
|
|
|
static int al3320_set_mode(struct i2c_client *client, int mode)
|
|
{
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
|
|
if (mode != al3320_get_mode(client))
|
|
{
|
|
__al3320_write_reg(client, AL3320_MODE_COMMAND,
|
|
AL3320_MODE_MASK, AL3320_MODE_SHIFT, mode);
|
|
|
|
if (als_polling)
|
|
{
|
|
/* Enable/Disable ALS */
|
|
if (ALS_ACTIVE & mode)
|
|
hrtimer_start(&data->light_timer, data->light_poll_delay, HRTIMER_MODE_REL);
|
|
else
|
|
{
|
|
hrtimer_cancel(&data->light_timer);
|
|
cancel_work_sync(&data->work_light);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* waiting time */
|
|
#if 0
|
|
static int al3320_get_waiting_time(struct i2c_client *client)
|
|
{
|
|
return __al3320_read_reg(client, AL3320_WAITING_TIME,
|
|
AL3320_WAITING_MASK, AL3320_WAITING_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
static int al3320_set_waiting_time(struct i2c_client *client, int wait_time)
|
|
{
|
|
int ret = __al3320_write_reg(client, AL3320_WAITING_TIME,
|
|
AL3320_WAITING_MASK, AL3320_WAITING_SHIFT, wait_time);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* INT enable */
|
|
#if 0
|
|
static int al3320_get_int_enable(struct i2c_client *client)
|
|
{
|
|
return __al3320_read_reg(client, AL3320_INT_ENABLE,
|
|
AL3320_INT_ENABLE_MASK, AL3320_INT_ENABLE_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
static int al3320_set_int_enable(struct i2c_client *client, int flag)
|
|
{
|
|
int ret = __al3320_write_reg(client, AL3320_INT_ENABLE,
|
|
AL3320_INT_ENABLE_MASK, AL3320_INT_ENABLE_SHIFT, flag);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* suspend enable */
|
|
#if 0
|
|
static int al3320_get_sus_enable(struct i2c_client *client)
|
|
{
|
|
return __al3320_read_reg(client, AL3320_INT_ENABLE,
|
|
AL3320_SUS_ENABLE_MASK, AL3320_SUS_ENABLE_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
static int al3320_set_sus_enable(struct i2c_client *client, int flag)
|
|
{
|
|
int ret = __al3320_write_reg(client, AL3320_INT_ENABLE,
|
|
AL3320_SUS_ENABLE_MASK, AL3320_SUS_ENABLE_SHIFT, flag);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* range */
|
|
static long al3320_get_range(struct i2c_client *client)
|
|
{
|
|
u8 idx, exgain;
|
|
|
|
exgain = __al3320_read_reg(client, AL3320_RAN_COMMAND,
|
|
AL3320_EXGAIN_MASK, AL3320_EXGAIN_SHIFT);
|
|
|
|
idx = __al3320_read_reg(client, AL3320_RAN_COMMAND,
|
|
AL3320_RAN_MASK, AL3320_RAN_SHIFT);
|
|
|
|
return (exgain ? al3320_range[idx] : al3320_range[idx+4]);
|
|
}
|
|
|
|
static int al3320_set_range(struct i2c_client *client, int range)
|
|
{
|
|
int adummy, ret;
|
|
|
|
switch(range)
|
|
{
|
|
case ALS_RAN_0: adummy = ALS_ADUMMY_0; break;
|
|
case ALS_RAN_1: adummy = ALS_ADUMMY_1; break;
|
|
case ALS_RAN_2: adummy = ALS_ADUMMY_2; break;
|
|
case ALS_RAN_3: adummy = ALS_ADUMMY_3; break;
|
|
default: adummy = 0;
|
|
}
|
|
|
|
ret = al3320_set_adummy(client, adummy);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
return __al3320_write_reg(client, AL3320_RAN_COMMAND,
|
|
AL3320_RAN_MASK, AL3320_RAN_SHIFT, range);;
|
|
}
|
|
|
|
/* exgain */
|
|
static int al3320_get_exgain(struct i2c_client *client)
|
|
{
|
|
u8 exgain = __al3320_read_reg(client, AL3320_RAN_COMMAND,
|
|
AL3320_EXGAIN_MASK, AL3320_EXGAIN_SHIFT);
|
|
|
|
return exgain;
|
|
}
|
|
|
|
static int al3320_set_exgain(struct i2c_client *client, int exgain)
|
|
{
|
|
return __al3320_write_reg(client, AL3320_RAN_COMMAND,
|
|
AL3320_EXGAIN_MASK, AL3320_EXGAIN_SHIFT, exgain);;
|
|
}
|
|
|
|
/* persist */
|
|
#if 0
|
|
static int al3320_get_persist(struct i2c_client *client)
|
|
{
|
|
return __al3320_read_reg(client, AL3320_ALS_PERSIST,
|
|
AL3320_PERSIST_MASK, AL3320_PERSIST_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
static int al3320_set_persist(struct i2c_client *client, int persist)
|
|
{
|
|
return __al3320_write_reg(client, AL3320_ALS_PERSIST,
|
|
AL3320_PERSIST_MASK, AL3320_PERSIST_SHIFT, persist);;
|
|
}
|
|
|
|
/* meantime */
|
|
#if 0
|
|
static int al3320_get_meantime(struct i2c_client *client)
|
|
{
|
|
return __al3320_read_reg(client, AL3320_ALS_MEANTIME,
|
|
AL3320_MEANTIME_MASK, AL3320_MEANTIME_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
static int al3320_set_meantime(struct i2c_client *client, int meantime)
|
|
{
|
|
return __al3320_write_reg(client, AL3320_ALS_MEANTIME,
|
|
AL3320_MEANTIME_MASK, AL3320_MEANTIME_SHIFT, meantime);;
|
|
}
|
|
|
|
/* a-dummy */
|
|
#if 0
|
|
static int al3320_get_adummy(struct i2c_client *client)
|
|
{
|
|
return __al3320_read_reg(client, AL3320_ALS_ADUMMY,
|
|
AL3320_ADUMMY_MASK, AL3320_ADUMMY_SHIFT);
|
|
}
|
|
#endif
|
|
|
|
static int al3320_set_adummy(struct i2c_client *client, int adummy)
|
|
{
|
|
return __al3320_write_reg(client, AL3320_ALS_ADUMMY,
|
|
AL3320_ADUMMY_MASK, AL3320_ADUMMY_SHIFT, adummy);;
|
|
}
|
|
|
|
/* ALS low threshold */
|
|
static int al3320_get_althres(struct i2c_client *client)
|
|
{
|
|
int lsb, msb;
|
|
lsb = __al3320_read_reg(client, AL3320_ALS_LTHL,
|
|
AL3320_ALS_LTHL_MASK, AL3320_ALS_LTHL_SHIFT);
|
|
msb = __al3320_read_reg(client, AL3320_ALS_LTHH,
|
|
AL3320_ALS_LTHH_MASK, AL3320_ALS_LTHH_SHIFT);
|
|
return ((msb << 8) | lsb);
|
|
}
|
|
|
|
static int al3320_set_althres(struct i2c_client *client, int val)
|
|
{
|
|
int lsb, msb, err;
|
|
|
|
msb = val >> 8;
|
|
lsb = val & AL3320_ALS_LTHL_MASK;
|
|
|
|
err = __al3320_write_reg(client, AL3320_ALS_LTHL,
|
|
AL3320_ALS_LTHL_MASK, AL3320_ALS_LTHL_SHIFT, lsb);
|
|
if (err)
|
|
return err;
|
|
|
|
err = __al3320_write_reg(client, AL3320_ALS_LTHH,
|
|
AL3320_ALS_LTHH_MASK, AL3320_ALS_LTHH_SHIFT, msb);
|
|
|
|
return err;
|
|
}
|
|
|
|
/* ALS high threshold */
|
|
static int al3320_get_ahthres(struct i2c_client *client)
|
|
{
|
|
int lsb, msb;
|
|
lsb = __al3320_read_reg(client, AL3320_ALS_HTHL,
|
|
AL3320_ALS_HTHL_MASK, AL3320_ALS_HTHL_SHIFT);
|
|
msb = __al3320_read_reg(client, AL3320_ALS_HTHH,
|
|
AL3320_ALS_HTHH_MASK, AL3320_ALS_HTHH_SHIFT);
|
|
return ((msb << 8) | lsb);
|
|
}
|
|
|
|
static int al3320_set_ahthres(struct i2c_client *client, int val)
|
|
{
|
|
int lsb, msb, err;
|
|
|
|
msb = val >> 8;
|
|
lsb = val & AL3320_ALS_HTHL_MASK;
|
|
|
|
err = __al3320_write_reg(client, AL3320_ALS_HTHL,
|
|
AL3320_ALS_HTHL_MASK, AL3320_ALS_HTHL_SHIFT, lsb);
|
|
if (err)
|
|
return err;
|
|
|
|
err = __al3320_write_reg(client, AL3320_ALS_HTHH,
|
|
AL3320_ALS_HTHH_MASK, AL3320_ALS_HTHH_SHIFT, msb);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int al3320_get_adc_value(struct i2c_client *client, int lock)
|
|
{
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
int lsb, msb;
|
|
unsigned long tmp, range;
|
|
|
|
if (!lock) mutex_lock(&data->lock);
|
|
lsb = i2c_smbus_read_byte_data(client, AL3320_ADC_LSB);
|
|
|
|
if (lsb < 0) {
|
|
if (!lock) mutex_unlock(&data->lock);
|
|
return lsb;
|
|
}
|
|
|
|
msb = i2c_smbus_read_byte_data(client, AL3320_ADC_MSB);
|
|
if (!lock) mutex_unlock(&data->lock);
|
|
|
|
if (msb < 0)
|
|
return msb;
|
|
|
|
range = al3320_get_range(client);
|
|
|
|
tmp = (((msb << 8) | lsb) * range) >> 16;
|
|
tmp *= cali;
|
|
|
|
return (tmp / 100);
|
|
}
|
|
|
|
static int al3320_clean_int(struct i2c_client *client)
|
|
{
|
|
int err;
|
|
|
|
err = __al3320_write_reg(client, AL3320_INT_COMMAND,
|
|
AL3320_INT_MASK, AL3320_INT_SHIFT, 0);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* sysfs layer
|
|
*/
|
|
|
|
/* als_poll_delay */
|
|
static ssize_t al3320_show_als_poll_delay(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
|
|
return sprintf( buf, "%d\n", do_div( ktime_to_ns(data->light_poll_delay), 1000) );
|
|
}
|
|
|
|
static ssize_t al3320_store_als_poll_delay(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
unsigned long val;
|
|
|
|
if (strict_strtoul(buf, 10, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
data->light_poll_delay = ns_to_ktime(val*1000);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(als_poll_delay, S_IWUSR | S_IWGRP | S_IRUGO,
|
|
al3320_show_als_poll_delay, al3320_store_als_poll_delay);
|
|
|
|
/* range */
|
|
static ssize_t al3320_show_range(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
return sprintf(buf, "%d\n", al3320_get_range(data->client));
|
|
}
|
|
|
|
static ssize_t al3320_store_range(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((strict_strtoul(buf, 10, &val) < 0) || (val > 3))
|
|
return -EINVAL;
|
|
|
|
ret = al3320_set_range(data->client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
|
|
al3320_show_range, al3320_store_range);
|
|
|
|
/* exgain */
|
|
static ssize_t al3320_show_exgain(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
return sprintf(buf, "%i\n", al3320_get_exgain(data->client));
|
|
}
|
|
|
|
static ssize_t al3320_store_exgain(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((strict_strtoul(buf, 10, &val) < 0) || (val > 1))
|
|
return -EINVAL;
|
|
|
|
ret = al3320_set_exgain(data->client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(exgain, S_IWUSR | S_IRUGO,
|
|
al3320_show_exgain, al3320_store_exgain);
|
|
|
|
/* mode */
|
|
static ssize_t al3320_show_mode(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
return sprintf(buf, "%d\n", al3320_get_mode(data->client));
|
|
}
|
|
|
|
static ssize_t al3320_store_mode(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((strict_strtoul(buf, 10, &val) < 0) || (val > 4))
|
|
return -EINVAL;
|
|
|
|
ret = al3320_set_mode(data->client, val);
|
|
|
|
if (ret < 0)
|
|
return ret;
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(mode, S_IWUSR | S_IWGRP | S_IRUGO,
|
|
al3320_show_mode, al3320_store_mode);
|
|
|
|
|
|
/* lux */
|
|
static ssize_t al3320_show_lux(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
|
|
|
|
|
|
/* No LUX data if power down */
|
|
if (al3320_get_mode(data->client) == 0x00){
|
|
|
|
wake_lock_timeout(&al3320b_wl, 10*HZ);
|
|
|
|
return sprintf((char*) buf, "%s\n", "Please power up first!");
|
|
}
|
|
|
|
|
|
return sprintf(buf, "%d\n", al3320_get_adc_value(data->client,0));
|
|
}
|
|
|
|
static DEVICE_ATTR(lux, S_IRUGO, al3320_show_lux, NULL);
|
|
|
|
|
|
/* ALS low threshold */
|
|
static ssize_t al3320_show_althres(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
return sprintf(buf, "%d\n", al3320_get_althres(data->client));
|
|
}
|
|
|
|
static ssize_t al3320_store_althres(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if (strict_strtoul(buf, 10, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
ret = al3320_set_althres(data->client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(althres, S_IWUSR | S_IRUGO,
|
|
al3320_show_althres, al3320_store_althres);
|
|
|
|
|
|
/* ALS high threshold */
|
|
static ssize_t al3320_show_ahthres(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
return sprintf(buf, "%d\n", al3320_get_ahthres(data->client));
|
|
}
|
|
|
|
static ssize_t al3320_store_ahthres(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if (strict_strtoul(buf, 10, &val) < 0)
|
|
return -EINVAL;
|
|
|
|
ret = al3320_set_ahthres(data->client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(ahthres, S_IWUSR | S_IRUGO,
|
|
al3320_show_ahthres, al3320_store_ahthres);
|
|
|
|
|
|
/* calibration */
|
|
static ssize_t al3320_show_calibration_state(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", cali);
|
|
}
|
|
|
|
static ssize_t al3320_store_calibration_state(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct input_dev *input = to_input_dev(dev);
|
|
struct al3320_data *data = input_get_drvdata(input);
|
|
int stdls, lux;
|
|
char tmp[10];
|
|
|
|
/* No LUX data if not operational */
|
|
if (al3320_get_mode(data->client) == 0x00)
|
|
{
|
|
printk("Please power up first!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(BLADE2_8) || defined(BLADE2_10)
|
|
cali = 660;
|
|
#elif defined(BLADE2_13)
|
|
cali = 100;
|
|
#endif
|
|
sscanf(buf, "%d %s", &stdls, tmp);
|
|
|
|
if (!strncmp(tmp, "-setcv", 6))
|
|
{
|
|
cali = stdls;
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (stdls < 0)
|
|
{
|
|
printk("Std light source: [%d] < 0 !!!\nCheck again, please.\n\
|
|
Set calibration factor to 100.\n", stdls);
|
|
return -EBUSY;
|
|
}
|
|
|
|
lux = al3320_get_adc_value(data->client, 0);
|
|
cali = stdls * 100 / lux;
|
|
|
|
return -EBUSY;
|
|
}
|
|
|
|
static DEVICE_ATTR(calibration, S_IWUSR | S_IRUGO,
|
|
al3320_show_calibration_state, al3320_store_calibration_state);
|
|
|
|
#ifdef LSC_DBG
|
|
/* engineer mode */
|
|
static ssize_t al3320_em_read(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
int i;
|
|
u8 tmp;
|
|
|
|
for (i = 0; i < AL3320_NUM_CACHABLE_REGS; i++)
|
|
{
|
|
mutex_lock(&data->lock);
|
|
tmp = i2c_smbus_read_byte_data(data->client, al3320_reg[i]);
|
|
mutex_unlock(&data->lock);
|
|
|
|
printk("Reg[0x%x] Val[0x%x]\n", al3320_reg[i], tmp);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t al3320_em_write(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
u32 addr,val,idx=0;
|
|
int ret = 0;
|
|
|
|
sscanf(buf, "%x%x", &addr, &val);
|
|
|
|
printk("Write [%x] to Reg[%x]...\n",val,addr);
|
|
mutex_lock(&data->lock);
|
|
|
|
ret = i2c_smbus_write_byte_data(data->client, addr, val);
|
|
ADD_TO_IDX(addr,idx)
|
|
if (!ret)
|
|
data->reg_cache[idx] = val;
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
return count;
|
|
}
|
|
static DEVICE_ATTR(em, S_IWUSR |S_IRUGO,
|
|
al3320_em_read, al3320_em_write);
|
|
#endif
|
|
|
|
static struct attribute *al3320_als_attributes[] = {
|
|
&dev_attr_als_poll_delay.attr,
|
|
&dev_attr_range.attr,
|
|
&dev_attr_exgain.attr,
|
|
&dev_attr_mode.attr,
|
|
&dev_attr_lux.attr,
|
|
&dev_attr_althres.attr,
|
|
&dev_attr_ahthres.attr,
|
|
&dev_attr_calibration.attr,
|
|
#ifdef LSC_DBG
|
|
&dev_attr_em.attr,
|
|
#endif
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group al3320_als_attr_group = {
|
|
.attrs = al3320_als_attributes,
|
|
};
|
|
|
|
static int al3320_init_client(struct i2c_client *client)
|
|
{
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
int i;
|
|
|
|
// reset
|
|
al3320_set_mode(client, ALS_RESET);
|
|
mdelay(15);
|
|
|
|
/* read all the registers once to fill the cache.
|
|
* if one of the reads fails, we consider the init failed */
|
|
for (i = 0; i < AL3320_NUM_CACHABLE_REGS; i++) {
|
|
int v = i2c_smbus_read_byte_data(client, al3320_reg[i]);
|
|
if (v < 0)
|
|
return -ENODEV;
|
|
|
|
data->reg_cache[i] = v;
|
|
}
|
|
|
|
/* set defaults */
|
|
|
|
// ALS waiting time
|
|
al3320_set_waiting_time(client, ALS_NO_WAITING);
|
|
mdelay(1);
|
|
|
|
// ALS gain
|
|
al3320_set_range(client, als_range);
|
|
mdelay(1);
|
|
al3320_set_exgain(client, als_exgain);
|
|
mdelay(1);
|
|
|
|
// ALS meantime
|
|
al3320_set_meantime(client, als_meantime);
|
|
mdelay(1);
|
|
|
|
// interrupt, suspend settings
|
|
al3320_set_sus_enable(client, DISABLE);
|
|
mdelay(1);
|
|
|
|
if (als_polling)
|
|
al3320_set_int_enable(client, DISABLE);
|
|
else
|
|
{
|
|
al3320_set_althres(client, als_low_threshold);
|
|
al3320_set_ahthres(client, als_high_threshold);
|
|
mdelay(1);
|
|
|
|
// ALS persist
|
|
al3320_set_persist(client, als_persist);
|
|
}
|
|
|
|
mdelay(1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void al3320_work_func_light(struct work_struct *work)
|
|
{
|
|
struct al3320_data *data = container_of(work, struct al3320_data, work_light);
|
|
int Aval;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
Aval = al3320_get_adc_value(data->client,1);
|
|
// printk("ALS lux value: %u\n", Aval);
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
input_report_abs(data->light_input_dev, ABS_MISC, Aval);
|
|
input_sync(data->light_input_dev);
|
|
}
|
|
|
|
static enum hrtimer_restart al3320_light_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct al3320_data *data = container_of(timer, struct al3320_data, light_timer);
|
|
queue_work(data->wq, &data->work_light);
|
|
hrtimer_forward_now(&data->light_timer, data->light_poll_delay);
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static void al3320_timer_init(struct al3320_data *data)
|
|
{
|
|
if (als_polling)
|
|
{
|
|
/* light hrtimer settings. */
|
|
hrtimer_init(&data->light_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
data->light_poll_delay = ns_to_ktime(als_poll_delay * NSEC_PER_MSEC);
|
|
data->light_timer.function = al3320_light_timer_func;
|
|
}
|
|
}
|
|
|
|
static int al3320_input_init(struct al3320_data *data)
|
|
{
|
|
struct input_dev *input_dev;
|
|
int ret;
|
|
|
|
/* allocate light input_device */
|
|
input_dev = input_allocate_device();
|
|
if (!input_dev) {
|
|
LDBG("could not allocate input device\n");
|
|
goto err_light_all;
|
|
}
|
|
input_set_drvdata(input_dev, data);
|
|
input_dev->name = "light";
|
|
input_set_capability(input_dev, EV_ABS, ABS_MISC);
|
|
input_set_abs_params(input_dev, ABS_MISC, 0, 1, 0, 0);
|
|
|
|
LDBG("registering light sensor input device\n");
|
|
ret = input_register_device(input_dev);
|
|
if (ret < 0) {
|
|
LDBG("could not register input device\n");
|
|
goto err_light_reg;
|
|
}
|
|
data->light_input_dev = input_dev;
|
|
ret = sysfs_create_group(&input_dev->dev.kobj, &al3320_als_attr_group);
|
|
if (ret) {
|
|
LDBG("could not create sysfs group\n");
|
|
goto err_light_sys;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_light_sys:
|
|
input_unregister_device(data->light_input_dev);
|
|
err_light_reg:
|
|
input_free_device(input_dev);
|
|
err_light_all:
|
|
|
|
return (-1);
|
|
}
|
|
|
|
static void al3320_input_fini(struct al3320_data *data)
|
|
{
|
|
struct input_dev *dev = data->light_input_dev;
|
|
|
|
input_unregister_device(dev);
|
|
input_free_device(dev);
|
|
}
|
|
|
|
/*
|
|
* I2C layer
|
|
*/
|
|
|
|
static irqreturn_t al3320_irq(int irq, void *data_)
|
|
{
|
|
struct al3320_data *data = data_;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
// ALS int
|
|
queue_work(data->wq, &data->work_light);
|
|
|
|
// clean interrupt flag
|
|
al3320_clean_int(data->client);
|
|
|
|
mutex_unlock(&data->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void al3320_early_suspend(struct early_suspend *h)
|
|
{
|
|
struct al3320_data *data = container_of(h, struct al3320_data, early_suspend);
|
|
|
|
LDBG("early suspend\n");
|
|
|
|
if ((al3320_get_mode(data->client) & ALS_ACTIVE))
|
|
suspend_mode = ALS_ACTIVE;
|
|
|
|
al3320_set_mode(data->client, ALS_DEACTIVE);
|
|
}
|
|
|
|
static void al3320_late_resume(struct early_suspend *h)
|
|
{
|
|
struct al3320_data *data = container_of(h, struct al3320_data, early_suspend);
|
|
|
|
LDBG("late resume\n");
|
|
|
|
if (suspend_mode)
|
|
{
|
|
al3320_set_mode(data->client, ALS_ACTIVE);
|
|
suspend_mode = ALS_DEACTIVE;
|
|
}
|
|
}
|
|
|
|
static int al3320_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
struct al3320_data *data;
|
|
int err = 0;
|
|
|
|
wake_lock_init(&al3320b_wl, WAKE_LOCK_SUSPEND, "al3320b");
|
|
|
|
dev_dbg(&client->dev, "%s\n", __func__);
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
|
|
return -EIO;
|
|
|
|
data = kzalloc(sizeof(struct al3320_data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->client = client;
|
|
i2c_set_clientdata(client, data);
|
|
mutex_init(&data->lock);
|
|
|
|
al3320_timer_init(data);
|
|
|
|
/* initialize the AL3320 chip */
|
|
err = al3320_init_client(client);
|
|
if (err)
|
|
goto exit_kfree;
|
|
|
|
err = al3320_input_init(data);
|
|
if (err)
|
|
goto exit_kfree;
|
|
|
|
if (!als_polling)
|
|
{
|
|
data->irq = client->irq;
|
|
err = request_threaded_irq(client->irq, NULL, al3320_irq,
|
|
IRQF_TRIGGER_FALLING,
|
|
"al3320", data);
|
|
|
|
if (err) {
|
|
dev_err(&client->dev, "ret: %d, could not get IRQ %d\n",err,client->irq);
|
|
goto exit_irq;
|
|
}
|
|
}
|
|
|
|
INIT_WORK(&data->work_light, al3320_work_func_light);
|
|
|
|
data->wq = create_singlethread_workqueue("al3320_wq");
|
|
if (!data->wq) {
|
|
LDBG("could not create workqueue\n");
|
|
goto exit_work;
|
|
}
|
|
|
|
data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN;
|
|
data->early_suspend.suspend = al3320_early_suspend;
|
|
data->early_suspend.resume = al3320_late_resume;
|
|
register_early_suspend(&data->early_suspend);
|
|
|
|
dev_info(&client->dev, "Driver version %s enabled\n", DRIVER_VERSION);
|
|
return 0;
|
|
|
|
exit_work:
|
|
destroy_workqueue(data->wq);
|
|
|
|
exit_irq:
|
|
al3320_input_fini(data);
|
|
|
|
exit_kfree:
|
|
mutex_destroy(&data->lock);
|
|
kfree(data);
|
|
return err;
|
|
}
|
|
|
|
static int al3320_remove(struct i2c_client *client)
|
|
{
|
|
struct al3320_data *data = i2c_get_clientdata(client);
|
|
|
|
if (!als_polling)
|
|
free_irq(data->irq, data);
|
|
sysfs_remove_group(&data->light_input_dev->dev.kobj, &al3320_als_attr_group);
|
|
input_unregister_device(data->light_input_dev);
|
|
|
|
if (data->reg_cache[0] & ALS_ACTIVE) {
|
|
hrtimer_cancel(&data->light_timer);
|
|
cancel_work_sync(&data->work_light);
|
|
}
|
|
|
|
destroy_workqueue(data->wq);
|
|
mutex_destroy(&data->lock);
|
|
unregister_early_suspend(&data->early_suspend);
|
|
kfree(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct i2c_device_id al3320_id[] = {
|
|
{ "al3320", 0 },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, al3320_id);
|
|
|
|
static struct i2c_driver al3320_driver = {
|
|
.driver = {
|
|
.name = AL3320_DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
},
|
|
.probe = al3320_probe,
|
|
.remove = al3320_remove,
|
|
.id_table = al3320_id,
|
|
};
|
|
|
|
static int __init al3320_init(void)
|
|
{
|
|
int i2c_busnum = 5;
|
|
struct i2c_board_info i2c_info;
|
|
void *pdata = NULL;
|
|
|
|
memset(&i2c_info, 0, sizeof(i2c_info));
|
|
strlcpy(i2c_info.type, AL3320_DRV_NAME, sizeof(AL3320_DRV_NAME));
|
|
|
|
i2c_info.addr = 0x1C;
|
|
i2c_info.irq = 102+15;
|
|
|
|
pr_info("I2C bus = %d, name = %16.16s, irq = 0x%2x, addr = 0x%x\n",
|
|
i2c_busnum,
|
|
i2c_info.type,
|
|
i2c_info.irq,
|
|
i2c_info.addr);
|
|
|
|
i2c_add_driver(&al3320_driver);
|
|
|
|
return i2c_register_board_info(i2c_busnum, &i2c_info, 1);
|
|
}
|
|
|
|
static void __exit al3320_exit(void)
|
|
{
|
|
i2c_del_driver(&al3320_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("yc.hou@liteonsemi.com");
|
|
MODULE_DESCRIPTION("AL3320 driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
|
fs_initcall(al3320_init);
|
|
module_exit(al3320_exit);
|
|
|