diff options
author | Liu Ying <Ying.Liu@freescale.com> | 2011-02-22 17:11:30 +0800 |
---|---|---|
committer | Liu Ying <Ying.Liu@freescale.com> | 2011-02-25 17:02:19 +0800 |
commit | 9d051dbb41aaebc462bf88ad2931d286058b88ae (patch) | |
tree | 03842da9d253c201469fffdea0a4f09f5c949fc8 /drivers | |
parent | 079a6024a7369117b8f2ca8f3a905702a0175394 (diff) |
ENGR00139383-1 Add ISL29023 light sensor support
This patch adds ISL29023 light sensor support.
Users may control the light sensor work at
different modes/ranges/resolutions or read
the lux value via sysfs.
A power state node is also created in sysfs.
If the light sensor works at ALS once mode,
users need to power down/power up the sensor
before read the lux value again.
Currently, IR(Infra Red) mode is not supported.
Signed-off-by: Liu Ying <Ying.Liu@freescale.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/input/misc/Kconfig | 9 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/misc/isl29023.c | 1023 |
3 files changed, 1033 insertions, 0 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index e407e142ccca..2de3a98136eb 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -400,4 +400,13 @@ config INPUT_DA9052_ONKEY To compile this driver as a module, choose M here: the module will be called da9052_onkey. +config INPUT_ISL29023 + tristate "Intersil ISL29023 ambient light sensor" + depends on I2C && SYSFS + help + If you say yes here you get support for the Intersil ISL29023 + ambient light sensor. + + This driver can also be built as a module. If so, the module + will be called isl29023. endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index c8231a783221..1ab63a7d379f 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -38,3 +38,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o +obj-$(CONFIG_INPUT_ISL29023) += isl29023.o diff --git a/drivers/input/misc/isl29023.c b/drivers/input/misc/isl29023.c new file mode 100644 index 000000000000..626a8b8e07ee --- /dev/null +++ b/drivers/input/misc/isl29023.c @@ -0,0 +1,1023 @@ +/* + * Copyright (C) 2011 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/isl29023.h> +#include <linux/fsl_devices.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 + +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 + */ + +/* interrupt persists */ +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 ((strict_strtoul(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 ((strict_strtoul(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 ((strict_strtoul(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 ((strict_strtoul(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 ((strict_strtoul(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 ((strict_strtoul(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 ((strict_strtoul(buf, 10, &val) < 0) || + (val > ISL29023_IR_CONT_MODE)) + return -EINVAL; + + 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 ((strict_strtoul(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 ((strict_strtoul(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 ((strict_strtoul(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); + + /* + * Max threshold to avoid multiple interrupts. + * Let the user to reprogramme the interrupt + * thresholds. + */ + isl29023_set_int_ht(client, 0xffff); + isl29023_set_int_lt(client, 0x0); + + 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; + queue_work(data->workqueue, &data->work); + return IRQ_HANDLED; +} + +/* + * I2C layer + */ + +static int __devinit 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 mxc_lightsensor_platform_data *ls_data; + struct input_dev *input_dev; + int err = 0; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; + + data = kzalloc(sizeof(struct isl29023_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + ls_data = (struct mxc_lightsensor_platform_data *) + (client->dev).platform_data; + + data->client = client; + data->rext = ls_data->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; + + /* set irq type to edge falling */ + set_irq_type(client->irq, IRQF_TRIGGER_FALLING); + err = request_irq(client->irq, isl29023_irq_handler, 0, + 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: + kfree(data); + return err; +} + +static int __devexit 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 +static int isl29023_suspend(struct i2c_client *client, pm_message_t mesg) +{ + 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 i2c_client *client) +{ + int i; + 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); +} + +#else +#define isl29023_suspend NULL +#define isl29023_resume NULL +#endif /* CONFIG_PM */ + +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, + }, + .suspend = isl29023_suspend, + .resume = isl29023_resume, + .probe = isl29023_probe, + .remove = __devexit_p(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); |