1078 lines
26 KiB
C
1078 lines
26 KiB
C
/*
|
|
* Copyright (C) 2011-2014 Freescale Semiconductor, Inc. All Rights Reserved.
|
|
*/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License 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.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/input.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/isl29023.h>
|
|
|
|
#define ISL29023_DRV_NAME "isl29023"
|
|
#define DRIVER_VERSION "1.0"
|
|
|
|
#define ISL29023_COMMAND1 (0x00)
|
|
#define ISL29023_MODE_SHIFT (5)
|
|
#define ISL29023_MODE_MASK (0x7 << ISL29023_MODE_SHIFT)
|
|
#define ISL29023_INT_FLAG_SHIFT (2)
|
|
#define ISL29023_INT_FLAG_MASK (0x1 << ISL29023_INT_FLAG_SHIFT)
|
|
#define ISL29023_INT_PERSISTS_SHIFT (0)
|
|
#define ISL29023_INT_PERSISTS_MASK (0x3 << ISL29023_INT_PERSISTS_SHIFT)
|
|
|
|
#define ISL29023_COMMAND2 (0x01)
|
|
#define ISL29023_RES_SHIFT (2)
|
|
#define ISL29023_RES_MASK (0x3 << ISL29023_RES_SHIFT)
|
|
#define ISL29023_RANGE_SHIFT (0)
|
|
#define ISL29023_RANGE_MASK (0x3 << ISL29023_RANGE_SHIFT)
|
|
|
|
#define ISL29023_REG_LSB_SENSOR (0x02)
|
|
#define ISL29023_REG_MSB_SENSOR (0x03)
|
|
#define ISL29023_REG_IRQ_TH_LO_LSB (0x04)
|
|
#define ISL29023_REG_IRQ_TH_LO_MSB (0x05)
|
|
#define ISL29023_REG_IRQ_TH_HI_LSB (0x06)
|
|
#define ISL29023_REG_IRQ_TH_HI_MSB (0x07)
|
|
|
|
#define ISL29023_NUM_CACHABLE_REGS 8
|
|
#define DEF_RANGE 2
|
|
#define DEFAULT_REGISTOR_VAL 499
|
|
|
|
struct isl29023_data {
|
|
struct i2c_client *client;
|
|
struct mutex lock;
|
|
struct input_dev *input;
|
|
struct work_struct work;
|
|
struct workqueue_struct *workqueue;
|
|
char phys[32];
|
|
u8 reg_cache[ISL29023_NUM_CACHABLE_REGS];
|
|
u8 mode_before_suspend;
|
|
u8 mode_before_interrupt;
|
|
u16 rext;
|
|
};
|
|
|
|
static int gain_range[] = {
|
|
1000, 4000, 16000, 64000
|
|
};
|
|
|
|
/*
|
|
* register access helpers
|
|
*/
|
|
static int __isl29023_read_reg(struct i2c_client *client,
|
|
u32 reg, u8 mask, u8 shift)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
return (data->reg_cache[reg] & mask) >> shift;
|
|
}
|
|
|
|
static int __isl29023_write_reg(struct i2c_client *client,
|
|
u32 reg, u8 mask, u8 shift, u8 val)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int ret = 0;
|
|
u8 tmp;
|
|
|
|
if (reg >= ISL29023_NUM_CACHABLE_REGS)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&data->lock);
|
|
|
|
tmp = data->reg_cache[reg];
|
|
tmp &= ~mask;
|
|
tmp |= val << shift;
|
|
|
|
ret = i2c_smbus_write_byte_data(client, reg, tmp);
|
|
if (!ret)
|
|
data->reg_cache[reg] = tmp;
|
|
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* internally used functions
|
|
*/
|
|
static int isl29023_get_int_persists(struct i2c_client *client)
|
|
{
|
|
return __isl29023_read_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_INT_PERSISTS_MASK, ISL29023_INT_PERSISTS_SHIFT);
|
|
}
|
|
|
|
static int isl29023_set_int_persists(struct i2c_client *client,
|
|
int int_persists)
|
|
{
|
|
return __isl29023_write_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_INT_PERSISTS_MASK, ISL29023_INT_PERSISTS_SHIFT,
|
|
int_persists);
|
|
}
|
|
|
|
/*
|
|
* interrupt flag
|
|
*/
|
|
static int isl29023_get_int_flag(struct i2c_client *client)
|
|
{
|
|
return __isl29023_read_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_INT_FLAG_MASK, ISL29023_INT_FLAG_SHIFT);
|
|
}
|
|
|
|
static int isl29023_set_int_flag(struct i2c_client *client, int flag)
|
|
{
|
|
return __isl29023_write_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_INT_FLAG_MASK, ISL29023_INT_FLAG_SHIFT, flag);
|
|
}
|
|
|
|
/*
|
|
* interrupt lt
|
|
*/
|
|
static int isl29023_get_int_lt(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int lsb, msb, lt;
|
|
|
|
mutex_lock(&data->lock);
|
|
lsb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_LO_LSB);
|
|
|
|
if (lsb < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return lsb;
|
|
}
|
|
|
|
msb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_LO_MSB);
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (msb < 0)
|
|
return msb;
|
|
|
|
lt = ((msb << 8) | lsb);
|
|
|
|
return lt;
|
|
}
|
|
|
|
static int isl29023_set_int_lt(struct i2c_client *client, int lt)
|
|
{
|
|
int ret = 0;
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_LO_LSB,
|
|
lt & 0xff);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_LO_MSB,
|
|
(lt >> 8) & 0xff);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_LO_MSB] = (lt >> 8) & 0xff;
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_LO_LSB] = lt & 0xff;
|
|
mutex_unlock(&data->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* interrupt ht
|
|
*/
|
|
static int isl29023_get_int_ht(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int lsb, msb, ht;
|
|
|
|
mutex_lock(&data->lock);
|
|
lsb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_HI_LSB);
|
|
|
|
if (lsb < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return lsb;
|
|
}
|
|
|
|
msb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_HI_MSB);
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (msb < 0)
|
|
return msb;
|
|
|
|
ht = ((msb << 8) | lsb);
|
|
|
|
return ht;
|
|
}
|
|
|
|
static int isl29023_set_int_ht(struct i2c_client *client, int ht)
|
|
{
|
|
int ret = 0;
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_HI_LSB,
|
|
ht & 0xff);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_HI_MSB,
|
|
(ht >> 8) & 0xff);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_HI_MSB] = (ht >> 8) & 0xff;
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_HI_LSB] = ht & 0xff;
|
|
mutex_unlock(&data->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* range
|
|
*/
|
|
static int isl29023_get_range(struct i2c_client *client)
|
|
{
|
|
return __isl29023_read_reg(client, ISL29023_COMMAND2,
|
|
ISL29023_RANGE_MASK, ISL29023_RANGE_SHIFT);
|
|
}
|
|
|
|
static int isl29023_set_range(struct i2c_client *client, int range)
|
|
{
|
|
return __isl29023_write_reg(client, ISL29023_COMMAND2,
|
|
ISL29023_RANGE_MASK, ISL29023_RANGE_SHIFT, range);
|
|
}
|
|
|
|
/*
|
|
* resolution
|
|
*/
|
|
static int isl29023_get_resolution(struct i2c_client *client)
|
|
{
|
|
return __isl29023_read_reg(client, ISL29023_COMMAND2,
|
|
ISL29023_RES_MASK, ISL29023_RES_SHIFT);
|
|
}
|
|
|
|
static int isl29023_set_resolution(struct i2c_client *client, int res)
|
|
{
|
|
return __isl29023_write_reg(client, ISL29023_COMMAND2,
|
|
ISL29023_RES_MASK, ISL29023_RES_SHIFT, res);
|
|
}
|
|
|
|
/*
|
|
* mode
|
|
*/
|
|
static int isl29023_get_mode(struct i2c_client *client)
|
|
{
|
|
return __isl29023_read_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_MODE_MASK, ISL29023_MODE_SHIFT);
|
|
}
|
|
|
|
static int isl29023_set_mode(struct i2c_client *client, int mode)
|
|
{
|
|
return __isl29023_write_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_MODE_MASK, ISL29023_MODE_SHIFT, mode);
|
|
}
|
|
|
|
/*
|
|
* power_state
|
|
*/
|
|
static int isl29023_set_power_state(struct i2c_client *client, int state)
|
|
{
|
|
return __isl29023_write_reg(client, ISL29023_COMMAND1,
|
|
ISL29023_MODE_MASK, ISL29023_MODE_SHIFT,
|
|
state ?
|
|
ISL29023_ALS_ONCE_MODE : ISL29023_PD_MODE);
|
|
}
|
|
|
|
static int isl29023_get_power_state(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
u8 cmdreg = data->reg_cache[ISL29023_COMMAND1];
|
|
|
|
if (cmdreg & ISL29023_MODE_MASK)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int isl29023_get_adc_value(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int lsb, msb, range, bitdepth;
|
|
|
|
mutex_lock(&data->lock);
|
|
lsb = i2c_smbus_read_byte_data(client, ISL29023_REG_LSB_SENSOR);
|
|
|
|
if (lsb < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return lsb;
|
|
}
|
|
|
|
msb = i2c_smbus_read_byte_data(client, ISL29023_REG_MSB_SENSOR);
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (msb < 0)
|
|
return msb;
|
|
|
|
range = isl29023_get_range(client);
|
|
bitdepth = (4 - isl29023_get_resolution(client)) * 4;
|
|
return (((msb << 8) | lsb) * ((gain_range[range] * 499) / data->rext))
|
|
>> bitdepth;
|
|
}
|
|
|
|
static int isl29023_get_int_lt_value(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int lsb, msb, range, bitdepth;
|
|
|
|
mutex_lock(&data->lock);
|
|
lsb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_LO_LSB);
|
|
|
|
if (lsb < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return lsb;
|
|
}
|
|
|
|
msb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_LO_MSB);
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (msb < 0)
|
|
return msb;
|
|
|
|
range = isl29023_get_range(client);
|
|
bitdepth = (4 - isl29023_get_resolution(client)) * 4;
|
|
return (((msb << 8) | lsb) * ((gain_range[range] * 499) / data->rext))
|
|
>> bitdepth;
|
|
}
|
|
|
|
static int isl29023_get_int_ht_value(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int lsb, msb, range, bitdepth;
|
|
|
|
mutex_lock(&data->lock);
|
|
lsb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_HI_LSB);
|
|
|
|
if (lsb < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return lsb;
|
|
}
|
|
|
|
msb = i2c_smbus_read_byte_data(client, ISL29023_REG_IRQ_TH_HI_MSB);
|
|
mutex_unlock(&data->lock);
|
|
|
|
if (msb < 0)
|
|
return msb;
|
|
|
|
range = isl29023_get_range(client);
|
|
bitdepth = (4 - isl29023_get_resolution(client)) * 4;
|
|
return (((msb << 8) | lsb) * ((gain_range[range] * 499) / data->rext))
|
|
>> bitdepth;
|
|
}
|
|
|
|
/*
|
|
* sysfs layer
|
|
*/
|
|
|
|
/*
|
|
* interrupt persists
|
|
*/
|
|
static ssize_t isl29023_show_int_persists(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%i\n", isl29023_get_int_persists(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_int_persists(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) ||
|
|
(val > ISL29023_INT_PERSISTS_16))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_int_persists(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(int_persists, S_IWUSR | S_IRUGO,
|
|
isl29023_show_int_persists, isl29023_store_int_persists);
|
|
|
|
/*
|
|
*interrupt flag
|
|
*/
|
|
static ssize_t isl29023_show_int_flag(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%i\n", isl29023_get_int_flag(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_int_flag(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) || (val > 1))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_int_flag(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(int_flag, S_IWUSR | S_IRUGO,
|
|
isl29023_show_int_flag, isl29023_store_int_flag);
|
|
|
|
/*
|
|
* interrupt lt
|
|
*/
|
|
static ssize_t isl29023_show_int_lt(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%i\n", isl29023_get_int_lt(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_int_lt(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 16, &val) < 0) || (val > 0xffff))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_int_lt(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(int_lt, S_IWUSR | S_IRUGO,
|
|
isl29023_show_int_lt, isl29023_store_int_lt);
|
|
|
|
/*
|
|
*interrupt ht
|
|
*/
|
|
static ssize_t isl29023_show_int_ht(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%i\n", isl29023_get_int_ht(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_int_ht(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 16, &val) < 0) || (val > 0xffff))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_int_ht(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(int_ht, S_IWUSR | S_IRUGO,
|
|
isl29023_show_int_ht, isl29023_store_int_ht);
|
|
|
|
/*
|
|
* range
|
|
*/
|
|
static ssize_t isl29023_show_range(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%i\n", isl29023_get_range(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_range(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) || (val > ISL29023_RANGE_64K))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_range(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(range, S_IWUSR | S_IRUGO,
|
|
isl29023_show_range, isl29023_store_range);
|
|
|
|
|
|
/*
|
|
* resolution
|
|
*/
|
|
static ssize_t isl29023_show_resolution(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%d\n", isl29023_get_resolution(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_resolution(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) || (val > ISL29023_RES_4))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_resolution(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(resolution, S_IWUSR | S_IRUGO,
|
|
isl29023_show_resolution, isl29023_store_resolution);
|
|
|
|
/*
|
|
*mode
|
|
*/
|
|
static ssize_t isl29023_show_mode(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%d\n", isl29023_get_mode(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_mode(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) ||
|
|
(val > ISL29023_IR_CONT_MODE))
|
|
return -EINVAL;
|
|
|
|
/* clear the interrupt flag */
|
|
i2c_smbus_read_byte_data(client, ISL29023_COMMAND1);
|
|
ret = isl29023_set_mode(client, val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
|
|
isl29023_show_mode, isl29023_store_mode);
|
|
|
|
|
|
/*
|
|
*power state
|
|
*/
|
|
static ssize_t isl29023_show_power_state(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
return sprintf(buf, "%d\n", isl29023_get_power_state(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_power_state(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
unsigned long val;
|
|
int ret;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0) || (val > 1))
|
|
return -EINVAL;
|
|
|
|
ret = isl29023_set_power_state(client, val);
|
|
return ret ? ret : count;
|
|
}
|
|
|
|
static DEVICE_ATTR(power_state, S_IWUSR | S_IRUGO,
|
|
isl29023_show_power_state, isl29023_store_power_state);
|
|
|
|
/*
|
|
* lux
|
|
*/
|
|
static ssize_t isl29023_show_lux(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
/* No LUX data if not operational */
|
|
if (!isl29023_get_power_state(client))
|
|
return -EBUSY;
|
|
|
|
return sprintf(buf, "%d\n", isl29023_get_adc_value(client));
|
|
}
|
|
|
|
static DEVICE_ATTR(lux, S_IRUGO, isl29023_show_lux, NULL);
|
|
|
|
/*
|
|
* lux interrupt low threshold
|
|
*/
|
|
static ssize_t isl29023_show_int_lt_lux(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
/* No LUX data if not operational */
|
|
if (isl29023_get_mode(client) != ISL29023_ALS_ONCE_MODE &&
|
|
isl29023_get_mode(client) != ISL29023_ALS_CONT_MODE)
|
|
return -EIO;
|
|
|
|
return sprintf(buf, "%d\n", isl29023_get_int_lt_value(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_int_lt_lux(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
unsigned long val, lux_data;
|
|
int range, bitdepth, ret;
|
|
u8 lsb, msb;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0))
|
|
return -EINVAL;
|
|
|
|
/* No LUX data if not operational */
|
|
if (isl29023_get_mode(client) != ISL29023_ALS_ONCE_MODE &&
|
|
isl29023_get_mode(client) != ISL29023_ALS_CONT_MODE)
|
|
return -EIO;
|
|
|
|
if (val > (gain_range[isl29023_get_range(client)]*499/data->rext))
|
|
return -EINVAL;
|
|
|
|
range = isl29023_get_range(client);
|
|
bitdepth = (4 - isl29023_get_resolution(client)) * 4;
|
|
lux_data = ((unsigned long)(val << bitdepth)) /
|
|
((gain_range[range] * 499) / data->rext);
|
|
lux_data &= 0xffff;
|
|
|
|
msb = lux_data >> 8;
|
|
lsb = lux_data & 0xff;
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_LO_LSB,
|
|
lsb);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_LO_MSB,
|
|
msb);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_LO_MSB] = msb;
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_LO_LSB] = lsb;
|
|
mutex_unlock(&data->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(int_lt_lux, S_IWUSR | S_IRUGO,
|
|
isl29023_show_int_lt_lux, isl29023_store_int_lt_lux);
|
|
|
|
/*
|
|
* lux interrupt high threshold
|
|
*/
|
|
static ssize_t isl29023_show_int_ht_lux(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
|
|
/* No LUX data if not operational */
|
|
if (isl29023_get_mode(client) != ISL29023_ALS_ONCE_MODE &&
|
|
isl29023_get_mode(client) != ISL29023_ALS_CONT_MODE)
|
|
return -EIO;
|
|
|
|
return sprintf(buf, "%d\n", isl29023_get_int_ht_value(client));
|
|
}
|
|
|
|
static ssize_t isl29023_store_int_ht_lux(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
unsigned long val, lux_data;
|
|
int range, bitdepth, ret;
|
|
u8 lsb, msb;
|
|
|
|
if ((kstrtoul(buf, 10, &val) < 0))
|
|
return -EINVAL;
|
|
|
|
/* No LUX data if not operational */
|
|
if (isl29023_get_mode(client) != ISL29023_ALS_ONCE_MODE &&
|
|
isl29023_get_mode(client) != ISL29023_ALS_CONT_MODE)
|
|
return -EIO;
|
|
|
|
if (val > (gain_range[isl29023_get_range(client)]*499/data->rext))
|
|
return -EINVAL;
|
|
|
|
range = isl29023_get_range(client);
|
|
bitdepth = (4 - isl29023_get_resolution(client)) * 4;
|
|
lux_data = ((unsigned long)(val << bitdepth)) /
|
|
((gain_range[range] * 499) / data->rext);
|
|
lux_data &= 0xffff;
|
|
|
|
msb = lux_data >> 8;
|
|
lsb = lux_data & 0xff;
|
|
|
|
mutex_lock(&data->lock);
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_HI_LSB,
|
|
lsb);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
ret = i2c_smbus_write_byte_data(client, ISL29023_REG_IRQ_TH_HI_MSB,
|
|
msb);
|
|
if (ret < 0) {
|
|
mutex_unlock(&data->lock);
|
|
return ret;
|
|
}
|
|
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_HI_MSB] = msb;
|
|
data->reg_cache[ISL29023_REG_IRQ_TH_HI_LSB] = lsb;
|
|
mutex_unlock(&data->lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(int_ht_lux, S_IWUSR | S_IRUGO,
|
|
isl29023_show_int_ht_lux, isl29023_store_int_ht_lux);
|
|
|
|
static struct attribute *isl29023_attributes[] = {
|
|
&dev_attr_int_persists.attr,
|
|
&dev_attr_range.attr,
|
|
&dev_attr_resolution.attr,
|
|
&dev_attr_mode.attr,
|
|
&dev_attr_power_state.attr,
|
|
&dev_attr_lux.attr,
|
|
&dev_attr_int_lt_lux.attr,
|
|
&dev_attr_int_ht_lux.attr,
|
|
&dev_attr_int_lt.attr,
|
|
&dev_attr_int_ht.attr,
|
|
&dev_attr_int_flag.attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group isl29023_attr_group = {
|
|
.attrs = isl29023_attributes,
|
|
};
|
|
|
|
static int isl29023_init_client(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
int i;
|
|
|
|
/* read all the registers once to fill the cache.
|
|
* if one of the reads fails, we consider the init failed */
|
|
for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++) {
|
|
int v = i2c_smbus_read_byte_data(client, i);
|
|
if (v < 0)
|
|
return -ENODEV;
|
|
|
|
data->reg_cache[i] = v;
|
|
}
|
|
|
|
/* set defaults */
|
|
isl29023_set_int_persists(client, ISL29023_INT_PERSISTS_8);
|
|
isl29023_set_int_ht(client, 0xffff);
|
|
isl29023_set_int_lt(client, 0x0);
|
|
isl29023_set_range(client, ISL29023_RANGE_16K);
|
|
isl29023_set_resolution(client, ISL29023_RES_16);
|
|
isl29023_set_mode(client, ISL29023_ALS_ONCE_MODE);
|
|
isl29023_set_int_flag(client, 0);
|
|
isl29023_set_power_state(client, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void isl29023_work(struct work_struct *work)
|
|
{
|
|
struct isl29023_data *data =
|
|
container_of(work, struct isl29023_data, work);
|
|
struct i2c_client *client = data->client;
|
|
int lux;
|
|
|
|
/* Clear interrupt flag */
|
|
isl29023_set_int_flag(client, 0);
|
|
|
|
data->mode_before_interrupt = isl29023_get_mode(client);
|
|
lux = isl29023_get_adc_value(client);
|
|
|
|
/* To clear the interrpt status */
|
|
isl29023_set_power_state(client, ISL29023_PD_MODE);
|
|
isl29023_set_mode(client, data->mode_before_interrupt);
|
|
|
|
msleep(100);
|
|
|
|
input_report_abs(data->input, ABS_MISC, lux);
|
|
input_sync(data->input);
|
|
}
|
|
|
|
static irqreturn_t isl29023_irq_handler(int irq, void *handle)
|
|
{
|
|
struct isl29023_data *data = handle;
|
|
int cmd_1;
|
|
cmd_1 = i2c_smbus_read_byte_data(data->client, ISL29023_COMMAND1);
|
|
if (!(cmd_1 & ISL29023_INT_FLAG_MASK))
|
|
return IRQ_NONE;
|
|
|
|
queue_work(data->workqueue, &data->work);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* I2C layer
|
|
*/
|
|
static int isl29023_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
struct isl29023_data *data;
|
|
struct input_dev *input_dev;
|
|
int err = 0;
|
|
struct regulator *vdd = NULL;
|
|
u32 rext = 0;
|
|
struct device_node *of_node = client->dev.of_node;
|
|
struct irq_data *irq_data = irq_get_irq_data(client->irq);
|
|
u32 irq_flag;
|
|
bool shared_irq;
|
|
|
|
vdd = devm_regulator_get(&client->dev, "vdd");
|
|
if (!IS_ERR(vdd)) {
|
|
err = regulator_enable(vdd);
|
|
if (err) {
|
|
dev_err(&client->dev, "vdd set voltage error\n");
|
|
return err;
|
|
}
|
|
}
|
|
|
|
err = of_property_read_u32(of_node, "rext", &rext);
|
|
if (err || rext == 0)
|
|
rext = DEFAULT_REGISTOR_VAL;
|
|
shared_irq = of_property_read_bool(of_node, "shared-interrupt");
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
|
|
return -EIO;
|
|
|
|
data = kzalloc(sizeof(struct isl29023_data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->client = client;
|
|
data->rext = (u16)rext;
|
|
snprintf(data->phys, sizeof(data->phys),
|
|
"%s", dev_name(&client->dev));
|
|
i2c_set_clientdata(client, data);
|
|
mutex_init(&data->lock);
|
|
|
|
/* initialize the ISL29023 chip */
|
|
err = isl29023_init_client(client);
|
|
if (err)
|
|
goto exit_kfree;
|
|
|
|
/* register sysfs hooks */
|
|
err = sysfs_create_group(&client->dev.kobj, &isl29023_attr_group);
|
|
if (err)
|
|
goto exit_kfree;
|
|
|
|
input_dev = input_allocate_device();
|
|
if (!input_dev) {
|
|
err = -ENOMEM;
|
|
goto exit_kfree;
|
|
}
|
|
|
|
data->input = input_dev;
|
|
input_dev->name = "isl29023 light sensor";
|
|
input_dev->id.bustype = BUS_I2C;
|
|
input_dev->phys = data->phys;
|
|
|
|
__set_bit(EV_ABS, input_dev->evbit);
|
|
input_set_abs_params(input_dev, ABS_MISC, 0,
|
|
gain_range[DEF_RANGE]*499/data->rext, 0, 0);
|
|
|
|
err = input_register_device(input_dev);
|
|
if (err)
|
|
goto exit_free_input;
|
|
|
|
irq_flag = irqd_get_trigger_type(irq_data);
|
|
irq_flag |= IRQF_ONESHOT;
|
|
if (shared_irq)
|
|
irq_flag |= IRQF_SHARED;
|
|
err = request_threaded_irq(client->irq, NULL,
|
|
isl29023_irq_handler, irq_flag,
|
|
client->dev.driver->name, data);
|
|
if (err < 0) {
|
|
dev_err(&client->dev, "failed to register irq %d!\n",
|
|
client->irq);
|
|
goto exit_free_input;
|
|
}
|
|
|
|
data->workqueue = create_singlethread_workqueue("isl29023");
|
|
INIT_WORK(&data->work, isl29023_work);
|
|
if (data->workqueue == NULL) {
|
|
dev_err(&client->dev, "couldn't create workqueue\n");
|
|
err = -ENOMEM;
|
|
goto exit_free_interrupt;
|
|
}
|
|
|
|
dev_info(&client->dev, "driver version %s enabled\n", DRIVER_VERSION);
|
|
return 0;
|
|
|
|
exit_free_interrupt:
|
|
free_irq(client->irq, data);
|
|
exit_free_input:
|
|
input_free_device(input_dev);
|
|
exit_kfree:
|
|
if (!IS_ERR(vdd))
|
|
regulator_disable(vdd);
|
|
kfree(data);
|
|
return err;
|
|
}
|
|
|
|
static int isl29023_remove(struct i2c_client *client)
|
|
{
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
|
|
cancel_work_sync(&data->work);
|
|
destroy_workqueue(data->workqueue);
|
|
free_irq(client->irq, data);
|
|
input_unregister_device(data->input);
|
|
input_free_device(data->input);
|
|
sysfs_remove_group(&client->dev.kobj, &isl29023_attr_group);
|
|
isl29023_set_power_state(client, 0);
|
|
kfree(i2c_get_clientdata(client));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int isl29023_suspend(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
|
|
data->mode_before_suspend = isl29023_get_mode(client);
|
|
return isl29023_set_power_state(client, ISL29023_PD_MODE);
|
|
}
|
|
|
|
static int isl29023_resume(struct device *dev)
|
|
{
|
|
int i;
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct isl29023_data *data = i2c_get_clientdata(client);
|
|
|
|
/* restore registers from cache */
|
|
for (i = 0; i < ARRAY_SIZE(data->reg_cache); i++)
|
|
if (i2c_smbus_write_byte_data(client, i, data->reg_cache[i]))
|
|
return -EIO;
|
|
|
|
return isl29023_set_mode(client, data->mode_before_suspend);
|
|
}
|
|
#endif
|
|
|
|
static SIMPLE_DEV_PM_OPS(isl29023_pm_ops, isl29023_suspend, isl29023_resume);
|
|
|
|
static const struct i2c_device_id isl29023_id[] = {
|
|
{ ISL29023_DRV_NAME, 0 },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, isl29023_id);
|
|
|
|
static struct i2c_driver isl29023_driver = {
|
|
.driver = {
|
|
.name = ISL29023_DRV_NAME,
|
|
.owner = THIS_MODULE,
|
|
.pm = &isl29023_pm_ops,
|
|
},
|
|
.probe = isl29023_probe,
|
|
.remove = isl29023_remove,
|
|
.id_table = isl29023_id,
|
|
};
|
|
|
|
static int __init isl29023_init(void)
|
|
{
|
|
return i2c_add_driver(&isl29023_driver);
|
|
}
|
|
|
|
static void __exit isl29023_exit(void)
|
|
{
|
|
i2c_del_driver(&isl29023_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
|
|
MODULE_DESCRIPTION("ISL29023 ambient light sensor driver");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(DRIVER_VERSION);
|
|
|
|
module_init(isl29023_init);
|
|
module_exit(isl29023_exit);
|