| /* | |
| * 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: | |
| 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); |