diff options
Diffstat (limited to 'drivers/staging')
-rw-r--r-- | drivers/staging/iio/light/Kconfig | 29 | ||||
-rw-r--r-- | drivers/staging/iio/light/Makefile | 3 | ||||
-rw-r--r-- | drivers/staging/iio/light/isl29028.c | 1265 |
3 files changed, 1288 insertions, 9 deletions
diff --git a/drivers/staging/iio/light/Kconfig b/drivers/staging/iio/light/Kconfig index 1ad2d56c8ba8..92ee85fc85d6 100644 --- a/drivers/staging/iio/light/Kconfig +++ b/drivers/staging/iio/light/Kconfig @@ -4,15 +4,26 @@ comment "Light sensors" config SENSORS_ISL29018 - tristate "ISL 29018 light and proximity sensor" - depends on I2C - default n - help - If you say yes here you get support for ambient light sensing and - proximity infrared sensing from Intersil ISL29018. - This driver will provide the measurements of ambient light intensity - in lux, proximity infrared sensing and normal infrared sensing. - Data from sensor is accessible via sysfs. + tristate "ISL 29018 light and proximity sensor" + depends on I2C + default n + help + If you say yes here you get support for ambient light sensing and + proximity infrared sensing from Intersil ISL29018. + This driver will provide the measurements of ambient light intensity + in lux, proximity infrared sensing and normal infrared sensing. + Data from sensor is accessible via sysfs. + +config SENSORS_ISL29028 + tristate "ISL 29028 light and proximity sensor" + depends on I2C + default n + help + If you say yes here you get support for ambient light sensing and + proximity ir sensing from intersil ISL29028. + This driver will provide the measurements of ambient light intensity + in lux, proximity infrared sensing and normal infrared sensing. + Data from sensor is accessible via sysfs. config SENSORS_TSL2563 tristate "TAOS TSL2560, TSL2561, TSL2562 and TSL2563 ambient light sensors" diff --git a/drivers/staging/iio/light/Makefile b/drivers/staging/iio/light/Makefile index 3011fbfa8dc2..6f02c4c3f722 100644 --- a/drivers/staging/iio/light/Makefile +++ b/drivers/staging/iio/light/Makefile @@ -1,7 +1,10 @@ # # Makefile for industrial I/O Light sensors # +GCOV_PROFILE := y obj-$(CONFIG_SENSORS_TSL2563) += tsl2563.o obj-$(CONFIG_SENSORS_ISL29018) += isl29018.o obj-$(CONFIG_TSL2583) += tsl2583.o +obj-$(CONFIG_SENSORS_ISL29028) += isl29028.o + diff --git a/drivers/staging/iio/light/isl29028.c b/drivers/staging/iio/light/isl29028.c new file mode 100644 index 000000000000..ea7c284b219b --- /dev/null +++ b/drivers/staging/iio/light/isl29028.c @@ -0,0 +1,1265 @@ +/* + * A iio driver for the light sensor ISL 29028. + * + * IIO Light driver for monitoring ambient light intensity in lux and proximity + * ir. + * + * Copyright (c) 2011, NVIDIA Corporation. + * + * 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/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include "../iio.h" + +#define CONVERSION_TIME_MS 100 + +#define ISL29028_REG_ADD_CONFIGURE 0x01 + +#define CONFIGURE_PROX_EN_MASK (1 << 7) +#define CONFIGURE_PROX_EN_SH 7 + +#define CONFIGURE_PROX_SLP_SH 4 +#define CONFIGURE_PROX_SLP_MASK (7 << CONFIGURE_PROX_SLP_SH) + +#define CONFIGURE_PROX_DRIVE (1 << 3) + +#define CONFIGURE_ALS_EN 1 +#define CONFIGURE_ALS_DIS 0 +#define CONFIGURE_ALS_EN_SH 2 +#define CONFIGURE_ALS_EN_MASK (1 << CONFIGURE_ALS_EN_SH) + + +#define CONFIGURE_ALS_RANGE_LOW_LUX 0 +#define CONFIGURE_ALS_RANGE_HIGH_LUX 1 +#define CONFIGURE_ALS_RANGE_SH 1 +#define CONFIGURE_ALS_RANGE_MASK (1 << CONFIGURE_ALS_RANGE_SH) + +#define CONFIGURE_ALS_IR_MODE_MASK 1 +#define CONFIGURE_ALS_IR_MODE_SH 0 +#define CONFIGURE_ALS_IR_MODE_IR 1 +#define CONFIGURE_ALS_IR_MODE_ALS 0 + +#define ISL29028_REG_ADD_INTERRUPT 0x02 +#define INTERRUPT_PROX_FLAG_MASK (1 << 7) +#define INTERRUPT_PROX_FLAG_SH 7 +#define INTERRUPT_PROX_FLAG_EN 1 +#define INTERRUPT_PROX_FLAG_DIS 0 + +#define INTERRUPT_PROX_PERSIST_SH 5 +#define INTERRUPT_PROX_PERSIST_MASK (3 << 5) + +#define INTERRUPT_ALS_FLAG_MASK (1 << 3) +#define INTERRUPT_ALS_FLAG_SH 3 +#define INTERRUPT_ALS_FLAG_EN 1 +#define INTERRUPT_ALS_FLAG_DIS 0 + +#define INTERRUPT_ALS_PERSIST_SH 1 +#define INTERRUPT_ALS_PERSIST_MASK (3 << 1) + +#define ISL29028_REG_ADD_PROX_LOW_THRES 0x03 +#define ISL29028_REG_ADD_PROX_HIGH_THRES 0x04 + +#define ISL29028_REG_ADD_ALSIR_LOW_THRES 0x05 +#define ISL29028_REG_ADD_ALSIR_LH_THRES 0x06 +#define ISL29028_REG_ADD_ALSIR_LH_THRES_L_SH 0 +#define ISL29028_REG_ADD_ALSIR_LH_THRES_H_SH 4 +#define ISL29028_REG_ADD_ALSIR_HIGH_THRES 0x07 + +#define ISL29028_REG_ADD_PROX_DATA 0x08 +#define ISL29028_REG_ADD_ALSIR_L 0x09 +#define ISL29028_REG_ADD_ALSIR_U 0x0A + +#define ISL29028_REG_ADD_TEST1_MODE 0x0E +#define ISL29028_REG_ADD_TEST2_MODE 0x0F + +#define ISL29028_MAX_REGS ISL29028_REG_ADD_TEST2_MODE + +enum { + MODE_NONE = 0, + MODE_ALS, + MODE_IR +}; + +struct isl29028_chip { + struct iio_dev *indio_dev; + struct i2c_client *client; + struct mutex lock; + int irq; + + int prox_period; + int prox_low_thres; + int prox_high_thres; + int prox_persist; + bool is_prox_enable; + int prox_reading; + + int als_high_thres; + int als_low_thres; + int als_persist; + int als_range; + int als_reading; + int als_ir_mode; + + int ir_high_thres; + int ir_low_thres; + int ir_reading; + + bool is_int_enable; + bool is_proxim_int_waiting; + bool is_als_int_waiting; + struct completion prox_completion; + struct completion als_completion; + u8 reg_cache[ISL29028_MAX_REGS]; +}; + +static bool isl29028_write_data(struct i2c_client *client, u8 reg, + u8 val, u8 mask, u8 shift) +{ + u8 regval; + int ret = 0; + struct isl29028_chip *chip = i2c_get_clientdata(client); + + regval = chip->reg_cache[reg]; + regval &= ~mask; + regval |= val << shift; + + ret = i2c_smbus_write_byte_data(client, reg, regval); + if (ret) { + dev_err(&client->dev, "Write to device reg %d fails status " + "%x\n", reg, ret); + return false; + } + chip->reg_cache[reg] = regval; + return true; +} + +static bool isl29018_set_proxim_period(struct i2c_client *client, + bool is_enable, int period) +{ + int prox_period[] = {0, 12, 50, 75, 100, 200, 400, 800}; + int i; + int sel; + bool st; + if (period < 12) + sel = 7; + else { + for (i = 1; i < ARRAY_SIZE(prox_period) - 1; ++i) { + if ((prox_period[i] <= period) && + period < prox_period[i + 1]) + break; + } + sel = 7 - i; + } + + if (!is_enable) { + dev_dbg(&client->dev, "Disabling proximity sensing\n"); + st = isl29028_write_data(client, ISL29028_REG_ADD_CONFIGURE, + 0, CONFIGURE_PROX_EN_MASK, CONFIGURE_PROX_EN_SH); + } else { + dev_dbg(&client->dev, "Enabling proximity sensing with period " + "of %d ms sel %d period %d\n", prox_period[7 - sel], + sel, period); + st = isl29028_write_data(client, ISL29028_REG_ADD_CONFIGURE, + sel, CONFIGURE_PROX_SLP_MASK, CONFIGURE_PROX_SLP_SH); + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_CONFIGURE, 1, + CONFIGURE_PROX_EN_MASK, CONFIGURE_PROX_EN_SH); + } + return st; +} + +static bool isl29018_set_proxim_persist(struct i2c_client *client, + bool is_enable, int persist) +{ + int prox_perstant[] = {1, 4, 8, 16}; + int i; + int sel; + bool st; + if (is_enable) { + for (i = 0; i < ARRAY_SIZE(prox_perstant) - 1; ++i) { + if ((prox_perstant[i] <= persist) && + persist < prox_perstant[i+1]) + break; + } + sel = i; + } + + if (is_enable) { + dev_dbg(&client->dev, "Enabling proximity threshold interrupt\n"); + st = isl29028_write_data(client, ISL29028_REG_ADD_INTERRUPT, + sel, INTERRUPT_PROX_PERSIST_MASK, + INTERRUPT_PROX_PERSIST_SH); + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_INTERRUPT, + INTERRUPT_PROX_FLAG_EN, + INTERRUPT_PROX_FLAG_MASK, + INTERRUPT_PROX_FLAG_SH); + } else { + st = isl29028_write_data(client, + ISL29028_REG_ADD_INTERRUPT, INTERRUPT_PROX_FLAG_DIS, + INTERRUPT_PROX_FLAG_MASK, INTERRUPT_PROX_FLAG_SH); + } + return st; +} + +static bool isl29018_set_als_persist(struct i2c_client *client, bool is_enable, + int persist) +{ + int prox_perstant[] = {1, 4, 8, 16}; + int i; + int sel; + bool st; + if (is_enable) { + for (i = 0; i < ARRAY_SIZE(prox_perstant) - 1; ++i) { + if ((prox_perstant[i] <= persist) && + persist < prox_perstant[i+1]) + break; + } + sel = i; + } + + if (is_enable) { + dev_dbg(&client->dev, "Enabling als threshold interrupt\n"); + st = isl29028_write_data(client, ISL29028_REG_ADD_INTERRUPT, + sel, INTERRUPT_ALS_PERSIST_MASK, + INTERRUPT_ALS_PERSIST_SH); + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_INTERRUPT, + INTERRUPT_ALS_FLAG_EN, + INTERRUPT_ALS_FLAG_MASK, + INTERRUPT_ALS_FLAG_SH); + } else { + st = isl29028_write_data(client, + ISL29028_REG_ADD_INTERRUPT, INTERRUPT_ALS_FLAG_DIS, + INTERRUPT_ALS_FLAG_MASK, INTERRUPT_ALS_FLAG_SH); + } + return st; +} + +static bool isl29018_set_proxim_high_threshold(struct i2c_client *client, u8 th) +{ + return isl29028_write_data(client, ISL29028_REG_ADD_PROX_HIGH_THRES, + th, 0xFF, 0); +} + +static bool isl29018_set_proxim_low_threshold(struct i2c_client *client, u8 th) +{ + return isl29028_write_data(client, ISL29028_REG_ADD_PROX_LOW_THRES, + th, 0xFF, 0); +} + +static bool isl29018_set_irals_high_threshold(struct i2c_client *client, + u32 als) +{ + bool st; + st = isl29028_write_data(client, ISL29028_REG_ADD_ALSIR_HIGH_THRES, + (als >> 4) & 0xFF, 0xFF, 0); + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_ALSIR_LH_THRES, als & 0xF, + 0xF << ISL29028_REG_ADD_ALSIR_LH_THRES_H_SH, + ISL29028_REG_ADD_ALSIR_LH_THRES_H_SH); + return st; +} + +static bool isl29018_set_irals_low_threshold(struct i2c_client *client, u32 als) +{ + bool st; + st = isl29028_write_data(client, + ISL29028_REG_ADD_ALSIR_LH_THRES, (als >> 8) & 0xF, + 0xF << ISL29028_REG_ADD_ALSIR_LH_THRES_L_SH, + ISL29028_REG_ADD_ALSIR_LH_THRES_L_SH); + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_ALSIR_LOW_THRES, + als & 0xFF, 0xFF, 0); + return st; +} + +static bool isl29018_set_als_ir_mode(struct i2c_client *client, bool is_enable, + bool is_als) +{ + struct isl29028_chip *chip = i2c_get_clientdata(client); + bool st; + if (is_enable) { + if (is_als) { + dev_dbg(&client->dev, "Enabling ALS mode\n"); + st = isl29028_write_data(client, + ISL29028_REG_ADD_CONFIGURE, + CONFIGURE_ALS_IR_MODE_ALS, + CONFIGURE_ALS_IR_MODE_MASK, + CONFIGURE_ALS_IR_MODE_SH); + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_CONFIGURE, + CONFIGURE_ALS_RANGE_HIGH_LUX, + CONFIGURE_ALS_RANGE_MASK, + CONFIGURE_ALS_RANGE_SH); + if (st) + st = isl29018_set_irals_high_threshold(client, + chip->als_high_thres); + if (st) + st = isl29018_set_irals_low_threshold(client, + chip->als_low_thres); + } else { + dev_dbg(&client->dev, "Enabling IR mode\n"); + st = isl29028_write_data(client, + ISL29028_REG_ADD_CONFIGURE, + CONFIGURE_ALS_IR_MODE_IR, + CONFIGURE_ALS_IR_MODE_MASK, + CONFIGURE_ALS_IR_MODE_SH); + if (st) + st = isl29018_set_irals_high_threshold(client, + chip->ir_high_thres); + if (st) + st = isl29018_set_irals_low_threshold(client, + chip->ir_low_thres); + } + if (st) + st = isl29028_write_data(client, + ISL29028_REG_ADD_CONFIGURE, + CONFIGURE_ALS_EN, + CONFIGURE_ALS_EN_MASK, + CONFIGURE_ALS_EN_SH); + } else { + st = isl29028_write_data(client, + ISL29028_REG_ADD_CONFIGURE, + CONFIGURE_ALS_DIS, + CONFIGURE_ALS_EN_MASK, + CONFIGURE_ALS_EN_SH); + } + return st; +} + +static bool isl29028_read_als_ir(struct i2c_client *client, int *als_ir) +{ + s32 lsb; + s32 msb; + + lsb = i2c_smbus_read_byte_data(client, ISL29028_REG_ADD_ALSIR_L); + if (lsb < 0) { + dev_err(&client->dev, "Error in reading register %d, error %d\n", + ISL29028_REG_ADD_ALSIR_L, lsb); + return false; + } + + msb = i2c_smbus_read_byte_data(client, ISL29028_REG_ADD_ALSIR_U); + if (msb < 0) { + dev_err(&client->dev, "Error in reading register %d, error %d\n", + ISL29028_REG_ADD_ALSIR_U, lsb); + return false; + } + *als_ir = ((msb & 0xF) << 8) | (lsb & 0xFF); + return true; +} + +static bool isl29028_read_proxim(struct i2c_client *client, int *prox) +{ + s32 data; + + data = i2c_smbus_read_byte_data(client, ISL29028_REG_ADD_PROX_DATA); + if (data < 0) { + dev_err(&client->dev, "Error in reading register %d, error %d\n", + ISL29028_REG_ADD_PROX_DATA, data); + return false; + } + *prox = (int)data; + return true; +} + +/* Sysfs interface */ +/* proximity period */ +static ssize_t show_prox_period(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->prox_period); +} + +static ssize_t store_prox_period(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + mutex_lock(&chip->lock); + st = isl29018_set_proxim_period(client, chip->is_prox_enable, + (int)lval); + if (st) + chip->prox_period = (int)lval; + else + dev_err(dev, "Error in setting the proximity period\n"); + + mutex_unlock(&chip->lock); + return count; +} + +/* proximity enable/disable */ +static ssize_t show_prox_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + if (chip->is_prox_enable) + return sprintf(buf, "1\n"); + else + return sprintf(buf, "0\n"); +} + +static ssize_t store_prox_enable(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + if ((lval != 1) && (lval != 0)) { + dev_err(dev, "illegal value %lu\n", lval); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (lval == 1) + st = isl29018_set_proxim_period(client, true, + chip->prox_period); + else + st = isl29018_set_proxim_period(client, false, + chip->prox_period); + if (st) + chip->is_prox_enable = (lval) ? true : false; + else + dev_err(dev, "Error in enabling proximity\n"); + + mutex_unlock(&chip->lock); + return count; +} + +/* als/ir enable/disable */ +static ssize_t show_als_ir_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "Current Mode: %d [0:None, 1:ALS, 2:IR]\n", + chip->als_ir_mode); +} + +static ssize_t store_als_ir_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + if (lval > 2) { + dev_err(dev, "illegal value %lu\n", lval); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (lval == 0) + st = isl29018_set_als_ir_mode(client, false, false); + else if (lval == 1) + st = isl29018_set_als_ir_mode(client, true, true); + else + st = isl29018_set_als_ir_mode(client, true, false); + if (st) + chip->als_ir_mode = (int)lval; + else + dev_err(dev, "Error in enabling als/ir mode\n"); + + mutex_unlock(&chip->lock); + return count; +} + +/* Proximity low thresholds */ +static ssize_t show_proxim_low_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->prox_low_thres); +} + +static ssize_t store_proxim_low_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 0xFF) || (lval < 0x0)) { + dev_err(dev, "The threshold is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + st = isl29018_set_proxim_low_threshold(client, (u8)lval); + if (st) + chip->prox_low_thres = (int)lval; + else + dev_err(dev, "Error in setting proximity low threshold\n"); + + mutex_unlock(&chip->lock); + return count; +} + +/* Proximity high thresholds */ +static ssize_t show_proxim_high_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->prox_high_thres); +} + +static ssize_t store_proxim_high_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 0xFF) || (lval < 0x0)) { + dev_err(dev, "The threshold is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + st = isl29018_set_proxim_high_threshold(client, (u8)lval); + if (st) + chip->prox_high_thres = (int)lval; + else + dev_err(dev, "Error in setting proximity high threshold\n"); + + mutex_unlock(&chip->lock); + return count; +} + +/* als low thresholds */ +static ssize_t show_als_low_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->als_low_thres); +} + +static ssize_t store_als_low_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 0xFFFF) || (lval < 0x0)) { + dev_err(dev, "The ALS threshold is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (chip->als_ir_mode == MODE_ALS) { + st = isl29018_set_irals_low_threshold(client, (int)lval); + if (st) + chip->als_low_thres = (int)lval; + else + dev_err(dev, "Error in setting als low threshold\n"); + } else + chip->als_low_thres = (int)lval; + mutex_unlock(&chip->lock); + return count; +} + +/* Als high thresholds */ +static ssize_t show_als_high_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->als_high_thres); +} + +static ssize_t store_als_high_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 0xFFFF) || (lval < 0x0)) { + dev_err(dev, "The als threshold is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (chip->als_ir_mode == MODE_ALS) { + st = isl29018_set_irals_high_threshold(client, (int)lval); + if (st) + chip->als_high_thres = (int)lval; + else + dev_err(dev, "Error in setting als high threshold\n"); + } else + chip->als_high_thres = (int)lval; + mutex_unlock(&chip->lock); + return count; +} + +/* IR low thresholds */ +static ssize_t show_ir_low_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->ir_low_thres); +} + +static ssize_t store_ir_low_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 0xFFFF) || (lval < 0x0)) { + dev_err(dev, "The IR threshold is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (chip->als_ir_mode == MODE_IR) { + st = isl29018_set_irals_low_threshold(client, (int)lval); + if (st) + chip->ir_low_thres = (int)lval; + else + dev_err(dev, "Error in setting als low threshold\n"); + } else + chip->ir_low_thres = (int)lval; + mutex_unlock(&chip->lock); + return count; +} + +/* IR high thresholds */ +static ssize_t show_ir_high_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->ir_high_thres); +} + +static ssize_t store_ir_high_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 0xFFFF) || (lval < 0x0)) { + dev_err(dev, "The als threshold is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + if (chip->als_ir_mode == MODE_IR) { + st = isl29018_set_irals_high_threshold(client, (int)lval); + if (st) + chip->ir_high_thres = (int)lval; + else + dev_err(dev, "Error in setting als high threshold\n"); + } else + chip->ir_high_thres = (int)lval; + mutex_unlock(&chip->lock); + return count; +} + +/* Proximity persist */ +static ssize_t show_proxim_persist(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->prox_persist); +} + +static ssize_t store_proxim_persist(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 16) || (lval < 0x0)) { + dev_err(dev, "The proximity persist is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + chip->prox_persist = (int)lval; + mutex_unlock(&chip->lock); + return count; +} + +/* als/ir persist */ +static ssize_t show_als_persist(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + + dev_vdbg(dev, "%s()\n", __func__); + return sprintf(buf, "%d\n", chip->als_persist); +} + +static ssize_t store_als_persist(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + unsigned long lval; + + dev_vdbg(dev, "%s()\n", __func__); + + if (strict_strtoul(buf, 10, &lval)) + return -EINVAL; + + if ((lval > 16) || (lval < 0x0)) { + dev_err(dev, "The als persist is not supported\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + chip->als_persist = (int)lval; + mutex_unlock(&chip->lock); + return count; +} + +/* Display proxim data */ +static ssize_t show_proxim_data(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + int prox_data; + bool st; + ssize_t buf_count = 0; + + dev_vdbg(dev, "%s()\n", __func__); + mutex_lock(&chip->lock); + + if (chip->is_prox_enable) { + st = isl29028_read_proxim(chip->client, &prox_data); + if (st) { + buf_count = sprintf(buf, "%d\n", prox_data); + chip->prox_reading = prox_data; + } + } else + buf_count = sprintf(buf, "%d\n", chip->prox_reading); + + mutex_unlock(&chip->lock); + return buf_count; +} + +/* Display als data */ +static ssize_t show_als_data(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + int als_ir_data; + bool st; + ssize_t buf_count = 0; + + dev_vdbg(dev, "%s()\n", __func__); + mutex_lock(&chip->lock); + + if (chip->als_ir_mode == MODE_ALS) { + st = isl29028_read_als_ir(chip->client, &als_ir_data); + if (st) { + /* convert als data count to lux */ + /* if als_range = 0, lux = count * 0.0326 */ + /* if als_range = 1, lux = count * 0.522 */ + if (!chip->als_range) + als_ir_data = (als_ir_data * 326) / 10000; + else + als_ir_data = (als_ir_data * 522) / 1000; + + buf_count = sprintf(buf, "%d\n", als_ir_data); + chip->als_reading = als_ir_data; + } + } else + buf_count = sprintf(buf, "%d\n", chip->als_reading); + mutex_unlock(&chip->lock); + return buf_count; +} + +/* Display IR data */ +static ssize_t show_ir_data(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + int als_ir_data; + bool st; + ssize_t buf_count = 0; + + dev_vdbg(dev, "%s()\n", __func__); + mutex_lock(&chip->lock); + + if (chip->als_ir_mode == MODE_IR) { + st = isl29028_read_als_ir(chip->client, &als_ir_data); + if (st) { + buf_count = sprintf(buf, "%d\n", als_ir_data); + chip->ir_reading = als_ir_data; + } + } else + buf_count = sprintf(buf, "%d\n", chip->ir_reading); + mutex_unlock(&chip->lock); + return buf_count; +} + +/* Wait for the proximity threshold interrupt*/ +static ssize_t show_wait_proxim_int(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + + dev_vdbg(dev, "%s()\n", __func__); + if (!chip->is_int_enable) { + dev_err(dev, "%s() Interrupt mode not supported\n", __func__); + return sprintf(buf, "error\n"); + } + + mutex_lock(&chip->lock); + st = isl29018_set_proxim_persist(client, true, chip->prox_persist); + if (!st) { + dev_err(dev, "%s() Error in configuration\n", __func__); + mutex_unlock(&chip->lock); + return sprintf(buf, "error\n"); + } + + chip->is_proxim_int_waiting = true; + mutex_unlock(&chip->lock); + wait_for_completion(&chip->prox_completion); + mutex_lock(&chip->lock); + chip->is_proxim_int_waiting = false; + isl29018_set_proxim_persist(client, false, chip->prox_persist); + mutex_unlock(&chip->lock); + return sprintf(buf, "done\n"); +} + +/* Wait for the als/ir interrupt*/ +static ssize_t show_wait_als_ir_int(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + struct i2c_client *client = chip->client; + bool st; + + dev_vdbg(dev, "%s()\n", __func__); + if (!chip->is_int_enable) { + dev_err(dev, "%s() Interrupt mode not supported\n", __func__); + return sprintf(buf, "error\n"); + } + + mutex_lock(&chip->lock); + + st = isl29018_set_als_persist(client, true, chip->als_persist); + if (!st) { + dev_err(dev, "%s() Error in als ir int configuration\n", + __func__); + mutex_unlock(&chip->lock); + return sprintf(buf, "error\n"); + } + + chip->is_als_int_waiting = true; + mutex_unlock(&chip->lock); + wait_for_completion(&chip->als_completion); + mutex_lock(&chip->lock); + chip->is_als_int_waiting = false; + st = isl29018_set_als_persist(client, false, chip->als_persist); + mutex_unlock(&chip->lock); + return sprintf(buf, "done\n"); +} + +/* Read name */ +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct iio_dev *indio_dev = dev_get_drvdata(dev); + struct isl29028_chip *chip = indio_dev->dev_data; + return sprintf(buf, "%s\n", chip->client->name); +} + +static IIO_DEVICE_ATTR(proximity_low_threshold, S_IRUGO | S_IWUSR, + show_proxim_low_threshold, store_proxim_low_threshold, 0); +static IIO_DEVICE_ATTR(proximity_high_threshold, S_IRUGO | S_IWUSR, + show_proxim_high_threshold, store_proxim_high_threshold, 0); +static IIO_DEVICE_ATTR(proximity_persist, S_IRUGO | S_IWUSR, + show_proxim_persist, store_proxim_persist, 0); +static IIO_DEVICE_ATTR(proximity_period, S_IRUGO | S_IWUSR, + show_prox_period, store_prox_period, 0); +static IIO_DEVICE_ATTR(proximity_enable, S_IRUGO | S_IWUSR, + show_prox_enable, store_prox_enable, 0); +static IIO_DEVICE_ATTR(wait_proxim_thres, S_IRUGO, + show_wait_proxim_int, NULL, 0); +static IIO_DEVICE_ATTR(proximity_value, S_IRUGO, + show_proxim_data, NULL, 0); + +static IIO_DEVICE_ATTR(als_low_threshold, S_IRUGO | S_IWUSR, + show_als_low_threshold, store_als_low_threshold, 0); +static IIO_DEVICE_ATTR(als_high_threshold, S_IRUGO | S_IWUSR, + show_als_high_threshold, store_als_high_threshold, 0); +static IIO_DEVICE_ATTR(als_persist, S_IRUGO | S_IWUSR, + show_als_persist, store_als_persist, 0); +static IIO_DEVICE_ATTR(als_ir_mode, S_IRUGO | S_IWUSR, + show_als_ir_mode, store_als_ir_mode, 0); +static IIO_DEVICE_ATTR(als_value, S_IRUGO, + show_als_data, NULL, 0); +static IIO_DEVICE_ATTR(wait_als_ir_thres, S_IRUGO, + show_wait_als_ir_int, NULL, 0); + +static IIO_DEVICE_ATTR(ir_value, S_IRUGO, + show_ir_data, NULL, 0); +static IIO_DEVICE_ATTR(ir_low_threshold, S_IRUGO | S_IWUSR, + show_ir_low_threshold, store_ir_low_threshold, 0); +static IIO_DEVICE_ATTR(ir_high_threshold, S_IRUGO | S_IWUSR, + show_ir_high_threshold, store_ir_high_threshold, 0); + +static IIO_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +static struct attribute *isl29028_attributes[] = { + &iio_dev_attr_name.dev_attr.attr, + + &iio_dev_attr_ir_value.dev_attr.attr, + + &iio_dev_attr_als_low_threshold.dev_attr.attr, + &iio_dev_attr_als_high_threshold.dev_attr.attr, + &iio_dev_attr_als_persist.dev_attr.attr, + &iio_dev_attr_als_ir_mode.dev_attr.attr, + &iio_dev_attr_als_value.dev_attr.attr, + &iio_dev_attr_wait_als_ir_thres.dev_attr.attr, + &iio_dev_attr_ir_low_threshold.dev_attr.attr, + &iio_dev_attr_ir_high_threshold.dev_attr.attr, + + &iio_dev_attr_proximity_low_threshold.dev_attr.attr, + &iio_dev_attr_proximity_high_threshold.dev_attr.attr, + &iio_dev_attr_proximity_enable.dev_attr.attr, + &iio_dev_attr_proximity_period.dev_attr.attr, + &iio_dev_attr_proximity_persist.dev_attr.attr, + &iio_dev_attr_proximity_value.dev_attr.attr, + &iio_dev_attr_wait_proxim_thres.dev_attr.attr, + NULL +}; + +static const struct attribute_group isl29108_group = { + .attrs = isl29028_attributes, +}; + +static int isl29028_chip_init(struct i2c_client *client) +{ + struct isl29028_chip *chip = i2c_get_clientdata(client); + int i; + bool st; + + for (i = 0; i < ARRAY_SIZE(chip->reg_cache); i++) + chip->reg_cache[i] = 0; + + chip->is_prox_enable = 0; + chip->prox_low_thres = 0; + chip->prox_high_thres = 0xFF; + chip->prox_period = 0; + chip->prox_reading = 0; + + chip->als_low_thres = 0; + chip->als_high_thres = 0xFFF; + chip->als_range = 1; + chip->als_reading = 0; + chip->als_ir_mode = 0; + + chip->ir_high_thres = 0xFFF; + chip->ir_low_thres = 0; + chip->ir_reading = 0; + + chip->is_int_enable = false; + chip->prox_persist = 1; + chip->als_persist = 1; + chip->is_proxim_int_waiting = false; + chip->is_als_int_waiting = false; + + st = isl29028_write_data(client, ISL29028_REG_ADD_TEST1_MODE, + 0x0, 0xFF, 0); + if (st) + st = isl29028_write_data(client, ISL29028_REG_ADD_TEST2_MODE, + 0x0, 0xFF, 0); + if (st) + st = isl29028_write_data(client, ISL29028_REG_ADD_CONFIGURE, + 0x0, 0xFF, 0); + if (st) + msleep(1); + if (!st) { + dev_err(&client->dev, "%s(): fails\n", __func__); + return -ENODEV; + } + return 0; +} + +static irqreturn_t threshold_isr(int irq, void *irq_data) +{ + struct isl29028_chip *chip = (struct isl29028_chip *)irq_data; + s32 int_reg; + struct i2c_client *client = chip->client; + + int_reg = i2c_smbus_read_byte_data(client, ISL29028_REG_ADD_INTERRUPT); + if (int_reg < 0) { + dev_err(&client->dev, "Error in reading register %d, error %d\n", + ISL29028_REG_ADD_INTERRUPT, int_reg); + return IRQ_HANDLED; + } + + if (int_reg & INTERRUPT_PROX_FLAG_MASK) { + /* Write 0 to clear */ + isl29028_write_data(client, + ISL29028_REG_ADD_INTERRUPT, INTERRUPT_PROX_FLAG_DIS, + INTERRUPT_PROX_FLAG_MASK, INTERRUPT_PROX_FLAG_SH); + if (chip->is_proxim_int_waiting) + complete(&chip->prox_completion); + } + + if (int_reg & INTERRUPT_ALS_FLAG_MASK) { + /* Write 0 to clear */ + isl29028_write_data(client, + ISL29028_REG_ADD_INTERRUPT, INTERRUPT_ALS_FLAG_DIS, + INTERRUPT_ALS_FLAG_MASK, INTERRUPT_ALS_FLAG_SH); + if (chip->is_als_int_waiting) + complete(&chip->als_completion); + } + + return IRQ_HANDLED; +} + +static int __devinit isl29028_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct isl29028_chip *chip; + int err; + + dev_dbg(&client->dev, "%s() called\n", __func__); + + chip = kzalloc(sizeof(struct isl29028_chip), GFP_KERNEL); + if (!chip) { + dev_err(&client->dev, "Memory allocation fails\n"); + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, chip); + chip->client = client; + chip->irq = client->irq; + + mutex_init(&chip->lock); + + err = isl29028_chip_init(client); + if (err) + goto exit_free; + + init_completion(&chip->prox_completion); + init_completion(&chip->als_completion); + + if (chip->irq > 0) { + err = request_threaded_irq(chip->irq, NULL, threshold_isr, + IRQF_SHARED, "ISL29028", chip); + if (err) { + dev_err(&client->dev, "Unable to register irq %d; " + "error %d\n", chip->irq, err); + goto exit_free; + } + } + + chip->is_int_enable = true; + chip->indio_dev = iio_allocate_device(); + if (!chip->indio_dev) { + dev_err(&client->dev, "iio allocation fails\n"); + goto exit_irq; + } + + chip->indio_dev->attrs = &isl29108_group; + chip->indio_dev->dev.parent = &client->dev; + chip->indio_dev->dev_data = (void *)(chip); + chip->indio_dev->driver_module = THIS_MODULE; + chip->indio_dev->modes = INDIO_DIRECT_MODE; + err = iio_device_register(chip->indio_dev); + if (err) { + dev_err(&client->dev, "iio registration fails\n"); + goto exit_iio_free; + } + dev_dbg(&client->dev, "%s() success\n", __func__); + return 0; + +exit_iio_free: + iio_free_device(chip->indio_dev); +exit_irq: + if (chip->irq > 0) + free_irq(chip->irq, chip); +exit_free: + kfree(chip); +exit: + return err; +} + +static int __devexit isl29028_remove(struct i2c_client *client) +{ + struct isl29028_chip *chip = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s()\n", __func__); + iio_device_unregister(chip->indio_dev); + if (chip->irq > 0) + free_irq(chip->irq, chip); + kfree(chip); + return 0; +} + +static const struct i2c_device_id isl29028_id[] = { + {"isl29028", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, isl29028_id); + +static struct i2c_driver isl29028_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "isl29028", + .owner = THIS_MODULE, + }, + .probe = isl29028_probe, + .remove = __devexit_p(isl29028_remove), + .id_table = isl29028_id, +}; + +static int __init isl29028_init(void) +{ + return i2c_add_driver(&isl29028_driver); +} + +static void __exit isl29028_exit(void) +{ + i2c_del_driver(&isl29028_driver); +} + +module_init(isl29028_init); +module_exit(isl29028_exit); |